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 }