# fileio.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
    00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "fileio_func.h"
    00014 #include "debug.h"
    00015 #include "fios.h"
    00016 #include "string_func.h"
    00017 #include "tar_type.h"
    00018 #ifdef WIN32
    00019 #include <windows.h>
    00020 # define access _taccess
    00021 #elif defined(__HAIKU__)
    00022 #include <Path.h>
    00023 #include <storage/FindDirectory.h>
    00024 #else
    00025 #if defined(OPENBSD) || defined(DOS)
    00026 #include <unistd.h>
    00027 #endif
    00028 #include <pwd.h>
    00029 #endif
    00030 #include <sys/stat.h>
    00031 #include <algorithm>
    00032
    00033 /*************************************************/
    00034 /* FILE IO ROUTINES ******************************/
    00035 /*************************************************/
    00036
    00038 #define FIO_BUFFER_SIZE 512
    00039
    00041 struct Fio {
    00042   byte *buffer, *buffer_end;
    00043   size_t pos;
    00044   FILE *cur_fh;
    00045   const char *filename;
    00046   FILE *handles[MAX_FILE_SLOTS];
    00047   byte buffer_start[FIO_BUFFER_SIZE];
    00048   const char *filenames[MAX_FILE_SLOTS];
    00049   char *shortnames[MAX_FILE_SLOTS];
    00050 #if defined(LIMITED_FDS)
    00051   uint open_handles;
    00052   uint usage_count[MAX_FILE_SLOTS];
    00053 #endif /* LIMITED_FDS */
    00054 };
    00055
    00056 static Fio _fio;
    00057
    00059 static bool _do_scan_working_directory = true;
    00060
    00061 extern char *_config_file;
    00062 extern char *_highscore_file;
    00063
    00065 size_t FioGetPos()
    00066 {
    00067   return _fio.pos + (_fio.buffer - _fio.buffer_end);
    00068 }
    00069
    00075 const char *FioGetFilename(uint8 slot)
    00076 {
    00077   return _fio.shortnames[slot];
    00078 }
    00079
    00080 void FioSeekTo(size_t pos, int mode)
    00081 {
    00082   if (mode == SEEK_CUR) pos += FioGetPos();
    00083   _fio.buffer = _fio.buffer_end = _fio.buffer_start + FIO_BUFFER_SIZE;
    00084   _fio.pos = pos;
    00085   fseek(_fio.cur_fh, _fio.pos, SEEK_SET);
    00086 }
    00087
    00088 #if defined(LIMITED_FDS)
    00089 static void FioRestoreFile(int slot)
    00090 {
    00091   /* Do we still have the file open, or should we reopen it? */
    00092   if (_fio.handles[slot] == NULL) {
    00093     DEBUG(misc, 6, "Restoring file '%s' in slot '%d' from disk", _fio.filenames[slot], slot);
    00094     FioOpenFile(slot, _fio.filenames[slot]);
    00095   }
    00096   _fio.usage_count[slot]++;
    00097 }
    00098 #endif /* LIMITED_FDS */
    00099
    00100 /* Seek to a file and a position */
    00101 void FioSeekToFile(uint8 slot, size_t pos)
    00102 {
    00103   FILE *f;
    00104 #if defined(LIMITED_FDS)
    00105   /* Make sure we have this file open */
    00106   FioRestoreFile(slot);
    00107 #endif /* LIMITED_FDS */
    00108   f = _fio.handles[slot];
    00109   assert(f != NULL);
    00110   _fio.cur_fh = f;
    00111   _fio.filename = _fio.filenames[slot];
    00112   FioSeekTo(pos, SEEK_SET);
    00113 }
    00114
    00115 byte FioReadByte()
    00116 {
    00117   if (_fio.buffer == _fio.buffer_end) {
    00118     _fio.buffer = _fio.buffer_start;
    00119     size_t size = fread(_fio.buffer, 1, FIO_BUFFER_SIZE, _fio.cur_fh);
    00120     _fio.pos += size;
    00121     _fio.buffer_end = _fio.buffer_start + size;
    00122
    00123     if (size == 0) return 0;
    00124   }
    00125   return *_fio.buffer++;
    00126 }
    00127
    00128 void FioSkipBytes(int n)
    00129 {
    00130   for (;;) {
    00131     int m = min(_fio.buffer_end - _fio.buffer, n);
    00132     _fio.buffer += m;
    00133     n -= m;
    00134     if (n == 0) break;
    00135     FioReadByte();
    00136     n--;
    00137   }
    00138 }
    00139
    00140 uint16 FioReadWord()
    00141 {
    00142   byte b = FioReadByte();
    00143   return (FioReadByte() << 8) | b;
    00144 }
    00145
    00146 uint32 FioReadDword()
    00147 {
    00148   uint b = FioReadWord();
    00149   return (FioReadWord() << 16) | b;
    00150 }
    00151
    00152 void FioReadBlock(void *ptr, size_t size)
    00153 {
    00154   FioSeekTo(FioGetPos(), SEEK_SET);
    00155   _fio.pos += fread(ptr, 1, size, _fio.cur_fh);
    00156 }
    00157
    00158 static inline void FioCloseFile(int slot)
    00159 {
    00160   if (_fio.handles[slot] != NULL) {
    00161     fclose(_fio.handles[slot]);
    00162
    00163     free(_fio.shortnames[slot]);
    00164     _fio.shortnames[slot] = NULL;
    00165
    00166     _fio.handles[slot] = NULL;
    00167 #if defined(LIMITED_FDS)
    00168     _fio.open_handles--;
    00169 #endif /* LIMITED_FDS */
    00170   }
    00171 }
    00172
    00173 void FioCloseAll()
    00174 {
    00175   for (int i = 0; i != lengthof(_fio.handles); i++) {
    00176     FioCloseFile(i);
    00177   }
    00178 }
    00179
    00180 #if defined(LIMITED_FDS)
    00181 static void FioFreeHandle()
    00182 {
    00183   /* If we are about to open a file that will exceed the limit, close a file */
    00184   if (_fio.open_handles + 1 == LIMITED_FDS) {
    00185     uint i, count;
    00186     int slot;
    00187
    00188     count = UINT_MAX;
    00189     slot = -1;
    00190     /* Find the file that is used the least */
    00191     for (i = 0; i < lengthof(_fio.handles); i++) {
    00192       if (_fio.handles[i] != NULL && _fio.usage_count[i] < count) {
    00193         count = _fio.usage_count[i];
    00194         slot  = i;
    00195       }
    00196     }
    00197     assert(slot != -1);
    00198     DEBUG(misc, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio.filenames[slot], slot);
    00199     FioCloseFile(slot);
    00200   }
    00201 }
    00202 #endif /* LIMITED_FDS */
    00203
    00204 void FioOpenFile(int slot, const char *filename)
    00205 {
    00206   FILE *f;
    00207
    00208 #if defined(LIMITED_FDS)
    00209   FioFreeHandle();
    00210 #endif /* LIMITED_FDS */
    00211   f = FioFOpenFile(filename);
    00212   if (f == NULL) usererror("Cannot open file '%s'", filename);
    00213   uint32 pos = ftell(f);
    00214
    00215   FioCloseFile(slot); // if file was opened before, close it
    00216   _fio.handles[slot] = f;
    00217   _fio.filenames[slot] = filename;
    00218
    00219   /* Store the filename without path and extension */
    00220   const char *t = strrchr(filename, PATHSEPCHAR);
    00221   _fio.shortnames[slot] = strdup(t == NULL ? filename : t);
    00222   char *t2 = strrchr(_fio.shortnames[slot], '.');
    00223   if (t2 != NULL) *t2 = '\0';
    00224   strtolower(_fio.shortnames[slot]);
    00225
    00226 #if defined(LIMITED_FDS)
    00227   _fio.usage_count[slot] = 0;
    00228   _fio.open_handles++;
    00229 #endif /* LIMITED_FDS */
    00230   FioSeekToFile(slot, pos);
    00231 }
    00232
    00233 static const char * const _subdirs[NUM_SUBDIRS] = {
    00234   "",
    00235   "save" PATHSEP,
    00236   "save" PATHSEP "autosave" PATHSEP,
    00237   "scenario" PATHSEP,
    00238   "scenario" PATHSEP "heightmap" PATHSEP,
    00239   "gm" PATHSEP,
    00240   "data" PATHSEP,
    00241   "lang" PATHSEP,
    00242   "ai" PATHSEP,
    00243   "ai" PATHSEP "library" PATHSEP,
    00244 };
    00245
    00246 const char *_searchpaths[NUM_SEARCHPATHS];
    00247 TarList _tar_list;
    00248 TarFileList _tar_filelist;
    00249
    00250 typedef std::map<std::string, std::string> TarLinkList;
    00251 static TarLinkList _tar_linklist;
    00252
    00259 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
    00260 {
    00261   FILE *f = FioFOpenFile(filename, "rb", subdir);
    00262   if (f == NULL) return false;
    00263
    00264   FioFCloseFile(f);
    00265   return true;
    00266 }
    00267
    00273 bool FileExists(const char *filename)
    00274 {
    00275 #if defined(WINCE)
    00276   /* There is always one platform that doesn't support basic commands... */
    00277   HANDLE hand = CreateFile(OTTD2FS(filename), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
    00278   if (hand == INVALID_HANDLE_VALUE) return 1;
    00279   CloseHandle(hand);
    00280   return 0;
    00281 #else
    00282   return access(OTTD2FS(filename), 0) == 0;
    00283 #endif
    00284 }
    00285
    00289 void FioFCloseFile(FILE *f)
    00290 {
    00291   fclose(f);
    00292 }
    00293
    00294 char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
    00295 {
    00296   assert(subdir < NUM_SUBDIRS);
    00297   assert(sp < NUM_SEARCHPATHS);
    00298
    00299   snprintf(buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
    00300   return buf;
    00301 }
    00302
    00303 char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename)
    00304 {
    00305   Searchpath sp;
    00306   assert(subdir < NUM_SUBDIRS);
    00307
    00308   FOR_ALL_SEARCHPATHS(sp) {
    00309     FioGetFullPath(buf, buflen, sp, subdir, filename);
    00310     if (FileExists(buf)) break;
    00311 #if !defined(WIN32)
    00312     /* Be, as opening files, aware that sometimes the filename
    00313      * might be in uppercase when it is in lowercase on the
    00314      * disk. Ofcourse Windows doesn't care about casing. */
    00315     strtolower(buf + strlen(_searchpaths[sp]) - 1);
    00316     if (FileExists(buf)) break;
    00317 #endif
    00318   }
    00319
    00320   return buf;
    00321 }
    00322
    00323 char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir)
    00324 {
    00325   assert(subdir < NUM_SUBDIRS);
    00326   assert(sp < NUM_SEARCHPATHS);
    00327
    00328   snprintf(buf, buflen, "%s%s", _searchpaths[sp], _subdirs[subdir]);
    00329   return buf;
    00330 }
    00331
    00332 char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir)
    00333 {
    00334   Searchpath sp;
    00335
    00336   /* Find and return the first valid directory */
    00337   FOR_ALL_SEARCHPATHS(sp) {
    00338     char *ret = FioAppendDirectory(buf, buflen, sp, subdir);
    00339     if (FileExists(buf)) return ret;
    00340   }
    00341
    00342   /* Could not find the directory, fall back to a base path */
    00343   ttd_strlcpy(buf, _personal_dir, buflen);
    00344
    00345   return buf;
    00346 }
    00347
    00348 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
    00349 {
    00350 #if defined(WIN32) && defined(UNICODE)
    00351   /* fopen is implemented as a define with ellipses for
    00352    * Unicode support (prepend an L). As we are not sending
    00353    * a string, but a variable, it 'renames' the variable,
    00354    * so make that variable to makes it compile happily */
    00355   wchar_t Lmode[5];
    00356   MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
    00357 #endif
    00358   FILE *f = NULL;
    00359   char buf[MAX_PATH];
    00360
    00361   if (subdir == NO_DIRECTORY) {
    00362     strecpy(buf, filename, lastof(buf));
    00363   } else {
    00364     snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
    00365   }
    00366
    00367 #if defined(WIN32)
    00368   if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL;
    00369 #endif
    00370
    00371   f = fopen(buf, mode);
    00372 #if !defined(WIN32)
    00373   if (f == NULL) {
    00374     strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1));
    00375     f = fopen(buf, mode);
    00376   }
    00377 #endif
    00378   if (f != NULL && filesize != NULL) {
    00379     /* Find the size of the file */
    00380     fseek(f, 0, SEEK_END);
    00381     *filesize = ftell(f);
    00382     fseek(f, 0, SEEK_SET);
    00383   }
    00384   return f;
    00385 }
    00386
    00387 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
    00388 {
    00389   FILE *f = fopen(entry->tar_filename, "rb");
    00390   if (f == NULL) return f;
    00391
    00392   fseek(f, entry->position, SEEK_SET);
    00393   if (filesize != NULL) *filesize = entry->size;
    00394   return f;
    00395 }
    00396
    00398 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
    00399 {
    00400   FILE *f = NULL;
    00401   Searchpath sp;
    00402
    00403   assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
    00404
    00405   FOR_ALL_SEARCHPATHS(sp) {
    00406     f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
    00407     if (f != NULL || subdir == NO_DIRECTORY) break;
    00408   }
    00409
    00410   /* We can only use .tar in case of data-dir, and read-mode */
    00411   if (f == NULL && mode[0] == 'r') {
    00412     static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
    00413     char resolved_name[MAX_RESOLVED_LENGTH];
    00414
    00415     /* Filenames in tars are always forced to be lowercase */
    00416     strecpy(resolved_name, filename, lastof(resolved_name));
    00417     strtolower(resolved_name);
    00418
    00419     size_t resolved_len = strlen(resolved_name);
    00420
    00421     /* Resolve ONE directory link */
    00422     for (TarLinkList::iterator link = _tar_linklist.begin(); link != _tar_linklist.end(); link++) {
    00423       const std::string &src = link->first;
    00424       size_t len = src.length();
    00425       if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) {
    00426         /* Apply link */
    00427         char resolved_name2[MAX_RESOLVED_LENGTH];
    00428         const std::string &dest = link->second;
    00429         strecpy(resolved_name2, &(resolved_name[len]), lastof(resolved_name2));
    00430         strecpy(resolved_name, dest.c_str(), lastof(resolved_name));
    00431         strecpy(&(resolved_name[dest.length()]), resolved_name2, lastof(resolved_name));
    00432         break; // Only resolve one level
    00433       }
    00434     }
    00435
    00436     TarFileList::iterator it = _tar_filelist.find(resolved_name);
    00437     if (it != _tar_filelist.end()) {
    00438       f = FioFOpenFileTar(&((*it).second), filesize);
    00439     }
    00440   }
    00441
    00442   /* Sometimes a full path is given. To support
    00443    * the 'subdirectory' must be 'removed'. */
    00444   if (f == NULL && subdir != NO_DIRECTORY) {
    00445     f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
    00446   }
    00447
    00448   return f;
    00449 }
    00450
    00455 static void FioCreateDirectory(const char *name)
    00456 {
    00457 #if defined(WIN32) || defined(WINCE)
    00458   CreateDirectory(OTTD2FS(name), NULL);
    00459 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
    00460   mkdir(OTTD2FS(name));
    00461 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
    00462   char buf[MAX_PATH];
    00463   ttd_strlcpy(buf, name, MAX_PATH);
    00464
    00465   size_t len = strlen(name) - 1;
    00466   if (buf[len] == '/') {
    00467     buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail
    00468   }
    00469
    00470   mkdir(OTTD2FS(buf), 0755);
    00471 #else
    00472   mkdir(OTTD2FS(name), 0755);
    00473 #endif
    00474 }
    00475
    00483 bool AppendPathSeparator(char *buf, size_t buflen)
    00484 {
    00485   size_t s = strlen(buf);
    00486
    00487   /* Length of string + path separator + '\0' */
    00488   if (s != 0 && buf[s - 1] != PATHSEPCHAR) {
    00489     if (s + 2 >= buflen) return false;
    00490
    00491     buf[s]     = PATHSEPCHAR;
    00492     buf[s + 1] = '\0';
    00493   }
    00494
    00495   return true;
    00496 }
    00497
    00504 char *BuildWithFullPath(const char *dir)
    00505 {
    00506   char *dest = MallocT<char>(MAX_PATH);
    00507   ttd_strlcpy(dest, dir, MAX_PATH);
    00508
    00509   /* Check if absolute or relative path */
    00510   const char *s = strchr(dest, PATHSEPCHAR);
    00511
    00512   /* Add absolute path */
    00513   if (s == NULL || dest != s) {
    00514     if (getcwd(dest, MAX_PATH) == NULL) *dest = '\0';
    00515     AppendPathSeparator(dest, MAX_PATH);
    00516     ttd_strlcat(dest, dir, MAX_PATH);
    00517   }
    00518   AppendPathSeparator(dest, MAX_PATH);
    00519
    00520   return dest;
    00521 }
    00522
    00523 const char *FioTarFirstDir(const char *tarname)
    00524 {
    00525   TarList::iterator it = _tar_list.find(tarname);
    00526   if (it == _tar_list.end()) return NULL;
    00527   return (*it).second.dirname;
    00528 }
    00529
    00530 static void TarAddLink(const std::string &srcParam, const std::string &destParam)
    00531 {
    00532   std::string src = srcParam;
    00533   std::string dest = destParam;
    00534   /* Tar internals assume lowercase */
    00535   std::transform(src.begin(), src.end(), src.begin(), tolower);
    00536   std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
    00537
    00538   TarFileList::iterator dest_file = _tar_filelist.find(dest);
    00539   if (dest_file != _tar_filelist.end()) {
    00540     /* Link to file. Process the link like the destination file. */
    00541     _tar_filelist.insert(TarFileList::value_type(src, dest_file->second));
    00542   } else {
    00543     /* Destination file not found. Assume 'link to directory'
    00544      * Append PATHSEPCHAR to 'src' and 'dest' if needed */
    00545     const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
    00546     const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
    00547     _tar_linklist.insert(TarLinkList::value_type(src_path, dst_path));
    00548   }
    00549 }
    00550
    00551 void FioTarAddLink(const char *src, const char *dest)
    00552 {
    00553   TarAddLink(src, dest);
    00554 }
    00555
    00561 static void SimplifyFileName(char *name)
    00562 {
    00563   /* Force lowercase */
    00564   strtolower(name);
    00565
    00566   /* Tar-files always have '/' path-seperator, but we want our PATHSEPCHAR */
    00567 #if (PATHSEPCHAR != '/')
    00568   for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
    00569 #endif
    00570 }
    00571
    00572 /* static */ uint TarScanner::DoScan()
    00573 {
    00574   _tar_filelist.clear();
    00575   _tar_list.clear();
    00576
    00577   DEBUG(misc, 1, "Scanning for tars");
    00578   TarScanner fs;
    00579   uint num = fs.Scan(".tar", DATA_DIR, false);
    00580   num += fs.Scan(".tar", AI_DIR, false);
    00581   num += fs.Scan(".tar", AI_LIBRARY_DIR, false);
    00582   num += fs.Scan(".tar", SCENARIO_DIR, false);
    00583   DEBUG(misc, 1, "Scan complete, found %d files", num);
    00584   return num;
    00585 }
    00586
    00587 bool TarScanner::AddFile(const char *filename, size_t basepath_length)
    00588 {
    00589   /* The TAR-header, repeated for every file */
    00590   typedef struct TarHeader {
    00591     char name[100];
    00592     char mode[8];
    00593     char uid[8];
    00594     char gid[8];
    00595     char size[12];
    00596     char mtime[12];
    00597     char chksum[8];
    00598     char typeflag;
    00599     char linkname[100];
    00600     char magic[6];
    00601     char version[2];
    00602     char uname[32];
    00603     char gname[32];
    00604     char devmajor[8];
    00605     char devminor[8];
    00606     char prefix[155];
    00607
    00608     char unused[12];
    00609   } TarHeader;
    00610
    00611   /* Check if we already seen this file */
    00612   TarList::iterator it = _tar_list.find(filename);
    00613   if (it != _tar_list.end()) return false;
    00614
    00615   FILE *f = fopen(filename, "rb");
    00616   /* Although the file has been found there can be
    00617    * a number of reasons we cannot open the file.
    00618    * Most common case is when we simply have not
    00619    * been given read access. */
    00620   if (f == NULL) return false;
    00621
    00622   const char *dupped_filename = strdup(filename);
    00623   _tar_list[filename].filename = dupped_filename;
    00624   _tar_list[filename].dirname = NULL;
    00625
    00626   TarLinkList links;
    00627
    00628   TarHeader th;
    00629   char buf[sizeof(th.name) + 1], *end;
    00630   char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
    00631   char link[sizeof(th.linkname) + 1];
    00632   char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
    00633   size_t num = 0, pos = 0;
    00634
    00635   /* Make a char of 512 empty bytes */
    00636   char empty[512];
    00637   memset(&empty[0], 0, sizeof(empty));
    00638
    00639   for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
    00640     size_t num_bytes_read = fread(&th, 1, 512, f);
    00641     if (num_bytes_read != 512) break;
    00642     pos += num_bytes_read;
    00643
    00644     /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
    00645     if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
    00646       /* If we have only zeros in the block, it can be an end-of-file indicator */
    00647       if (memcmp(&th, &empty[0], 512) == 0) continue;
    00648
    00649       DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
    00650       return false;
    00651     }
    00652
    00653     name[0] = '\0';
    00654     size_t len = 0;
    00655
    00656     /* The prefix contains the directory-name */
    00657     if (th.prefix[0] != '\0') {
    00658       memcpy(name, th.prefix, sizeof(th.prefix));
    00659       name[sizeof(th.prefix)] = '\0';
    00660       len = strlen(name);
    00661       name[len] = PATHSEPCHAR;
    00662       len++;
    00663     }
    00664
    00665     /* Copy the name of the file in a safe way at the end of 'name' */
    00666     memcpy(&name[len], th.name, sizeof(th.name));
    00667     name[len + sizeof(th.name)] = '\0';
    00668
    00669     /* Calculate the size of the file.. for some strange reason this is stored as a string */
    00670     memcpy(buf, th.size, sizeof(th.size));
    00671     buf[sizeof(th.size)] = '\0';
    00672     size_t skip = strtoul(buf, &end, 8);
    00673
    00674     switch (th.typeflag) {
    00675       case '\0':
    00676       case '0': { // regular file
    00677         /* Ignore empty files */
    00678         if (skip == 0) break;
    00679
    00680         if (strlen(name) == 0) break;
    00681
    00682         /* Store this entry in the list */
    00683         TarFileListEntry entry;
    00684         entry.tar_filename = dupped_filename;
    00685         entry.size         = skip;
    00686         entry.position     = pos;
    00687
    00688         /* Convert to lowercase and our PATHSEPCHAR */
    00689         SimplifyFileName(name);
    00690
    00691         DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
    00692         if (_tar_filelist.insert(TarFileList::value_type(name, entry)).second) num++;
    00693
    00694         break;
    00695       }
    00696
    00697       case '1': // hard links
    00698       case '2': { // symbolic links
    00699         /* Copy the destination of the link in a safe way at the end of 'linkname' */
    00700         memcpy(link, th.linkname, sizeof(th.linkname));
    00701         link[sizeof(th.linkname)] = '\0';
    00702
    00703         if (strlen(name) == 0 || strlen(link) == 0) break;
    00704
    00705         /* Convert to lowercase and our PATHSEPCHAR */
    00706         SimplifyFileName(name);
    00707         SimplifyFileName(link);
    00708
    00709         /* Only allow relative links */
    00710         if (link[0] == PATHSEPCHAR) {
    00711           DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
    00712           break;
    00713         }
    00714
    00715         /* Process relative path.
    00716          * Note: The destination of links must not contain any directory-links. */
    00717         strecpy(dest, name, lastof(dest));
    00718         char *destpos = strrchr(dest, PATHSEPCHAR);
    00719         if (destpos == NULL) destpos = dest;
    00720         *destpos = '\0';
    00721
    00722         char *pos = link;
    00723         while (*pos != '\0') {
    00724           char *next = strchr(link, PATHSEPCHAR);
    00725           if (next == NULL) next = pos + strlen(pos);
    00726
    00727           /* Skip '.' (current dir) */
    00728           if (next != pos + 1 || pos[0] != '.') {
    00729             if (next == pos + 2 && pos[0] == '.' && pos[1] == '.') {
    00730               /* level up */
    00731               if (dest[0] == '\0') {
    00732                 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
    00733                 break;
    00734               }
    00735
    00736               /* Truncate 'dest' after last PATHSEPCHAR.
    00737                * This assumes that the truncated part is a real directory and not a link. */
    00738               destpos = strrchr(dest, PATHSEPCHAR);
    00739               if (destpos == NULL) destpos = dest;
    00740             } else {
    00741               /* Append at end of 'dest' */
    00742               if (destpos != dest) *(destpos++) = PATHSEPCHAR;
    00743               strncpy(destpos, pos, next - pos); // Safe as we do '\0'-termination ourselves
    00744               destpos += next - pos;
    00745             }
    00746             *destpos = '\0';
    00747           }
    00748
    00749           pos = next;
    00750         }
    00751
    00752         /* Store links in temporary list */
    00753         DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
    00754         links.insert(TarLinkList::value_type(name, dest));
    00755
    00756         break;
    00757       }
    00758
    00759       case '5': // directory
    00760         /* Convert to lowercase and our PATHSEPCHAR */
    00761         SimplifyFileName(name);
    00762
    00763         /* Store the first directory name we detect */
    00764         DEBUG(misc, 6, "Found dir in tar: %s", name);
    00765         if (_tar_list[filename].dirname == NULL) _tar_list[filename].dirname = strdup(name);
    00766         break;
    00767
    00768       default:
    00769         /* Ignore other types */
    00770         break;
    00771     }
    00772
    00773     /* Skip to the next block.. */
    00774     skip = Align(skip, 512);
    00775     fseek(f, skip, SEEK_CUR);
    00776     pos += skip;
    00777   }
    00778
    00779   DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
    00780   fclose(f);
    00781
    00782   /* Resolve file links and store directory links.
    00783    * We restrict usage of links to two cases:
    00784    *  1) Links to directories:
    00785    *      Both the source path and the destination path must NOT contain any further links.
    00786    *      When resolving files at most one directory link is resolved.
    00787    *  2) Links to files:
    00788    *      The destination path must NOT contain any links.
    00789    *      The source path may contain one directory link.
    00790    */
    00791   for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
    00792     const std::string &src = link->first;
    00793     const std::string &dest = link->second;
    00794     TarAddLink(src, dest);
    00795   }
    00796
    00797   return true;
    00798 }
    00799
    00806 bool ExtractTar(const char *tar_filename)
    00807 {
    00808   TarList::iterator it = _tar_list.find(tar_filename);
    00809   /* We don't know the file. */
    00810   if (it == _tar_list.end()) return false;
    00811
    00812   const char *dirname = (*it).second.dirname;
    00813
    00814   /* The file doesn't have a sub directory! */
    00815   if (dirname == NULL) return false;
    00816
    00817   char filename[MAX_PATH];
    00818   strecpy(filename, tar_filename, lastof(filename));
    00819   char *p = strrchr(filename, PATHSEPCHAR);
    00820   /* The file's path does not have a separator? */
    00821   if (p == NULL) return false;
    00822
    00823   p++;
    00824   strecpy(p, dirname, lastof(filename));
    00825   DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
    00826   FioCreateDirectory(filename);
    00827
    00828   for (TarFileList::iterator it2 = _tar_filelist.begin(); it2 != _tar_filelist.end(); it2++) {
    00829     if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
    00830
    00831     strecpy(p, (*it2).first.c_str(), lastof(filename));
    00832
    00833     DEBUG(misc, 9, "  extracting %s", filename);
    00834
    00835     /* First open the file in the .tar. */
    00836     size_t to_copy = 0;
    00837     FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
    00838     if (in == NULL) {
    00839       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
    00840       return false;
    00841     }
    00842
    00843     /* Now open the 'output' file. */
    00844     FILE *out = fopen(filename, "wb");
    00845     if (out == NULL) {
    00846       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
    00847       fclose(in);
    00848       return false;
    00849     }
    00850
    00851     /* Now read from the tar and write it into the file. */
    00852     char buffer[4096];
    00853     size_t read;
    00854     for (; to_copy != 0; to_copy -= read) {
    00855       read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
    00856       if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
    00857     }
    00858
    00859     /* Close everything up. */
    00860     fclose(in);
    00861     fclose(out);
    00862
    00863     if (to_copy != 0) {
    00864       DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
    00865       return false;
    00866     }
    00867   }
    00868
    00869   DEBUG(misc, 9, "  extraction successful");
    00870   return true;
    00871 }
    00872
    00873 #if defined(WIN32) || defined(WINCE)
    00874
    00879 extern void DetermineBasePaths(const char *exe);
    00880 #else /* defined(WIN32) || defined(WINCE) */
    00881
    00889 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
    00890 {
    00891   bool success = false;
    00892 #ifdef WITH_COCOA
    00893   char *app_bundle = strchr(exe, '.');
    00894   while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
    00895
    00896   if (app_bundle != NULL) app_bundle[0] = '\0';
    00897 #endif /* WITH_COCOA */
    00898   char *s = const_cast<char *>(strrchr(exe, PATHSEPCHAR));
    00899   if (s != NULL) {
    00900     *s = '\0';
    00901 #if defined(__DJGPP__)
    00902     /* If we want to go to the root, we can't use cd C:, but we must use '/' */
    00903     if (s[-1] == ':') chdir("/");
    00904 #endif
    00905     if (chdir(exe) != 0) {
    00906       DEBUG(misc, 0, "Directory with the binary does not exist?");
    00907     } else {
    00908       success = true;
    00909     }
    00910     *s = PATHSEPCHAR;
    00911   }
    00912 #ifdef WITH_COCOA
    00913   if (app_bundle != NULL) app_bundle[0] = '.';
    00914 #endif /* WITH_COCOA */
    00915   return success;
    00916 }
    00917
    00928 bool DoScanWorkingDirectory()
    00929 {
    00930   /* No working directory, so nothing to do. */
    00931   if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
    00932
    00933   /* Working directory is root, so do nothing. */
    00934   if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
    00935
    00936   /* No personal/home directory, so the working directory won't be that. */
    00937   if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
    00938
    00939   char tmp[MAX_PATH];
    00940   snprintf(tmp, lengthof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
    00941   AppendPathSeparator(tmp, MAX_PATH);
    00942   return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
    00943 }
    00944
    00949 void DetermineBasePaths(const char *exe)
    00950 {
    00951   char tmp[MAX_PATH];
    00952 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
    00953   _searchpaths[SP_PERSONAL_DIR] = NULL;
    00954 #else
    00955 #ifdef __HAIKU__
    00956   BPath path;
    00957   find_directory(B_USER_SETTINGS_DIRECTORY, &path);
    00958   const char *homedir = path.Path();
    00959 #else
    00960   const char *homedir = getenv("HOME");
    00961
    00962   if (homedir == NULL) {
    00963     const struct passwd *pw = getpwuid(getuid());
    00964     homedir = (pw == NULL) ? "" : pw->pw_dir;
    00965   }
    00966 #endif
    00967
    00968   snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
    00969   AppendPathSeparator(tmp, MAX_PATH);
    00970
    00971   _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
    00972 #endif
    00973
    00974 #if defined(WITH_SHARED_DIR)
    00975   snprintf(tmp, MAX_PATH, "%s", SHARED_DIR);
    00976   AppendPathSeparator(tmp, MAX_PATH);
    00977   _searchpaths[SP_SHARED_DIR] = strdup(tmp);
    00978 #else
    00979   _searchpaths[SP_SHARED_DIR] = NULL;
    00980 #endif
    00981
    00982 #if defined(__MORPHOS__) || defined(__AMIGA__)
    00983   _searchpaths[SP_WORKING_DIR] = NULL;
    00984 #else
    00985   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
    00986   AppendPathSeparator(tmp, MAX_PATH);
    00987   _searchpaths[SP_WORKING_DIR] = strdup(tmp);
    00988 #endif
    00989
    00990   _do_scan_working_directory = DoScanWorkingDirectory();
    00991
    00992   /* Change the working directory to that one of the executable */
    00993   if (ChangeWorkingDirectoryToExecutable(exe)) {
    00994     if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
    00995     AppendPathSeparator(tmp, MAX_PATH);
    00996     _searchpaths[SP_BINARY_DIR] = strdup(tmp);
    00997   } else {
    00998     _searchpaths[SP_BINARY_DIR] = NULL;
    00999   }
    01000
    01001   if (_searchpaths[SP_WORKING_DIR] != NULL) {
    01002     /* Go back to the current working directory. */
    01003     if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
    01004       DEBUG(misc, 0, "Failed to return to working directory!");
    01005     }
    01006   }
    01007
    01008 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
    01009   _searchpaths[SP_INSTALLATION_DIR] = NULL;
    01010 #else
    01011   snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
    01012   AppendPathSeparator(tmp, MAX_PATH);
    01013   _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
    01014 #endif
    01015 #ifdef WITH_COCOA
    01016 extern void cocoaSetApplicationBundleDir();
    01017   cocoaSetApplicationBundleDir();
    01018 #else
    01019   _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
    01020 #endif
    01021 }
    01022 #endif /* defined(WIN32) || defined(WINCE) */
    01023
    01024 char *_personal_dir;
    01025
    01032 void DeterminePaths(const char *exe)
    01033 {
    01034   DetermineBasePaths(exe);
    01035
    01036   Searchpath sp;
    01037   FOR_ALL_SEARCHPATHS(sp) {
    01038     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
    01039     DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
    01040   }
    01041
    01042   if (_config_file != NULL) {
    01043     _personal_dir = strdup(_config_file);
    01044     char *end = strrchr(_personal_dir, PATHSEPCHAR);
    01045     if (end == NULL) {
    01046       _personal_dir[0] = '\0';
    01047     } else {
    01048       end[1] = '\0';
    01049     }
    01050   } else {
    01051     char personal_dir[MAX_PATH];
    01052     FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg");
    01053
    01054     if (FileExists(personal_dir)) {
    01055       char *end = strrchr(personal_dir, PATHSEPCHAR);
    01056       if (end != NULL) end[1] = '\0';
    01057       _personal_dir = strdup(personal_dir);
    01058       _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
    01059     } else {
    01060       static const Searchpath new_openttd_cfg_order[] = {
    01061           SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
    01062         };
    01063
    01064       for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
    01065         if (IsValidSearchPath(new_openttd_cfg_order[i])) {
    01066           _personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
    01067           _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
    01068           break;
    01069         }
    01070       }
    01071     }
    01072   }
    01073
    01074   DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
    01075
    01076   _highscore_file = str_fmt("%shs.dat", _personal_dir);
    01077   extern char *_hotkeys_file;
    01078   _hotkeys_file = str_fmt("%shotkeys.cfg",  _personal_dir);
    01079
    01080   /* Make the necessary folders */
    01081 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
    01082   FioCreateDirectory(_personal_dir);
    01083 #endif
    01084
    01085   static const Subdirectory default_subdirs[] = {
    01086     SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR
    01087   };
    01088
    01089   for (uint i = 0; i < lengthof(default_subdirs); i++) {
    01090     char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
    01091     FioCreateDirectory(dir);
    01092     free(dir);
    01093   }
    01094
    01095   /* If we have network we make a directory for the autodownloading of content */
    01096   _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
    01097 #ifdef ENABLE_NETWORK
    01098   FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
    01099
    01100   /* Create the directory for each of the types of content */
    01101   const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, DATA_DIR, AI_DIR, AI_LIBRARY_DIR, GM_DIR };
    01102   for (uint i = 0; i < lengthof(dirs); i++) {
    01103     char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
    01104     FioCreateDirectory(tmp);
    01105     free(tmp);
    01106   }
    01107
    01108   extern char *_log_file;
    01109   _log_file = str_fmt("%sopenttd.log",  _personal_dir);
    01110 #else /* ENABLE_NETWORK */
    01111   /* If we don't have networking, we don't need to make the directory. But
    01112    * if it exists we keep it, otherwise remove it from the search paths. */
    01113   if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR]))  {
    01114     free((void*)_searchpaths[SP_AUTODOWNLOAD_DIR]);
    01115     _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
    01116   }
    01117 #endif /* ENABLE_NETWORK */
    01118
    01119   TarScanner::DoScan();
    01120 }
    01121
    01126 void SanitizeFilename(char *filename)
    01127 {
    01128   for (; *filename != '\0'; filename++) {
    01129     switch (*filename) {
    01130       /* The following characters are not allowed in filenames
    01131        * on at least one of the supported operating systems: */
    01132       case ':': case '\\': case '*': case '?': case '/':
    01133       case '<': case '>': case '|': case '"':
    01134         *filename = '_';
    01135         break;
    01136     }
    01137   }
    01138 }
    01139
    01140 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
    01141 {
    01142   FILE *in = fopen(filename, "rb");
    01143   if (in == NULL) return NULL;
    01144
    01145   fseek(in, 0, SEEK_END);
    01146   size_t len = ftell(in);
    01147   fseek(in, 0, SEEK_SET);
    01148   if (len > maxsize) {
    01149     fclose(in);
    01150     return NULL;
    01151   }
    01152   byte *mem = MallocT<byte>(len + 1);
    01153   mem[len] = 0;
    01154   if (fread(mem, len, 1, in) != 1) {
    01155     fclose(in);
    01156     free(mem);
    01157     return NULL;
    01158   }
    01159   fclose(in);
    01160
    01161   *lenp = len;
    01162   return mem;
    01163 }
    01164
    01165
    01175 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
    01176 {
    01177   extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
    01178
    01179   uint num = 0;
    01180   struct stat sb;
    01181   struct dirent *dirent;
    01182   DIR *dir;
    01183
    01184   if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
    01185
    01186   while ((dirent = readdir(dir)) != NULL) {
    01187     const char *d_name = FS2OTTD(dirent->d_name);
    01188     char filename[MAX_PATH];
    01189
    01190     if (!FiosIsValidFile(path, dirent, &sb)) continue;
    01191
    01192     snprintf(filename, lengthof(filename), "%s%s", path, d_name);
    01193
    01194     if (S_ISDIR(sb.st_mode)) {
    01195       /* Directory */
    01196       if (!recursive) continue;
    01197       if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
    01198       if (!AppendPathSeparator(filename, lengthof(filename))) continue;
    01199       num += ScanPath(fs, extension, filename, basepath_length, recursive);
    01200     } else if (S_ISREG(sb.st_mode)) {
    01201       /* File */
    01202       if (extension != NULL) {
    01203         char *ext = strrchr(filename, '.');
    01204
    01205         /* If no extension or extension isn't .grf, skip the file */
    01206         if (ext == NULL) continue;
    01207         if (strcasecmp(ext, extension) != 0) continue;
    01208       }
    01209
    01210       if (fs->AddFile(filename, basepath_length)) num++;
    01211     }
    01212   }
    01213
    01214   closedir(dir);
    01215
    01216   return num;
    01217 }
    01218
    01225 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
    01226 {
    01227   uint num = 0;
    01228   const char *filename = (*tar).first.c_str();
    01229
    01230   if (extension != NULL) {
    01231     const char *ext = strrchr(filename, '.');
    01232
    01233     /* If no extension or extension isn't .grf, skip the file */
    01234     if (ext == NULL) return false;
    01235     if (strcasecmp(ext, extension) != 0) return false;
    01236   }
    01237
    01238   if (fs->AddFile(filename, 0)) num++;
    01239
    01240   return num;
    01241 }
    01242
    01252 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
    01253 {
    01254   Searchpath sp;
    01255   char path[MAX_PATH];
    01256   TarFileList::iterator tar;
    01257   uint num = 0;
    01258
    01259   FOR_ALL_SEARCHPATHS(sp) {
    01260     /* Don't search in the working directory */
    01261     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
    01262
    01263     FioAppendDirectory(path, MAX_PATH, sp, sd);
    01264     num += ScanPath(this, extension, path, strlen(path), recursive);
    01265   }
    01266
    01267   if (tars) {
    01268     FOR_ALL_TARS(tar) {
    01269       num += ScanTar(this, extension, tar);
    01270     }
    01271   }
    01272
    01273   return num;
    01274 }
    01275
    01284 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
    01285 {
    01286   char path[MAX_PATH];
    01287   strecpy(path, directory, lastof(path));
    01288   if (!AppendPathSeparator(path, lengthof(path))) return 0;
    01289   return ScanPath(this, extension, path, strlen(path), recursive);
    01290 }