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