# fileio.cpp

Go to the documentation of this file.

    00001 /* $Id: fileio.cpp 26114 2013-11-25 21:50:54Z 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 # define access _taccess
    00021 #elif defined(__HAIKU__)
    00022 #include <Path.h>
    00023 #include <storage/FindDirectory.h>
    00024 #else
    00025 #include <unistd.h>
    00026 #include <pwd.h>
    00027 #endif
    00028 #include <sys/stat.h>
    00029 #include <algorithm>
    00030
    00031 #ifdef WITH_XDG_BASEDIR
    00032 #include "basedir.h"
    00033 #endif
    00034
    00036 #define FIO_BUFFER_SIZE 512
    00037
    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
    00057 static bool _do_scan_working_directory = true;
    00058
    00059 extern char *_config_file;
    00060 extern char *_highscore_file;
    00061
    00066 size_t FioGetPos()
    00067 {
    00068   return _fio.pos + (_fio.buffer - _fio.buffer_end);
    00069 }
    00070
    00076 const char *FioGetFilename(uint8 slot)
    00077 {
    00078   return _fio.shortnames[slot];
    00079 }
    00080
    00086 void FioSeekTo(size_t pos, int mode)
    00087 {
    00088   if (mode == SEEK_CUR) pos += FioGetPos();
    00089   _fio.buffer = _fio.buffer_end = _fio.buffer_start + FIO_BUFFER_SIZE;
    00090   _fio.pos = pos;
    00091   if (fseek(_fio.cur_fh, _fio.pos, SEEK_SET) < 0) {
    00092     DEBUG(misc, 0, "Seeking in %s failed", _fio.filename);
    00093   }
    00094 }
    00095
    00096 #if defined(LIMITED_FDS)
    00097 static void FioRestoreFile(int slot)
    00098 {
    00099   /* Do we still have the file open, or should we reopen it? */
    00100   if (_fio.handles[slot] == NULL) {
    00101     DEBUG(misc, 6, "Restoring file '%s' in slot '%d' from disk", _fio.filenames[slot], slot);
    00102     FioOpenFile(slot, _fio.filenames[slot]);
    00103   }
    00104   _fio.usage_count[slot]++;
    00105 }
    00106 #endif /* LIMITED_FDS */
    00107
    00113 void FioSeekToFile(uint8 slot, size_t pos)
    00114 {
    00115   FILE *f;
    00116 #if defined(LIMITED_FDS)
    00117   /* Make sure we have this file open */
    00118   FioRestoreFile(slot);
    00119 #endif /* LIMITED_FDS */
    00120   f = _fio.handles[slot];
    00121   assert(f != NULL);
    00122   _fio.cur_fh = f;
    00123   _fio.filename = _fio.filenames[slot];
    00124   FioSeekTo(pos, SEEK_SET);
    00125 }
    00126
    00131 byte FioReadByte()
    00132 {
    00133   if (_fio.buffer == _fio.buffer_end) {
    00134     _fio.buffer = _fio.buffer_start;
    00135     size_t size = fread(_fio.buffer, 1, FIO_BUFFER_SIZE, _fio.cur_fh);
    00136     _fio.pos += size;
    00137     _fio.buffer_end = _fio.buffer_start + size;
    00138
    00139     if (size == 0) return 0;
    00140   }
    00141   return *_fio.buffer++;
    00142 }
    00143
    00148 void FioSkipBytes(int n)
    00149 {
    00150   for (;;) {
    00151     int m = min(_fio.buffer_end - _fio.buffer, n);
    00152     _fio.buffer += m;
    00153     n -= m;
    00154     if (n == 0) break;
    00155     FioReadByte();
    00156     n--;
    00157   }
    00158 }
    00159
    00164 uint16 FioReadWord()
    00165 {
    00166   byte b = FioReadByte();
    00167   return (FioReadByte() << 8) | b;
    00168 }
    00169
    00174 uint32 FioReadDword()
    00175 {
    00176   uint b = FioReadWord();
    00177   return (FioReadWord() << 16) | b;
    00178 }
    00179
    00185 void FioReadBlock(void *ptr, size_t size)
    00186 {
    00187   FioSeekTo(FioGetPos(), SEEK_SET);
    00188   _fio.pos += fread(ptr, 1, size, _fio.cur_fh);
    00189 }
    00190
    00195 static inline void FioCloseFile(int slot)
    00196 {
    00197   if (_fio.handles[slot] != NULL) {
    00198     fclose(_fio.handles[slot]);
    00199
    00200     free(_fio.shortnames[slot]);
    00201     _fio.shortnames[slot] = NULL;
    00202
    00203     _fio.handles[slot] = NULL;
    00204 #if defined(LIMITED_FDS)
    00205     _fio.open_handles--;
    00206 #endif /* LIMITED_FDS */
    00207   }
    00208 }
    00209
    00211 void FioCloseAll()
    00212 {
    00213   for (int i = 0; i != lengthof(_fio.handles); i++) {
    00214     FioCloseFile(i);
    00215   }
    00216 }
    00217
    00218 #if defined(LIMITED_FDS)
    00219 static void FioFreeHandle()
    00220 {
    00221   /* If we are about to open a file that will exceed the limit, close a file */
    00222   if (_fio.open_handles + 1 == LIMITED_FDS) {
    00223     uint i, count;
    00224     int slot;
    00225
    00226     count = UINT_MAX;
    00227     slot = -1;
    00228     /* Find the file that is used the least */
    00229     for (i = 0; i < lengthof(_fio.handles); i++) {
    00230       if (_fio.handles[i] != NULL && _fio.usage_count[i] < count) {
    00231         count = _fio.usage_count[i];
    00232         slot  = i;
    00233       }
    00234     }
    00235     assert(slot != -1);
    00236     DEBUG(misc, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio.filenames[slot], slot);
    00237     FioCloseFile(slot);
    00238   }
    00239 }
    00240 #endif /* LIMITED_FDS */
    00241
    00248 void FioOpenFile(int slot, const char *filename, Subdirectory subdir)
    00249 {
    00250   FILE *f;
    00251
    00252 #if defined(LIMITED_FDS)
    00253   FioFreeHandle();
    00254 #endif /* LIMITED_FDS */
    00255   f = FioFOpenFile(filename, "rb", subdir);
    00256   if (f == NULL) usererror("Cannot open file '%s'", filename);
    00257   long pos = ftell(f);
    00258   if (pos < 0) usererror("Cannot read file '%s'", filename);
    00259
    00260   FioCloseFile(slot); // if file was opened before, close it
    00261   _fio.handles[slot] = f;
    00262   _fio.filenames[slot] = filename;
    00263
    00264   /* Store the filename without path and extension */
    00265   const char *t = strrchr(filename, PATHSEPCHAR);
    00266   _fio.shortnames[slot] = strdup(t == NULL ? filename : t);
    00267   char *t2 = strrchr(_fio.shortnames[slot], '.');
    00268   if (t2 != NULL) *t2 = '\0';
    00269   strtolower(_fio.shortnames[slot]);
    00270
    00271 #if defined(LIMITED_FDS)
    00272   _fio.usage_count[slot] = 0;
    00273   _fio.open_handles++;
    00274 #endif /* LIMITED_FDS */
    00275   FioSeekToFile(slot, (uint32)pos);
    00276 }
    00277
    00278 static const char * const _subdirs[] = {
    00279   "",
    00280   "save" PATHSEP,
    00281   "save" PATHSEP "autosave" PATHSEP,
    00282   "scenario" PATHSEP,
    00283   "scenario" PATHSEP "heightmap" PATHSEP,
    00284   "gm" PATHSEP,
    00285   "data" PATHSEP,
    00286   "baseset" PATHSEP,
    00287   "newgrf" PATHSEP,
    00288   "lang" PATHSEP,
    00289   "ai" PATHSEP,
    00290   "ai" PATHSEP "library" PATHSEP,
    00291   "game" PATHSEP,
    00292   "game" PATHSEP "library" PATHSEP,
    00293   "screenshot" PATHSEP,
    00294 };
    00295 assert_compile(lengthof(_subdirs) == NUM_SUBDIRS);
    00296
    00297 const char *_searchpaths[NUM_SEARCHPATHS];
    00298 TarList _tar_list[NUM_SUBDIRS];
    00299 TarFileList _tar_filelist[NUM_SUBDIRS];
    00300
    00301 typedef std::map<std::string, std::string> TarLinkList;
    00302 static TarLinkList _tar_linklist[NUM_SUBDIRS];
    00303
    00310 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
    00311 {
    00312   FILE *f = FioFOpenFile(filename, "rb", subdir);
    00313   if (f == NULL) return false;
    00314
    00315   FioFCloseFile(f);
    00316   return true;
    00317 }
    00318
    00324 bool FileExists(const char *filename)
    00325 {
    00326 #if defined(WINCE)
    00327   /* There is always one platform that doesn't support basic commands... */
    00328   HANDLE hand = CreateFile(OTTD2FS(filename), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
    00329   if (hand == INVALID_HANDLE_VALUE) return 1;
    00330   CloseHandle(hand);
    00331   return 0;
    00332 #else
    00333   return access(OTTD2FS(filename), 0) == 0;
    00334 #endif
    00335 }
    00336
    00340 void FioFCloseFile(FILE *f)
    00341 {
    00342   fclose(f);
    00343 }
    00344
    00345 char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
    00346 {
    00347   assert(subdir < NUM_SUBDIRS);
    00348   assert(sp < NUM_SEARCHPATHS);
    00349
    00350   snprintf(buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
    00351   return buf;
    00352 }
    00353
    00362 char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename)
    00363 {
    00364   Searchpath sp;
    00365   assert(subdir < NUM_SUBDIRS);
    00366
    00367   FOR_ALL_SEARCHPATHS(sp) {
    00368     FioGetFullPath(buf, buflen, sp, subdir, filename);
    00369     if (FileExists(buf)) return buf;
    00370 #if !defined(WIN32)
    00371     /* Be, as opening files, aware that sometimes the filename
    00372      * might be in uppercase when it is in lowercase on the
    00373      * disk. Of course Windows doesn't care about casing. */
    00374     if (strtolower(buf + strlen(_searchpaths[sp]) - 1) && FileExists(buf)) return buf;
    00375 #endif
    00376   }
    00377
    00378   return NULL;
    00379 }
    00380
    00381 char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir)
    00382 {
    00383   assert(subdir < NUM_SUBDIRS);
    00384   assert(sp < NUM_SEARCHPATHS);
    00385
    00386   snprintf(buf, buflen, "%s%s", _searchpaths[sp], _subdirs[subdir]);
    00387   return buf;
    00388 }
    00389
    00390 char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir)
    00391 {
    00392   Searchpath sp;
    00393
    00394   /* Find and return the first valid directory */
    00395   FOR_ALL_SEARCHPATHS(sp) {
    00396     char *ret = FioAppendDirectory(buf, buflen, sp, subdir);
    00397     if (FileExists(buf)) return ret;
    00398   }
    00399
    00400   /* Could not find the directory, fall back to a base path */
    00401   ttd_strlcpy(buf, _personal_dir, buflen);
    00402
    00403   return buf;
    00404 }
    00405
    00406 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
    00407 {
    00408 #if defined(WIN32) && defined(UNICODE)
    00409   /* fopen is implemented as a define with ellipses for
    00410    * Unicode support (prepend an L). As we are not sending
    00411    * a string, but a variable, it 'renames' the variable,
    00412    * so make that variable to makes it compile happily */
    00413   wchar_t Lmode[5];
    00414   MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
    00415 #endif
    00416   FILE *f = NULL;
    00417   char buf[MAX_PATH];
    00418
    00419   if (subdir == NO_DIRECTORY) {
    00420     strecpy(buf, filename, lastof(buf));
    00421   } else {
    00422     snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
    00423   }
    00424
    00425 #if defined(WIN32)
    00426   if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL;
    00427 #endif
    00428
    00429   f = fopen(buf, mode);
    00430 #if !defined(WIN32)
    00431   if (f == NULL && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
    00432     f = fopen(buf, mode);
    00433   }
    00434 #endif
    00435   if (f != NULL && filesize != NULL) {
    00436     /* Find the size of the file */
    00437     fseek(f, 0, SEEK_END);
    00438     *filesize = ftell(f);
    00439     fseek(f, 0, SEEK_SET);
    00440   }
    00441   return f;
    00442 }
    00443
    00451 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
    00452 {
    00453   FILE *f = fopen(entry->tar_filename, "rb");
    00454   if (f == NULL) return f;
    00455
    00456   if (fseek(f, entry->position, SEEK_SET) < 0) {
    00457     fclose(f);
    00458     return NULL;
    00459   }
    00460
    00461   if (filesize != NULL) *filesize = entry->size;
    00462   return f;
    00463 }
    00464
    00472 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
    00473 {
    00474   FILE *f = NULL;
    00475   Searchpath sp;
    00476
    00477   assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
    00478
    00479   FOR_ALL_SEARCHPATHS(sp) {
    00480     f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
    00481     if (f != NULL || subdir == NO_DIRECTORY) break;
    00482   }
    00483
    00484   /* We can only use .tar in case of data-dir, and read-mode */
    00485   if (f == NULL && mode[0] == 'r' && subdir != NO_DIRECTORY) {
    00486     static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
    00487     char resolved_name[MAX_RESOLVED_LENGTH];
    00488
    00489     /* Filenames in tars are always forced to be lowercase */
    00490     strecpy(resolved_name, filename, lastof(resolved_name));
    00491     strtolower(resolved_name);
    00492
    00493     size_t resolved_len = strlen(resolved_name);
    00494
    00495     /* Resolve ONE directory link */
    00496     for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
    00497       const std::string &src = link->first;
    00498       size_t len = src.length();
    00499       if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) {
    00500         /* Apply link */
    00501         char resolved_name2[MAX_RESOLVED_LENGTH];
    00502         const std::string &dest = link->second;
    00503         strecpy(resolved_name2, &(resolved_name[len]), lastof(resolved_name2));
    00504         strecpy(resolved_name, dest.c_str(), lastof(resolved_name));
    00505         strecpy(&(resolved_name[dest.length()]), resolved_name2, lastof(resolved_name));
    00506         break; // Only resolve one level
    00507       }
    00508     }
    00509
    00510     TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
    00511     if (it != _tar_filelist[subdir].end()) {
    00512       f = FioFOpenFileTar(&((*it).second), filesize);
    00513     }
    00514   }
    00515
    00516   /* Sometimes a full path is given. To support
    00517    * the 'subdirectory' must be 'removed'. */
    00518   if (f == NULL && subdir != NO_DIRECTORY) {
    00519     switch (subdir) {
    00520       case BASESET_DIR:
    00521         f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
    00522         if (f != NULL) break;
    00523         /* FALL THROUGH */
    00524       case NEWGRF_DIR:
    00525         f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
    00526         break;
    00527
    00528       default:
    00529         f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
    00530         break;
    00531     }
    00532   }
    00533
    00534   return f;
    00535 }
    00536
    00541 static void FioCreateDirectory(const char *name)
    00542 {
    00543   /* Ignore directory creation errors; they'll surface later on, and most
    00544    * of the time they are 'directory already exists' errors anyhow. */
    00545 #if defined(WIN32) || defined(WINCE)
    00546   CreateDirectory(OTTD2FS(name), NULL);
    00547 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
    00548   mkdir(OTTD2FS(name));
    00549 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
    00550   char buf[MAX_PATH];
    00551   ttd_strlcpy(buf, name, MAX_PATH);
    00552
    00553   size_t len = strlen(name) - 1;
    00554   if (buf[len] == '/') {
    00555     buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail
    00556   }
    00557
    00558   mkdir(OTTD2FS(buf), 0755);
    00559 #else
    00560   mkdir(OTTD2FS(name), 0755);
    00561 #endif
    00562 }
    00563
    00571 bool AppendPathSeparator(char *buf, size_t buflen)
    00572 {
    00573   size_t s = strlen(buf);
    00574
    00575   /* Length of string + path separator + '\0' */
    00576   if (s != 0 && buf[s - 1] != PATHSEPCHAR) {
    00577     if (s + 2 >= buflen) return false;
    00578
    00579     buf[s]     = PATHSEPCHAR;
    00580     buf[s + 1] = '\0';
    00581   }
    00582
    00583   return true;
    00584 }
    00585
    00592 char *BuildWithFullPath(const char *dir)
    00593 {
    00594   char *dest = MallocT<char>(MAX_PATH);
    00595   ttd_strlcpy(dest, dir, MAX_PATH);
    00596
    00597   /* Check if absolute or relative path */
    00598   const char *s = strchr(dest, PATHSEPCHAR);
    00599
    00600   /* Add absolute path */
    00601   if (s == NULL || dest != s) {
    00602     if (getcwd(dest, MAX_PATH) == NULL) *dest = '\0';
    00603     AppendPathSeparator(dest, MAX_PATH);
    00604     ttd_strlcat(dest, dir, MAX_PATH);
    00605   }
    00606   AppendPathSeparator(dest, MAX_PATH);
    00607
    00608   return dest;
    00609 }
    00610
    00616 const char *FioTarFirstDir(const char *tarname, Subdirectory subdir)
    00617 {
    00618   TarList::iterator it = _tar_list[subdir].find(tarname);
    00619   if (it == _tar_list[subdir].end()) return NULL;
    00620   return (*it).second.dirname;
    00621 }
    00622
    00623 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
    00624 {
    00625   std::string src = srcParam;
    00626   std::string dest = destParam;
    00627   /* Tar internals assume lowercase */
    00628   std::transform(src.begin(), src.end(), src.begin(), tolower);
    00629   std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
    00630
    00631   TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
    00632   if (dest_file != _tar_filelist[subdir].end()) {
    00633     /* Link to file. Process the link like the destination file. */
    00634     _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
    00635   } else {
    00636     /* Destination file not found. Assume 'link to directory'
    00637      * Append PATHSEPCHAR to 'src' and 'dest' if needed */
    00638     const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
    00639     const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
    00640     _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
    00641   }
    00642 }
    00643
    00644 void FioTarAddLink(const char *src, const char *dest, Subdirectory subdir)
    00645 {
    00646   TarAddLink(src, dest, subdir);
    00647 }
    00648
    00654 static void SimplifyFileName(char *name)
    00655 {
    00656   /* Force lowercase */
    00657   strtolower(name);
    00658
    00659   /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
    00660 #if (PATHSEPCHAR != '/')
    00661   for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
    00662 #endif
    00663 }
    00664
    00670 uint TarScanner::DoScan(Subdirectory sd)
    00671 {
    00672   _tar_filelist[sd].clear();
    00673   _tar_list[sd].clear();
    00674   uint num = this->Scan(".tar", sd, false);
    00675   if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
    00676   return num;
    00677 }
    00678
    00679 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
    00680 {
    00681   DEBUG(misc, 1, "Scanning for tars");
    00682   TarScanner fs;
    00683   uint num = 0;
    00684   if (mode & TarScanner::BASESET) {
    00685     num += fs.DoScan(BASESET_DIR);
    00686   }
    00687   if (mode & TarScanner::NEWGRF) {
    00688     num += fs.DoScan(NEWGRF_DIR);
    00689   }
    00690   if (mode & TarScanner::AI) {
    00691     num += fs.DoScan(AI_DIR);
    00692     num += fs.DoScan(AI_LIBRARY_DIR);
    00693   }
    00694   if (mode & TarScanner::GAME) {
    00695     num += fs.DoScan(GAME_DIR);
    00696     num += fs.DoScan(GAME_LIBRARY_DIR);
    00697   }
    00698   if (mode & TarScanner::SCENARIO) {
    00699     num += fs.DoScan(SCENARIO_DIR);
    00700     num += fs.DoScan(HEIGHTMAP_DIR);
    00701   }
    00702   DEBUG(misc, 1, "Scan complete, found %d files", num);
    00703   return num;
    00704 }
    00705
    00712 bool TarScanner::AddFile(Subdirectory sd, const char *filename)
    00713 {
    00714   this->subdir = sd;
    00715   return this->AddFile(filename, 0);
    00716 }
    00717
    00718 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
    00719 {
    00720   /* No tar within tar. */
    00721   assert(tar_filename == NULL);
    00722
    00723   /* The TAR-header, repeated for every file */
    00724   struct TarHeader {
    00725     char name[100];
    00726     char mode[8];
    00727     char uid[8];
    00728     char gid[8];
    00729     char size[12];
    00730     char mtime[12];
    00731     char chksum[8];
    00732     char typeflag;
    00733     char linkname[100];
    00734     char magic[6];
    00735     char version[2];
    00736     char uname[32];
    00737     char gname[32];
    00738     char devmajor[8];
    00739     char devminor[8];
    00740     char prefix[155];
    00741
    00742     char unused[12];
    00743   };
    00744
    00745   /* Check if we already seen this file */
    00746   TarList::iterator it = _tar_list[this->subdir].find(filename);
    00747   if (it != _tar_list[this->subdir].end()) return false;
    00748
    00749   FILE *f = fopen(filename, "rb");
    00750   /* Although the file has been found there can be
    00751    * a number of reasons we cannot open the file.
    00752    * Most common case is when we simply have not
    00753    * been given read access. */
    00754   if (f == NULL) return false;
    00755
    00756   const char *dupped_filename = strdup(filename);
    00757   _tar_list[this->subdir][filename].filename = dupped_filename;
    00758   _tar_list[this->subdir][filename].dirname = NULL;
    00759
    00760   TarLinkList links;
    00761
    00762   TarHeader th;
    00763   char buf[sizeof(th.name) + 1], *end;
    00764   char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
    00765   char link[sizeof(th.linkname) + 1];
    00766   char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
    00767   size_t num = 0, pos = 0;
    00768
    00769   /* Make a char of 512 empty bytes */
    00770   char empty[512];
    00771   memset(&empty[0], 0, sizeof(empty));
    00772
    00773   for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
    00774     size_t num_bytes_read = fread(&th, 1, 512, f);
    00775     if (num_bytes_read != 512) break;
    00776     pos += num_bytes_read;
    00777
    00778     /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
    00779     if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
    00780       /* If we have only zeros in the block, it can be an end-of-file indicator */
    00781       if (memcmp(&th, &empty[0], 512) == 0) continue;
    00782
    00783       DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
    00784       fclose(f);
    00785       return false;
    00786     }
    00787
    00788     name[0] = '\0';
    00789
    00790     /* The prefix contains the directory-name */
    00791     if (th.prefix[0] != '\0') {
    00792       ttd_strlcpy(name, th.prefix, lengthof(name));
    00793       ttd_strlcat(name, PATHSEP, lengthof(name));
    00794     }
    00795
    00796     /* Copy the name of the file in a safe way at the end of 'name' */
    00797     ttd_strlcat(name, th.name, lengthof(name));
    00798
    00799     /* Calculate the size of the file.. for some strange reason this is stored as a string */
    00800     ttd_strlcpy(buf, th.size, lengthof(buf));
    00801     size_t skip = strtoul(buf, &end, 8);
    00802
    00803     switch (th.typeflag) {
    00804       case '\0':
    00805       case '0': { // regular file
    00806         /* Ignore empty files */
    00807         if (skip == 0) break;
    00808
    00809         if (strlen(name) == 0) break;
    00810
    00811         /* Store this entry in the list */
    00812         TarFileListEntry entry;
    00813         entry.tar_filename = dupped_filename;
    00814         entry.size         = skip;
    00815         entry.position     = pos;
    00816
    00817         /* Convert to lowercase and our PATHSEPCHAR */
    00818         SimplifyFileName(name);
    00819
    00820         DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
    00821         if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
    00822
    00823         break;
    00824       }
    00825
    00826       case '1': // hard links
    00827       case '2': { // symbolic links
    00828         /* Copy the destination of the link in a safe way at the end of 'linkname' */
    00829         ttd_strlcpy(link, th.linkname, lengthof(link));
    00830
    00831         if (strlen(name) == 0 || strlen(link) == 0) break;
    00832
    00833         /* Convert to lowercase and our PATHSEPCHAR */
    00834         SimplifyFileName(name);
    00835         SimplifyFileName(link);
    00836
    00837         /* Only allow relative links */
    00838         if (link[0] == PATHSEPCHAR) {
    00839           DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
    00840           break;
    00841         }
    00842
    00843         /* Process relative path.
    00844          * Note: The destination of links must not contain any directory-links. */
    00845         ttd_strlcpy(dest, name, lengthof(dest));
    00846         char *destpos = strrchr(dest, PATHSEPCHAR);
    00847         if (destpos == NULL) destpos = dest;
    00848         *destpos = '\0';
    00849
    00850         char *pos = link;
    00851         while (*pos != '\0') {
    00852           char *next = strchr(link, PATHSEPCHAR);
    00853           if (next == NULL) next = pos + strlen(pos);
    00854
    00855           /* Skip '.' (current dir) */
    00856           if (next != pos + 1 || pos[0] != '.') {
    00857             if (next == pos + 2 && pos[0] == '.' && pos[1] == '.') {
    00858               /* level up */
    00859               if (dest[0] == '\0') {
    00860                 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
    00861                 break;
    00862               }
    00863
    00864               /* Truncate 'dest' after last PATHSEPCHAR.
    00865                * This assumes that the truncated part is a real directory and not a link. */
    00866               destpos = strrchr(dest, PATHSEPCHAR);
    00867               if (destpos == NULL) destpos = dest;
    00868             } else {
    00869               /* Append at end of 'dest' */
    00870               if (destpos != dest) *(destpos++) = PATHSEPCHAR;
    00871               strncpy(destpos, pos, next - pos); // Safe as we do '\0'-termination ourselves
    00872               destpos += next - pos;
    00873             }
    00874             *destpos = '\0';
    00875           }
    00876
    00877           pos = next;
    00878         }
    00879
    00880         /* Store links in temporary list */
    00881         DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
    00882         links.insert(TarLinkList::value_type(name, dest));
    00883
    00884         break;
    00885       }
    00886
    00887       case '5': // directory
    00888         /* Convert to lowercase and our PATHSEPCHAR */
    00889         SimplifyFileName(name);
    00890
    00891         /* Store the first directory name we detect */
    00892         DEBUG(misc, 6, "Found dir in tar: %s", name);
    00893         if (_tar_list[this->subdir][filename].dirname == NULL) _tar_list[this->subdir][filename].dirname = strdup(name);
    00894         break;
    00895
    00896       default:
    00897         /* Ignore other types */
    00898         break;
    00899     }
    00900
    00901     /* Skip to the next block.. */
    00902     skip = Align(skip, 512);
    00903     if (fseek(f, skip, SEEK_CUR) < 0) {
    00904       DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
    00905       fclose(f);
    00906       return false;
    00907     }
    00908     pos += skip;
    00909   }
    00910
    00911   DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
    00912   fclose(f);
    00913
    00914   /* Resolve file links and store directory links.
    00915    * We restrict usage of links to two cases:
    00916    *  1) Links to directories:
    00917    *      Both the source path and the destination path must NOT contain any further links.
    00918    *      When resolving files at most one directory link is resolved.
    00919    *  2) Links to files:
    00920    *      The destination path must NOT contain any links.
    00921    *      The source path may contain one directory link.
    00922    */
    00923   for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
    00924     const std::string &src = link->first;
    00925     const std::string &dest = link->second;
    00926     TarAddLink(src, dest, this->subdir);
    00927   }
    00928
    00929   return true;
    00930 }
    00931
    00939 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
    00940 {
    00941   TarList::iterator it = _tar_list[subdir].find(tar_filename);
    00942   /* We don't know the file. */
    00943   if (it == _tar_list[subdir].end()) return false;
    00944
    00945   const char *dirname = (*it).second.dirname;
    00946
    00947   /* The file doesn't have a sub directory! */
    00948   if (dirname == NULL) return false;
    00949
    00950   char filename[MAX_PATH];
    00951   strecpy(filename, tar_filename, lastof(filename));
    00952   char *p = strrchr(filename, PATHSEPCHAR);
    00953   /* The file's path does not have a separator? */
    00954   if (p == NULL) return false;
    00955
    00956   p++;
    00957   strecpy(p, dirname, lastof(filename));
    00958   DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
    00959   FioCreateDirectory(filename);
    00960
    00961   for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
    00962     if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
    00963
    00964     strecpy(p, (*it2).first.c_str(), lastof(filename));
    00965
    00966     DEBUG(misc, 9, "  extracting %s", filename);
    00967
    00968     /* First open the file in the .tar. */
    00969     size_t to_copy = 0;
    00970     FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
    00971     if (in == NULL) {
    00972       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
    00973       return false;
    00974     }
    00975
    00976     /* Now open the 'output' file. */
    00977     FILE *out = fopen(filename, "wb");
    00978     if (out == NULL) {
    00979       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
    00980       fclose(in);
    00981       return false;
    00982     }
    00983
    00984     /* Now read from the tar and write it into the file. */
    00985     char buffer[4096];
    00986     size_t read;
    00987     for (; to_copy != 0; to_copy -= read) {
    00988       read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
    00989       if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
    00990     }
    00991
    00992     /* Close everything up. */
    00993     fclose(in);
    00994     fclose(out);
    00995
    00996     if (to_copy != 0) {
    00997       DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
    00998       return false;
    00999     }
    01000   }
    01001
    01002   DEBUG(misc, 9, "  extraction successful");
    01003   return true;
    01004 }
    01005
    01006 #if defined(WIN32) || defined(WINCE)
    01007
    01012 extern void DetermineBasePaths(const char *exe);
    01013 #else /* defined(WIN32) || defined(WINCE) */
    01014
    01022 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
    01023 {
    01024   bool success = false;
    01025 #ifdef WITH_COCOA
    01026   char *app_bundle = strchr(exe, '.');
    01027   while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
    01028
    01029   if (app_bundle != NULL) app_bundle[0] = '\0';
    01030 #endif /* WITH_COCOA */
    01031   char *s = const_cast<char *>(strrchr(exe, PATHSEPCHAR));
    01032   if (s != NULL) {
    01033     *s = '\0';
    01034 #if defined(__DJGPP__)
    01035     /* If we want to go to the root, we can't use cd C:, but we must use '/' */
    01036     if (s[-1] == ':') chdir("/");
    01037 #endif
    01038     if (chdir(exe) != 0) {
    01039       DEBUG(misc, 0, "Directory with the binary does not exist?");
    01040     } else {
    01041       success = true;
    01042     }
    01043     *s = PATHSEPCHAR;
    01044   }
    01045 #ifdef WITH_COCOA
    01046   if (app_bundle != NULL) app_bundle[0] = '.';
    01047 #endif /* WITH_COCOA */
    01048   return success;
    01049 }
    01050
    01061 bool DoScanWorkingDirectory()
    01062 {
    01063   /* No working directory, so nothing to do. */
    01064   if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
    01065
    01066   /* Working directory is root, so do nothing. */
    01067   if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
    01068
    01069   /* No personal/home directory, so the working directory won't be that. */
    01070   if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
    01071
    01072   char tmp[MAX_PATH];
    01073   snprintf(tmp, lengthof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
    01074   AppendPathSeparator(tmp, MAX_PATH);
    01075   return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
    01076 }
    01077
    01082 void DetermineBasePaths(const char *exe)
    01083 {
    01084   char tmp[MAX_PATH];
    01085 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
    01086   const char *xdg_data_home = xdgDataHome(NULL);
    01087   snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", xdg_data_home,
    01088       PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
    01089   free(xdg_data_home);
    01090
    01091   AppendPathSeparator(tmp, MAX_PATH);
    01092   _searchpaths[SP_PERSONAL_DIR_XDG] = strdup(tmp);
    01093 #endif
    01094 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
    01095   _searchpaths[SP_PERSONAL_DIR] = NULL;
    01096 #else
    01097 #ifdef __HAIKU__
    01098   BPath path;
    01099   find_directory(B_USER_SETTINGS_DIRECTORY, &path);
    01100   const char *homedir = strdup(path.Path());
    01101 #else
    01102   /* getenv is highly unsafe; duplicate it as soon as possible,
    01103    * or at least before something else touches the environment
    01104    * variables in any way. It can also contain all kinds of
    01105    * unvalidated data we rather not want internally. */
    01106   const char *homedir = getenv("HOME");
    01107   if (homedir != NULL) {
    01108     homedir = strndup(homedir, MAX_PATH);
    01109   }
    01110
    01111   if (homedir == NULL) {
    01112     const struct passwd *pw = getpwuid(getuid());
    01113     homedir = (pw == NULL) ? NULL : strdup(pw->pw_dir);
    01114   }
    01115 #endif
    01116
    01117   if (homedir != NULL) {
    01118     ValidateString(homedir);
    01119     snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
    01120     AppendPathSeparator(tmp, MAX_PATH);
    01121
    01122     _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
    01123     free(homedir);
    01124   } else {
    01125     _searchpaths[SP_PERSONAL_DIR] = NULL;
    01126   }
    01127 #endif
    01128
    01129 #if defined(WITH_SHARED_DIR)
    01130   snprintf(tmp, MAX_PATH, "%s", SHARED_DIR);
    01131   AppendPathSeparator(tmp, MAX_PATH);
    01132   _searchpaths[SP_SHARED_DIR] = strdup(tmp);
    01133 #else
    01134   _searchpaths[SP_SHARED_DIR] = NULL;
    01135 #endif
    01136
    01137 #if defined(__MORPHOS__) || defined(__AMIGA__)
    01138   _searchpaths[SP_WORKING_DIR] = NULL;
    01139 #else
    01140   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
    01141   AppendPathSeparator(tmp, MAX_PATH);
    01142   _searchpaths[SP_WORKING_DIR] = strdup(tmp);
    01143 #endif
    01144
    01145   _do_scan_working_directory = DoScanWorkingDirectory();
    01146
    01147   /* Change the working directory to that one of the executable */
    01148   if (ChangeWorkingDirectoryToExecutable(exe)) {
    01149     if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
    01150     AppendPathSeparator(tmp, MAX_PATH);
    01151     _searchpaths[SP_BINARY_DIR] = strdup(tmp);
    01152   } else {
    01153     _searchpaths[SP_BINARY_DIR] = NULL;
    01154   }
    01155
    01156   if (_searchpaths[SP_WORKING_DIR] != NULL) {
    01157     /* Go back to the current working directory. */
    01158     if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
    01159       DEBUG(misc, 0, "Failed to return to working directory!");
    01160     }
    01161   }
    01162
    01163 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
    01164   _searchpaths[SP_INSTALLATION_DIR] = NULL;
    01165 #else
    01166   snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
    01167   AppendPathSeparator(tmp, MAX_PATH);
    01168   _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
    01169 #endif
    01170 #ifdef WITH_COCOA
    01171 extern void cocoaSetApplicationBundleDir();
    01172   cocoaSetApplicationBundleDir();
    01173 #else
    01174   _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
    01175 #endif
    01176 }
    01177 #endif /* defined(WIN32) || defined(WINCE) */
    01178
    01179 const char *_personal_dir;
    01180
    01187 void DeterminePaths(const char *exe)
    01188 {
    01189   DetermineBasePaths(exe);
    01190
    01191 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
    01192   char config_home[MAX_PATH];
    01193
    01194   const char *xdg_config_home = xdgConfigHome(NULL);
    01195   snprintf(config_home, MAX_PATH, "%s" PATHSEP "%s", xdg_config_home,
    01196       PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
    01197   free(xdg_config_home);
    01198
    01199   AppendPathSeparator(config_home, MAX_PATH);
    01200 #endif
    01201
    01202   Searchpath sp;
    01203   FOR_ALL_SEARCHPATHS(sp) {
    01204     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
    01205     DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
    01206   }
    01207
    01208   char *config_dir;
    01209   if (_config_file != NULL) {
    01210     config_dir = strdup(_config_file);
    01211     char *end = strrchr(config_dir, PATHSEPCHAR);
    01212     if (end == NULL) {
    01213       config_dir[0] = '\0';
    01214     } else {
    01215       end[1] = '\0';
    01216     }
    01217   } else {
    01218     char personal_dir[MAX_PATH];
    01219     if (FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg") != NULL) {
    01220       char *end = strrchr(personal_dir, PATHSEPCHAR);
    01221       if (end != NULL) end[1] = '\0';
    01222       config_dir = strdup(personal_dir);
    01223       _config_file = str_fmt("%sopenttd.cfg", config_dir);
    01224     } else {
    01225 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
    01226       /* No previous configuration file found. Use the configuration folder from XDG. */
    01227       config_dir = config_home;
    01228 #else
    01229       static const Searchpath new_openttd_cfg_order[] = {
    01230           SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
    01231         };
    01232
    01233       config_dir = NULL;
    01234       for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
    01235         if (IsValidSearchPath(new_openttd_cfg_order[i])) {
    01236           config_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
    01237           break;
    01238         }
    01239       }
    01240       assert(config_dir != NULL);
    01241 #endif
    01242       _config_file = str_fmt("%sopenttd.cfg", config_dir);
    01243     }
    01244   }
    01245
    01246   DEBUG(misc, 3, "%s found as config directory", config_dir);
    01247
    01248   _highscore_file = str_fmt("%shs.dat", config_dir);
    01249   extern char *_hotkeys_file;
    01250   _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
    01251   extern char *_windows_file;
    01252   _windows_file = str_fmt("%swindows.cfg", config_dir);
    01253
    01254 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
    01255   if (config_dir == config_home) {
    01256     /* We are using the XDG configuration home for the config file,
    01257      * then store the rest in the XDG data home folder. */
    01258     _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
    01259     FioCreateDirectory(_personal_dir);
    01260   } else
    01261 #endif
    01262   {
    01263     _personal_dir = config_dir;
    01264   }
    01265
    01266   /* Make the necessary folders */
    01267 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
    01268   FioCreateDirectory(config_dir);
    01269   if (config_dir != _personal_dir) FioCreateDirectory(_personal_dir);
    01270 #endif
    01271
    01272   DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
    01273
    01274   static const Subdirectory default_subdirs[] = {
    01275     SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
    01276   };
    01277
    01278   for (uint i = 0; i < lengthof(default_subdirs); i++) {
    01279     char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
    01280     FioCreateDirectory(dir);
    01281     free(dir);
    01282   }
    01283
    01284   /* If we have network we make a directory for the autodownloading of content */
    01285   _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
    01286 #ifdef ENABLE_NETWORK
    01287   FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
    01288
    01289   /* Create the directory for each of the types of content */
    01290   const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
    01291   for (uint i = 0; i < lengthof(dirs); i++) {
    01292     char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
    01293     FioCreateDirectory(tmp);
    01294     free(tmp);
    01295   }
    01296
    01297   extern char *_log_file;
    01298   _log_file = str_fmt("%sopenttd.log",  _personal_dir);
    01299 #else /* ENABLE_NETWORK */
    01300   /* If we don't have networking, we don't need to make the directory. But
    01301    * if it exists we keep it, otherwise remove it from the search paths. */
    01302   if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR]))  {
    01303     free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
    01304     _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
    01305   }
    01306 #endif /* ENABLE_NETWORK */
    01307 }
    01308
    01313 void SanitizeFilename(char *filename)
    01314 {
    01315   for (; *filename != '\0'; filename++) {
    01316     switch (*filename) {
    01317       /* The following characters are not allowed in filenames
    01318        * on at least one of the supported operating systems: */
    01319       case ':': case '\\': case '*': case '?': case '/':
    01320       case '<': case '>': case '|': case '"':
    01321         *filename = '_';
    01322         break;
    01323     }
    01324   }
    01325 }
    01326
    01335 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
    01336 {
    01337   FILE *in = fopen(filename, "rb");
    01338   if (in == NULL) return NULL;
    01339
    01340   fseek(in, 0, SEEK_END);
    01341   size_t len = ftell(in);
    01342   fseek(in, 0, SEEK_SET);
    01343   if (len > maxsize) {
    01344     fclose(in);
    01345     return NULL;
    01346   }
    01347   byte *mem = MallocT<byte>(len + 1);
    01348   mem[len] = 0;
    01349   if (fread(mem, len, 1, in) != 1) {
    01350     fclose(in);
    01351     free(mem);
    01352     return NULL;
    01353   }
    01354   fclose(in);
    01355
    01356   *lenp = len;
    01357   return mem;
    01358 }
    01359
    01366 static bool MatchesExtension(const char *extension, const char *filename)
    01367 {
    01368   if (extension == NULL) return true;
    01369
    01370   const char *ext = strrchr(filename, extension[0]);
    01371   return ext != NULL && strcasecmp(ext, extension) == 0;
    01372 }
    01373
    01383 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
    01384 {
    01385   extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
    01386
    01387   uint num = 0;
    01388   struct stat sb;
    01389   struct dirent *dirent;
    01390   DIR *dir;
    01391
    01392   if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
    01393
    01394   while ((dirent = readdir(dir)) != NULL) {
    01395     const char *d_name = FS2OTTD(dirent->d_name);
    01396     char filename[MAX_PATH];
    01397
    01398     if (!FiosIsValidFile(path, dirent, &sb)) continue;
    01399
    01400     snprintf(filename, lengthof(filename), "%s%s", path, d_name);
    01401
    01402     if (S_ISDIR(sb.st_mode)) {
    01403       /* Directory */
    01404       if (!recursive) continue;
    01405       if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
    01406       if (!AppendPathSeparator(filename, lengthof(filename))) continue;
    01407       num += ScanPath(fs, extension, filename, basepath_length, recursive);
    01408     } else if (S_ISREG(sb.st_mode)) {
    01409       /* File */
    01410       if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, NULL)) num++;
    01411     }
    01412   }
    01413
    01414   closedir(dir);
    01415
    01416   return num;
    01417 }
    01418
    01425 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
    01426 {
    01427   uint num = 0;
    01428   const char *filename = (*tar).first.c_str();
    01429
    01430   if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
    01431
    01432   return num;
    01433 }
    01434
    01444 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
    01445 {
    01446   this->subdir = sd;
    01447
    01448   Searchpath sp;
    01449   char path[MAX_PATH];
    01450   TarFileList::iterator tar;
    01451   uint num = 0;
    01452
    01453   FOR_ALL_SEARCHPATHS(sp) {
    01454     /* Don't search in the working directory */
    01455     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
    01456
    01457     FioAppendDirectory(path, MAX_PATH, sp, sd);
    01458     num += ScanPath(this, extension, path, strlen(path), recursive);
    01459   }
    01460
    01461   if (tars && sd != NO_DIRECTORY) {
    01462     FOR_ALL_TARS(tar, sd) {
    01463       num += ScanTar(this, extension, tar);
    01464     }
    01465   }
    01466
    01467   switch (sd) {
    01468     case BASESET_DIR:
    01469       num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
    01470       /* FALL THROUGH */
    01471     case NEWGRF_DIR:
    01472       num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
    01473       break;
    01474
    01475     default: break;
    01476   }
    01477
    01478   return num;
    01479 }
    01480
    01489 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
    01490 {
    01491   char path[MAX_PATH];
    01492   strecpy(path, directory, lastof(path));
    01493   if (!AppendPathSeparator(path, lengthof(path))) return 0;
    01494   return ScanPath(this, extension, path, strlen(path), recursive);
    01495 }