# fileio.cpp

Go to the documentation of this file.

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