fileio.cpp

Go to the documentation of this file.
00001 /* $Id: fileio.cpp 23612 2011-12-19 20:56:59Z truebrain $ */
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 
00032 #define FIO_BUFFER_SIZE 512
00033 
00035 struct Fio {
00036   byte *buffer, *buffer_end;             
00037   size_t pos;                            
00038   FILE *cur_fh;                          
00039   const char *filename;                  
00040   FILE *handles[MAX_FILE_SLOTS];         
00041   byte buffer_start[FIO_BUFFER_SIZE];    
00042   const char *filenames[MAX_FILE_SLOTS]; 
00043   char *shortnames[MAX_FILE_SLOTS];      
00044 #if defined(LIMITED_FDS)
00045   uint open_handles;                     
00046   uint usage_count[MAX_FILE_SLOTS];      
00047 #endif /* LIMITED_FDS */
00048 };
00049 
00050 static Fio _fio; 
00051 
00053 static bool _do_scan_working_directory = true;
00054 
00055 extern char *_config_file;
00056 extern char *_highscore_file;
00057 
00062 size_t FioGetPos()
00063 {
00064   return _fio.pos + (_fio.buffer - _fio.buffer_end);
00065 }
00066 
00072 const char *FioGetFilename(uint8 slot)
00073 {
00074   return _fio.shortnames[slot];
00075 }
00076 
00082 void FioSeekTo(size_t pos, int mode)
00083 {
00084   if (mode == SEEK_CUR) pos += FioGetPos();
00085   _fio.buffer = _fio.buffer_end = _fio.buffer_start + FIO_BUFFER_SIZE;
00086   _fio.pos = pos;
00087   fseek(_fio.cur_fh, _fio.pos, SEEK_SET);
00088 }
00089 
00090 #if defined(LIMITED_FDS)
00091 static void FioRestoreFile(int slot)
00092 {
00093   /* Do we still have the file open, or should we reopen it? */
00094   if (_fio.handles[slot] == NULL) {
00095     DEBUG(misc, 6, "Restoring file '%s' in slot '%d' from disk", _fio.filenames[slot], slot);
00096     FioOpenFile(slot, _fio.filenames[slot]);
00097   }
00098   _fio.usage_count[slot]++;
00099 }
00100 #endif /* LIMITED_FDS */
00101 
00107 void FioSeekToFile(uint8 slot, size_t pos)
00108 {
00109   FILE *f;
00110 #if defined(LIMITED_FDS)
00111   /* Make sure we have this file open */
00112   FioRestoreFile(slot);
00113 #endif /* LIMITED_FDS */
00114   f = _fio.handles[slot];
00115   assert(f != NULL);
00116   _fio.cur_fh = f;
00117   _fio.filename = _fio.filenames[slot];
00118   FioSeekTo(pos, SEEK_SET);
00119 }
00120 
00125 byte FioReadByte()
00126 {
00127   if (_fio.buffer == _fio.buffer_end) {
00128     _fio.buffer = _fio.buffer_start;
00129     size_t size = fread(_fio.buffer, 1, FIO_BUFFER_SIZE, _fio.cur_fh);
00130     _fio.pos += size;
00131     _fio.buffer_end = _fio.buffer_start + size;
00132 
00133     if (size == 0) return 0;
00134   }
00135   return *_fio.buffer++;
00136 }
00137 
00142 void FioSkipBytes(int n)
00143 {
00144   for (;;) {
00145     int m = min(_fio.buffer_end - _fio.buffer, n);
00146     _fio.buffer += m;
00147     n -= m;
00148     if (n == 0) break;
00149     FioReadByte();
00150     n--;
00151   }
00152 }
00153 
00158 uint16 FioReadWord()
00159 {
00160   byte b = FioReadByte();
00161   return (FioReadByte() << 8) | b;
00162 }
00163 
00168 uint32 FioReadDword()
00169 {
00170   uint b = FioReadWord();
00171   return (FioReadWord() << 16) | b;
00172 }
00173 
00179 void FioReadBlock(void *ptr, size_t size)
00180 {
00181   FioSeekTo(FioGetPos(), SEEK_SET);
00182   _fio.pos += fread(ptr, 1, size, _fio.cur_fh);
00183 }
00184 
00189 static inline void FioCloseFile(int slot)
00190 {
00191   if (_fio.handles[slot] != NULL) {
00192     fclose(_fio.handles[slot]);
00193 
00194     free(_fio.shortnames[slot]);
00195     _fio.shortnames[slot] = NULL;
00196 
00197     _fio.handles[slot] = NULL;
00198 #if defined(LIMITED_FDS)
00199     _fio.open_handles--;
00200 #endif /* LIMITED_FDS */
00201   }
00202 }
00203 
00205 void FioCloseAll()
00206 {
00207   for (int i = 0; i != lengthof(_fio.handles); i++) {
00208     FioCloseFile(i);
00209   }
00210 }
00211 
00212 #if defined(LIMITED_FDS)
00213 static void FioFreeHandle()
00214 {
00215   /* If we are about to open a file that will exceed the limit, close a file */
00216   if (_fio.open_handles + 1 == LIMITED_FDS) {
00217     uint i, count;
00218     int slot;
00219 
00220     count = UINT_MAX;
00221     slot = -1;
00222     /* Find the file that is used the least */
00223     for (i = 0; i < lengthof(_fio.handles); i++) {
00224       if (_fio.handles[i] != NULL && _fio.usage_count[i] < count) {
00225         count = _fio.usage_count[i];
00226         slot  = i;
00227       }
00228     }
00229     assert(slot != -1);
00230     DEBUG(misc, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio.filenames[slot], slot);
00231     FioCloseFile(slot);
00232   }
00233 }
00234 #endif /* LIMITED_FDS */
00235 
00242 void FioOpenFile(int slot, const char *filename, Subdirectory subdir)
00243 {
00244   FILE *f;
00245 
00246 #if defined(LIMITED_FDS)
00247   FioFreeHandle();
00248 #endif /* LIMITED_FDS */
00249   f = FioFOpenFile(filename, "rb", subdir);
00250   if (f == NULL) usererror("Cannot open file '%s'", filename);
00251   uint32 pos = ftell(f);
00252 
00253   FioCloseFile(slot); // if file was opened before, close it
00254   _fio.handles[slot] = f;
00255   _fio.filenames[slot] = filename;
00256 
00257   /* Store the filename without path and extension */
00258   const char *t = strrchr(filename, PATHSEPCHAR);
00259   _fio.shortnames[slot] = strdup(t == NULL ? filename : t);
00260   char *t2 = strrchr(_fio.shortnames[slot], '.');
00261   if (t2 != NULL) *t2 = '\0';
00262   strtolower(_fio.shortnames[slot]);
00263 
00264 #if defined(LIMITED_FDS)
00265   _fio.usage_count[slot] = 0;
00266   _fio.open_handles++;
00267 #endif /* LIMITED_FDS */
00268   FioSeekToFile(slot, pos);
00269 }
00270 
00271 static const char * const _subdirs[] = {
00272   "",
00273   "save" PATHSEP,
00274   "save" PATHSEP "autosave" PATHSEP,
00275   "scenario" PATHSEP,
00276   "scenario" PATHSEP "heightmap" PATHSEP,
00277   "gm" PATHSEP,
00278   "data" PATHSEP,
00279   "baseset" PATHSEP,
00280   "newgrf" PATHSEP,
00281   "lang" PATHSEP,
00282   "ai" PATHSEP,
00283   "ai" PATHSEP "library" PATHSEP,
00284   "game" PATHSEP,
00285   "game" PATHSEP "library" PATHSEP,
00286 };
00287 assert_compile(lengthof(_subdirs) == NUM_SUBDIRS);
00288 
00289 const char *_searchpaths[NUM_SEARCHPATHS];
00290 TarList _tar_list[NUM_SUBDIRS];
00291 TarFileList _tar_filelist[NUM_SUBDIRS];
00292 
00293 typedef std::map<std::string, std::string> TarLinkList;
00294 static TarLinkList _tar_linklist[NUM_SUBDIRS]; 
00295 
00302 bool FioCheckFileExists(const char *filename, Subdirectory subdir)
00303 {
00304   FILE *f = FioFOpenFile(filename, "rb", subdir);
00305   if (f == NULL) return false;
00306 
00307   FioFCloseFile(f);
00308   return true;
00309 }
00310 
00316 bool FileExists(const char *filename)
00317 {
00318 #if defined(WINCE)
00319   /* There is always one platform that doesn't support basic commands... */
00320   HANDLE hand = CreateFile(OTTD2FS(filename), 0, 0, NULL, OPEN_EXISTING, 0, NULL);
00321   if (hand == INVALID_HANDLE_VALUE) return 1;
00322   CloseHandle(hand);
00323   return 0;
00324 #else
00325   return access(OTTD2FS(filename), 0) == 0;
00326 #endif
00327 }
00328 
00332 void FioFCloseFile(FILE *f)
00333 {
00334   fclose(f);
00335 }
00336 
00337 char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename)
00338 {
00339   assert(subdir < NUM_SUBDIRS);
00340   assert(sp < NUM_SEARCHPATHS);
00341 
00342   snprintf(buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
00343   return buf;
00344 }
00345 
00354 char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename)
00355 {
00356   Searchpath sp;
00357   assert(subdir < NUM_SUBDIRS);
00358 
00359   FOR_ALL_SEARCHPATHS(sp) {
00360     FioGetFullPath(buf, buflen, sp, subdir, filename);
00361     if (FileExists(buf)) return buf;
00362 #if !defined(WIN32)
00363     /* Be, as opening files, aware that sometimes the filename
00364      * might be in uppercase when it is in lowercase on the
00365      * disk. Of course Windows doesn't care about casing. */
00366     if (strtolower(buf + strlen(_searchpaths[sp]) - 1) && FileExists(buf)) return buf;
00367 #endif
00368   }
00369 
00370   return NULL;
00371 }
00372 
00373 char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir)
00374 {
00375   assert(subdir < NUM_SUBDIRS);
00376   assert(sp < NUM_SEARCHPATHS);
00377 
00378   snprintf(buf, buflen, "%s%s", _searchpaths[sp], _subdirs[subdir]);
00379   return buf;
00380 }
00381 
00382 char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir)
00383 {
00384   Searchpath sp;
00385 
00386   /* Find and return the first valid directory */
00387   FOR_ALL_SEARCHPATHS(sp) {
00388     char *ret = FioAppendDirectory(buf, buflen, sp, subdir);
00389     if (FileExists(buf)) return ret;
00390   }
00391 
00392   /* Could not find the directory, fall back to a base path */
00393   ttd_strlcpy(buf, _personal_dir, buflen);
00394 
00395   return buf;
00396 }
00397 
00398 static FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
00399 {
00400 #if defined(WIN32) && defined(UNICODE)
00401   /* fopen is implemented as a define with ellipses for
00402    * Unicode support (prepend an L). As we are not sending
00403    * a string, but a variable, it 'renames' the variable,
00404    * so make that variable to makes it compile happily */
00405   wchar_t Lmode[5];
00406   MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
00407 #endif
00408   FILE *f = NULL;
00409   char buf[MAX_PATH];
00410 
00411   if (subdir == NO_DIRECTORY) {
00412     strecpy(buf, filename, lastof(buf));
00413   } else {
00414     snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename);
00415   }
00416 
00417 #if defined(WIN32)
00418   if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL;
00419 #endif
00420 
00421   f = fopen(buf, mode);
00422 #if !defined(WIN32)
00423   if (f == NULL && strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1))) {
00424     f = fopen(buf, mode);
00425   }
00426 #endif
00427   if (f != NULL && filesize != NULL) {
00428     /* Find the size of the file */
00429     fseek(f, 0, SEEK_END);
00430     *filesize = ftell(f);
00431     fseek(f, 0, SEEK_SET);
00432   }
00433   return f;
00434 }
00435 
00443 FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize)
00444 {
00445   FILE *f = fopen(entry->tar_filename, "rb");
00446   if (f == NULL) return f;
00447 
00448   fseek(f, entry->position, SEEK_SET);
00449   if (filesize != NULL) *filesize = entry->size;
00450   return f;
00451 }
00452 
00460 FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
00461 {
00462   FILE *f = NULL;
00463   Searchpath sp;
00464 
00465   assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
00466 
00467   FOR_ALL_SEARCHPATHS(sp) {
00468     f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
00469     if (f != NULL || subdir == NO_DIRECTORY) break;
00470   }
00471 
00472   /* We can only use .tar in case of data-dir, and read-mode */
00473   if (f == NULL && mode[0] == 'r' && subdir != NO_DIRECTORY) {
00474     static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'.
00475     char resolved_name[MAX_RESOLVED_LENGTH];
00476 
00477     /* Filenames in tars are always forced to be lowercase */
00478     strecpy(resolved_name, filename, lastof(resolved_name));
00479     strtolower(resolved_name);
00480 
00481     size_t resolved_len = strlen(resolved_name);
00482 
00483     /* Resolve ONE directory link */
00484     for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
00485       const std::string &src = link->first;
00486       size_t len = src.length();
00487       if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) {
00488         /* Apply link */
00489         char resolved_name2[MAX_RESOLVED_LENGTH];
00490         const std::string &dest = link->second;
00491         strecpy(resolved_name2, &(resolved_name[len]), lastof(resolved_name2));
00492         strecpy(resolved_name, dest.c_str(), lastof(resolved_name));
00493         strecpy(&(resolved_name[dest.length()]), resolved_name2, lastof(resolved_name));
00494         break; // Only resolve one level
00495       }
00496     }
00497 
00498     TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
00499     if (it != _tar_filelist[subdir].end()) {
00500       f = FioFOpenFileTar(&((*it).second), filesize);
00501     }
00502   }
00503 
00504   /* Sometimes a full path is given. To support
00505    * the 'subdirectory' must be 'removed'. */
00506   if (f == NULL && subdir != NO_DIRECTORY) {
00507     switch (subdir) {
00508       case BASESET_DIR:
00509         f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
00510         if (f != NULL) break;
00511         /* FALL THROUGH */
00512       case NEWGRF_DIR:
00513         f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
00514         break;
00515 
00516       default:
00517         f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
00518         break;
00519     }
00520   }
00521 
00522   return f;
00523 }
00524 
00529 static void FioCreateDirectory(const char *name)
00530 {
00531 #if defined(WIN32) || defined(WINCE)
00532   CreateDirectory(OTTD2FS(name), NULL);
00533 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
00534   mkdir(OTTD2FS(name));
00535 #elif defined(__MORPHOS__) || defined(__AMIGAOS__)
00536   char buf[MAX_PATH];
00537   ttd_strlcpy(buf, name, MAX_PATH);
00538 
00539   size_t len = strlen(name) - 1;
00540   if (buf[len] == '/') {
00541     buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail
00542   }
00543 
00544   mkdir(OTTD2FS(buf), 0755);
00545 #else
00546   mkdir(OTTD2FS(name), 0755);
00547 #endif
00548 }
00549 
00557 bool AppendPathSeparator(char *buf, size_t buflen)
00558 {
00559   size_t s = strlen(buf);
00560 
00561   /* Length of string + path separator + '\0' */
00562   if (s != 0 && buf[s - 1] != PATHSEPCHAR) {
00563     if (s + 2 >= buflen) return false;
00564 
00565     buf[s]     = PATHSEPCHAR;
00566     buf[s + 1] = '\0';
00567   }
00568 
00569   return true;
00570 }
00571 
00578 char *BuildWithFullPath(const char *dir)
00579 {
00580   char *dest = MallocT<char>(MAX_PATH);
00581   ttd_strlcpy(dest, dir, MAX_PATH);
00582 
00583   /* Check if absolute or relative path */
00584   const char *s = strchr(dest, PATHSEPCHAR);
00585 
00586   /* Add absolute path */
00587   if (s == NULL || dest != s) {
00588     if (getcwd(dest, MAX_PATH) == NULL) *dest = '\0';
00589     AppendPathSeparator(dest, MAX_PATH);
00590     ttd_strlcat(dest, dir, MAX_PATH);
00591   }
00592   AppendPathSeparator(dest, MAX_PATH);
00593 
00594   return dest;
00595 }
00596 
00602 const char *FioTarFirstDir(const char *tarname, Subdirectory subdir)
00603 {
00604   TarList::iterator it = _tar_list[subdir].find(tarname);
00605   if (it == _tar_list[subdir].end()) return NULL;
00606   return (*it).second.dirname;
00607 }
00608 
00609 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
00610 {
00611   std::string src = srcParam;
00612   std::string dest = destParam;
00613   /* Tar internals assume lowercase */
00614   std::transform(src.begin(), src.end(), src.begin(), tolower);
00615   std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
00616 
00617   TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
00618   if (dest_file != _tar_filelist[subdir].end()) {
00619     /* Link to file. Process the link like the destination file. */
00620     _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
00621   } else {
00622     /* Destination file not found. Assume 'link to directory'
00623      * Append PATHSEPCHAR to 'src' and 'dest' if needed */
00624     const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
00625     const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
00626     _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
00627   }
00628 }
00629 
00630 void FioTarAddLink(const char *src, const char *dest, Subdirectory subdir)
00631 {
00632   TarAddLink(src, dest, subdir);
00633 }
00634 
00640 static void SimplifyFileName(char *name)
00641 {
00642   /* Force lowercase */
00643   strtolower(name);
00644 
00645   /* Tar-files always have '/' path-seperator, but we want our PATHSEPCHAR */
00646 #if (PATHSEPCHAR != '/')
00647   for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
00648 #endif
00649 }
00650 
00656 uint TarScanner::DoScan(Subdirectory sd)
00657 {
00658   _tar_filelist[sd].clear();
00659   _tar_list[sd].clear();
00660   uint num = this->Scan(".tar", sd, false);
00661   if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
00662   return num;
00663 }
00664 
00665 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
00666 {
00667   DEBUG(misc, 1, "Scanning for tars");
00668   TarScanner fs;
00669   uint num = 0;
00670   if (mode & TarScanner::BASESET) {
00671     num += fs.DoScan(BASESET_DIR);
00672   }
00673   if (mode & TarScanner::NEWGRF) {
00674     num += fs.DoScan(NEWGRF_DIR);
00675   }
00676   if (mode & TarScanner::AI) {
00677     num += fs.DoScan(AI_DIR);
00678     num += fs.DoScan(AI_LIBRARY_DIR);
00679   }
00680   if (mode & TarScanner::GAME) {
00681     num += fs.DoScan(GAME_DIR);
00682     num += fs.DoScan(GAME_LIBRARY_DIR);
00683   }
00684   if (mode & TarScanner::SCENARIO) {
00685     num += fs.DoScan(SCENARIO_DIR);
00686   }
00687   DEBUG(misc, 1, "Scan complete, found %d files", num);
00688   return num;
00689 }
00690 
00697 bool TarScanner::AddFile(Subdirectory sd, const char *filename)
00698 {
00699   this->subdir = sd;
00700   return this->AddFile(filename, 0);
00701 }
00702 
00703 bool TarScanner::AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
00704 {
00705   /* No tar within tar. */
00706   assert(tar_filename == NULL);
00707 
00708   /* The TAR-header, repeated for every file */
00709   typedef struct TarHeader {
00710     char name[100];      
00711     char mode[8];
00712     char uid[8];
00713     char gid[8];
00714     char size[12];       
00715     char mtime[12];
00716     char chksum[8];
00717     char typeflag;
00718     char linkname[100];
00719     char magic[6];
00720     char version[2];
00721     char uname[32];
00722     char gname[32];
00723     char devmajor[8];
00724     char devminor[8];
00725     char prefix[155];    
00726 
00727     char unused[12];
00728   } TarHeader;
00729 
00730   /* Check if we already seen this file */
00731   TarList::iterator it = _tar_list[this->subdir].find(filename);
00732   if (it != _tar_list[this->subdir].end()) return false;
00733 
00734   FILE *f = fopen(filename, "rb");
00735   /* Although the file has been found there can be
00736    * a number of reasons we cannot open the file.
00737    * Most common case is when we simply have not
00738    * been given read access. */
00739   if (f == NULL) return false;
00740 
00741   const char *dupped_filename = strdup(filename);
00742   _tar_list[this->subdir][filename].filename = dupped_filename;
00743   _tar_list[this->subdir][filename].dirname = NULL;
00744 
00745   TarLinkList links; 
00746 
00747   TarHeader th;
00748   char buf[sizeof(th.name) + 1], *end;
00749   char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
00750   char link[sizeof(th.linkname) + 1];
00751   char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
00752   size_t num = 0, pos = 0;
00753 
00754   /* Make a char of 512 empty bytes */
00755   char empty[512];
00756   memset(&empty[0], 0, sizeof(empty));
00757 
00758   for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
00759     size_t num_bytes_read = fread(&th, 1, 512, f);
00760     if (num_bytes_read != 512) break;
00761     pos += num_bytes_read;
00762 
00763     /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
00764     if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
00765       /* If we have only zeros in the block, it can be an end-of-file indicator */
00766       if (memcmp(&th, &empty[0], 512) == 0) continue;
00767 
00768       DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
00769       return false;
00770     }
00771 
00772     name[0] = '\0';
00773     size_t len = 0;
00774 
00775     /* The prefix contains the directory-name */
00776     if (th.prefix[0] != '\0') {
00777       memcpy(name, th.prefix, sizeof(th.prefix));
00778       name[sizeof(th.prefix)] = '\0';
00779       len = strlen(name);
00780       name[len] = PATHSEPCHAR;
00781       len++;
00782     }
00783 
00784     /* Copy the name of the file in a safe way at the end of 'name' */
00785     memcpy(&name[len], th.name, sizeof(th.name));
00786     name[len + sizeof(th.name)] = '\0';
00787 
00788     /* Calculate the size of the file.. for some strange reason this is stored as a string */
00789     memcpy(buf, th.size, sizeof(th.size));
00790     buf[sizeof(th.size)] = '\0';
00791     size_t skip = strtoul(buf, &end, 8);
00792 
00793     switch (th.typeflag) {
00794       case '\0':
00795       case '0': { // regular file
00796         /* Ignore empty files */
00797         if (skip == 0) break;
00798 
00799         if (strlen(name) == 0) break;
00800 
00801         /* Store this entry in the list */
00802         TarFileListEntry entry;
00803         entry.tar_filename = dupped_filename;
00804         entry.size         = skip;
00805         entry.position     = pos;
00806 
00807         /* Convert to lowercase and our PATHSEPCHAR */
00808         SimplifyFileName(name);
00809 
00810         DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
00811         if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
00812 
00813         break;
00814       }
00815 
00816       case '1': // hard links
00817       case '2': { // symbolic links
00818         /* Copy the destination of the link in a safe way at the end of 'linkname' */
00819         memcpy(link, th.linkname, sizeof(th.linkname));
00820         link[sizeof(th.linkname)] = '\0';
00821 
00822         if (strlen(name) == 0 || strlen(link) == 0) break;
00823 
00824         /* Convert to lowercase and our PATHSEPCHAR */
00825         SimplifyFileName(name);
00826         SimplifyFileName(link);
00827 
00828         /* Only allow relative links */
00829         if (link[0] == PATHSEPCHAR) {
00830           DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
00831           break;
00832         }
00833 
00834         /* Process relative path.
00835          * Note: The destination of links must not contain any directory-links. */
00836         strecpy(dest, name, lastof(dest));
00837         char *destpos = strrchr(dest, PATHSEPCHAR);
00838         if (destpos == NULL) destpos = dest;
00839         *destpos = '\0';
00840 
00841         char *pos = link;
00842         while (*pos != '\0') {
00843           char *next = strchr(link, PATHSEPCHAR);
00844           if (next == NULL) next = pos + strlen(pos);
00845 
00846           /* Skip '.' (current dir) */
00847           if (next != pos + 1 || pos[0] != '.') {
00848             if (next == pos + 2 && pos[0] == '.' && pos[1] == '.') {
00849               /* level up */
00850               if (dest[0] == '\0') {
00851                 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
00852                 break;
00853               }
00854 
00855               /* Truncate 'dest' after last PATHSEPCHAR.
00856                * This assumes that the truncated part is a real directory and not a link. */
00857               destpos = strrchr(dest, PATHSEPCHAR);
00858               if (destpos == NULL) destpos = dest;
00859             } else {
00860               /* Append at end of 'dest' */
00861               if (destpos != dest) *(destpos++) = PATHSEPCHAR;
00862               strncpy(destpos, pos, next - pos); // Safe as we do '\0'-termination ourselves
00863               destpos += next - pos;
00864             }
00865             *destpos = '\0';
00866           }
00867 
00868           pos = next;
00869         }
00870 
00871         /* Store links in temporary list */
00872         DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
00873         links.insert(TarLinkList::value_type(name, dest));
00874 
00875         break;
00876       }
00877 
00878       case '5': // directory
00879         /* Convert to lowercase and our PATHSEPCHAR */
00880         SimplifyFileName(name);
00881 
00882         /* Store the first directory name we detect */
00883         DEBUG(misc, 6, "Found dir in tar: %s", name);
00884         if (_tar_list[this->subdir][filename].dirname == NULL) _tar_list[this->subdir][filename].dirname = strdup(name);
00885         break;
00886 
00887       default:
00888         /* Ignore other types */
00889         break;
00890     }
00891 
00892     /* Skip to the next block.. */
00893     skip = Align(skip, 512);
00894     fseek(f, skip, SEEK_CUR);
00895     pos += skip;
00896   }
00897 
00898   DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
00899   fclose(f);
00900 
00901   /* Resolve file links and store directory links.
00902    * We restrict usage of links to two cases:
00903    *  1) Links to directories:
00904    *      Both the source path and the destination path must NOT contain any further links.
00905    *      When resolving files at most one directory link is resolved.
00906    *  2) Links to files:
00907    *      The destination path must NOT contain any links.
00908    *      The source path may contain one directory link.
00909    */
00910   for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
00911     const std::string &src = link->first;
00912     const std::string &dest = link->second;
00913     TarAddLink(src, dest, this->subdir);
00914   }
00915 
00916   return true;
00917 }
00918 
00926 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
00927 {
00928   TarList::iterator it = _tar_list[subdir].find(tar_filename);
00929   /* We don't know the file. */
00930   if (it == _tar_list[subdir].end()) return false;
00931 
00932   const char *dirname = (*it).second.dirname;
00933 
00934   /* The file doesn't have a sub directory! */
00935   if (dirname == NULL) return false;
00936 
00937   char filename[MAX_PATH];
00938   strecpy(filename, tar_filename, lastof(filename));
00939   char *p = strrchr(filename, PATHSEPCHAR);
00940   /* The file's path does not have a separator? */
00941   if (p == NULL) return false;
00942 
00943   p++;
00944   strecpy(p, dirname, lastof(filename));
00945   DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
00946   FioCreateDirectory(filename);
00947 
00948   for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
00949     if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
00950 
00951     strecpy(p, (*it2).first.c_str(), lastof(filename));
00952 
00953     DEBUG(misc, 9, "  extracting %s", filename);
00954 
00955     /* First open the file in the .tar. */
00956     size_t to_copy = 0;
00957     FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
00958     if (in == NULL) {
00959       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
00960       return false;
00961     }
00962 
00963     /* Now open the 'output' file. */
00964     FILE *out = fopen(filename, "wb");
00965     if (out == NULL) {
00966       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
00967       fclose(in);
00968       return false;
00969     }
00970 
00971     /* Now read from the tar and write it into the file. */
00972     char buffer[4096];
00973     size_t read;
00974     for (; to_copy != 0; to_copy -= read) {
00975       read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
00976       if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
00977     }
00978 
00979     /* Close everything up. */
00980     fclose(in);
00981     fclose(out);
00982 
00983     if (to_copy != 0) {
00984       DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
00985       return false;
00986     }
00987   }
00988 
00989   DEBUG(misc, 9, "  extraction successful");
00990   return true;
00991 }
00992 
00993 #if defined(WIN32) || defined(WINCE)
00994 
00999 extern void DetermineBasePaths(const char *exe);
01000 #else /* defined(WIN32) || defined(WINCE) */
01001 
01009 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
01010 {
01011   bool success = false;
01012 #ifdef WITH_COCOA
01013   char *app_bundle = strchr(exe, '.');
01014   while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
01015 
01016   if (app_bundle != NULL) app_bundle[0] = '\0';
01017 #endif /* WITH_COCOA */
01018   char *s = const_cast<char *>(strrchr(exe, PATHSEPCHAR));
01019   if (s != NULL) {
01020     *s = '\0';
01021 #if defined(__DJGPP__)
01022     /* If we want to go to the root, we can't use cd C:, but we must use '/' */
01023     if (s[-1] == ':') chdir("/");
01024 #endif
01025     if (chdir(exe) != 0) {
01026       DEBUG(misc, 0, "Directory with the binary does not exist?");
01027     } else {
01028       success = true;
01029     }
01030     *s = PATHSEPCHAR;
01031   }
01032 #ifdef WITH_COCOA
01033   if (app_bundle != NULL) app_bundle[0] = '.';
01034 #endif /* WITH_COCOA */
01035   return success;
01036 }
01037 
01048 bool DoScanWorkingDirectory()
01049 {
01050   /* No working directory, so nothing to do. */
01051   if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
01052 
01053   /* Working directory is root, so do nothing. */
01054   if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
01055 
01056   /* No personal/home directory, so the working directory won't be that. */
01057   if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
01058 
01059   char tmp[MAX_PATH];
01060   snprintf(tmp, lengthof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
01061   AppendPathSeparator(tmp, MAX_PATH);
01062   return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
01063 }
01064 
01069 void DetermineBasePaths(const char *exe)
01070 {
01071   char tmp[MAX_PATH];
01072 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
01073   _searchpaths[SP_PERSONAL_DIR] = NULL;
01074 #else
01075 #ifdef __HAIKU__
01076   BPath path;
01077   find_directory(B_USER_SETTINGS_DIRECTORY, &path);
01078   const char *homedir = path.Path();
01079 #else
01080   const char *homedir = getenv("HOME");
01081 
01082   if (homedir == NULL) {
01083     const struct passwd *pw = getpwuid(getuid());
01084     homedir = (pw == NULL) ? "" : pw->pw_dir;
01085   }
01086 #endif
01087 
01088   snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
01089   AppendPathSeparator(tmp, MAX_PATH);
01090 
01091   _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
01092 #endif
01093 
01094 #if defined(WITH_SHARED_DIR)
01095   snprintf(tmp, MAX_PATH, "%s", SHARED_DIR);
01096   AppendPathSeparator(tmp, MAX_PATH);
01097   _searchpaths[SP_SHARED_DIR] = strdup(tmp);
01098 #else
01099   _searchpaths[SP_SHARED_DIR] = NULL;
01100 #endif
01101 
01102 #if defined(__MORPHOS__) || defined(__AMIGA__)
01103   _searchpaths[SP_WORKING_DIR] = NULL;
01104 #else
01105   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
01106   AppendPathSeparator(tmp, MAX_PATH);
01107   _searchpaths[SP_WORKING_DIR] = strdup(tmp);
01108 #endif
01109 
01110   _do_scan_working_directory = DoScanWorkingDirectory();
01111 
01112   /* Change the working directory to that one of the executable */
01113   if (ChangeWorkingDirectoryToExecutable(exe)) {
01114     if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
01115     AppendPathSeparator(tmp, MAX_PATH);
01116     _searchpaths[SP_BINARY_DIR] = strdup(tmp);
01117   } else {
01118     _searchpaths[SP_BINARY_DIR] = NULL;
01119   }
01120 
01121   if (_searchpaths[SP_WORKING_DIR] != NULL) {
01122     /* Go back to the current working directory. */
01123     if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
01124       DEBUG(misc, 0, "Failed to return to working directory!");
01125     }
01126   }
01127 
01128 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
01129   _searchpaths[SP_INSTALLATION_DIR] = NULL;
01130 #else
01131   snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
01132   AppendPathSeparator(tmp, MAX_PATH);
01133   _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
01134 #endif
01135 #ifdef WITH_COCOA
01136 extern void cocoaSetApplicationBundleDir();
01137   cocoaSetApplicationBundleDir();
01138 #else
01139   _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
01140 #endif
01141 }
01142 #endif /* defined(WIN32) || defined(WINCE) */
01143 
01144 char *_personal_dir;
01145 
01152 void DeterminePaths(const char *exe)
01153 {
01154   DetermineBasePaths(exe);
01155 
01156   Searchpath sp;
01157   FOR_ALL_SEARCHPATHS(sp) {
01158     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
01159     DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
01160   }
01161 
01162   if (_config_file != NULL) {
01163     _personal_dir = strdup(_config_file);
01164     char *end = strrchr(_personal_dir, PATHSEPCHAR);
01165     if (end == NULL) {
01166       _personal_dir[0] = '\0';
01167     } else {
01168       end[1] = '\0';
01169     }
01170   } else {
01171     char personal_dir[MAX_PATH];
01172     if (FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg") != NULL) {
01173       char *end = strrchr(personal_dir, PATHSEPCHAR);
01174       if (end != NULL) end[1] = '\0';
01175       _personal_dir = strdup(personal_dir);
01176       _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
01177     } else {
01178       static const Searchpath new_openttd_cfg_order[] = {
01179           SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
01180         };
01181 
01182       for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
01183         if (IsValidSearchPath(new_openttd_cfg_order[i])) {
01184           _personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
01185           _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
01186           break;
01187         }
01188       }
01189     }
01190   }
01191 
01192   DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
01193 
01194   _highscore_file = str_fmt("%shs.dat", _personal_dir);
01195   extern char *_hotkeys_file;
01196   _hotkeys_file = str_fmt("%shotkeys.cfg",  _personal_dir);
01197 
01198   /* Make the necessary folders */
01199 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
01200   FioCreateDirectory(_personal_dir);
01201 #endif
01202 
01203   static const Subdirectory default_subdirs[] = {
01204     SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR
01205   };
01206 
01207   for (uint i = 0; i < lengthof(default_subdirs); i++) {
01208     char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
01209     FioCreateDirectory(dir);
01210     free(dir);
01211   }
01212 
01213   /* If we have network we make a directory for the autodownloading of content */
01214   _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
01215 #ifdef ENABLE_NETWORK
01216   FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
01217 
01218   /* Create the directory for each of the types of content */
01219   const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
01220   for (uint i = 0; i < lengthof(dirs); i++) {
01221     char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
01222     FioCreateDirectory(tmp);
01223     free(tmp);
01224   }
01225 
01226   extern char *_log_file;
01227   _log_file = str_fmt("%sopenttd.log",  _personal_dir);
01228 #else /* ENABLE_NETWORK */
01229   /* If we don't have networking, we don't need to make the directory. But
01230    * if it exists we keep it, otherwise remove it from the search paths. */
01231   if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR]))  {
01232     free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
01233     _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
01234   }
01235 #endif /* ENABLE_NETWORK */
01236 }
01237 
01242 void SanitizeFilename(char *filename)
01243 {
01244   for (; *filename != '\0'; filename++) {
01245     switch (*filename) {
01246       /* The following characters are not allowed in filenames
01247        * on at least one of the supported operating systems: */
01248       case ':': case '\\': case '*': case '?': case '/':
01249       case '<': case '>': case '|': case '"':
01250         *filename = '_';
01251         break;
01252     }
01253   }
01254 }
01255 
01264 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
01265 {
01266   FILE *in = fopen(filename, "rb");
01267   if (in == NULL) return NULL;
01268 
01269   fseek(in, 0, SEEK_END);
01270   size_t len = ftell(in);
01271   fseek(in, 0, SEEK_SET);
01272   if (len > maxsize) {
01273     fclose(in);
01274     return NULL;
01275   }
01276   byte *mem = MallocT<byte>(len + 1);
01277   mem[len] = 0;
01278   if (fread(mem, len, 1, in) != 1) {
01279     fclose(in);
01280     free(mem);
01281     return NULL;
01282   }
01283   fclose(in);
01284 
01285   *lenp = len;
01286   return mem;
01287 }
01288 
01295 static bool MatchesExtension(const char *extension, const char *filename)
01296 {
01297   if (extension == NULL) return true;
01298 
01299   const char *ext = strrchr(filename, extension[0]);
01300   return ext != NULL && strcasecmp(ext, extension) == 0;
01301 }
01302 
01312 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
01313 {
01314   extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
01315 
01316   uint num = 0;
01317   struct stat sb;
01318   struct dirent *dirent;
01319   DIR *dir;
01320 
01321   if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
01322 
01323   while ((dirent = readdir(dir)) != NULL) {
01324     const char *d_name = FS2OTTD(dirent->d_name);
01325     char filename[MAX_PATH];
01326 
01327     if (!FiosIsValidFile(path, dirent, &sb)) continue;
01328 
01329     snprintf(filename, lengthof(filename), "%s%s", path, d_name);
01330 
01331     if (S_ISDIR(sb.st_mode)) {
01332       /* Directory */
01333       if (!recursive) continue;
01334       if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
01335       if (!AppendPathSeparator(filename, lengthof(filename))) continue;
01336       num += ScanPath(fs, extension, filename, basepath_length, recursive);
01337     } else if (S_ISREG(sb.st_mode)) {
01338       /* File */
01339       if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, NULL)) num++;
01340     }
01341   }
01342 
01343   closedir(dir);
01344 
01345   return num;
01346 }
01347 
01354 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
01355 {
01356   uint num = 0;
01357   const char *filename = (*tar).first.c_str();
01358 
01359   if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
01360 
01361   return num;
01362 }
01363 
01373 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
01374 {
01375   this->subdir = sd;
01376 
01377   Searchpath sp;
01378   char path[MAX_PATH];
01379   TarFileList::iterator tar;
01380   uint num = 0;
01381 
01382   FOR_ALL_SEARCHPATHS(sp) {
01383     /* Don't search in the working directory */
01384     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
01385 
01386     FioAppendDirectory(path, MAX_PATH, sp, sd);
01387     num += ScanPath(this, extension, path, strlen(path), recursive);
01388   }
01389 
01390   if (tars && sd != NO_DIRECTORY) {
01391     FOR_ALL_TARS(tar, sd) {
01392       num += ScanTar(this, extension, tar);
01393     }
01394   }
01395 
01396   switch (sd) {
01397     case BASESET_DIR:
01398       num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
01399       /* FALL THROUGH */
01400     case NEWGRF_DIR:
01401       num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
01402       break;
01403 
01404     default: break;
01405   }
01406 
01407   return num;
01408 }
01409 
01418 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
01419 {
01420   char path[MAX_PATH];
01421   strecpy(path, directory, lastof(path));
01422   if (!AppendPathSeparator(path, lengthof(path))) return 0;
01423   return ScanPath(this, extension, path, strlen(path), recursive);
01424 }