# fileio.cpp

Go to the documentation of this file.

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