# fileio.cpp

Go to the documentation of this file.

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