# fileio.cpp

Go to the documentation of this file.

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