fileio.cpp

Go to the documentation of this file.
00001 /* $Id: fileio.cpp 26543 2014-04-29 18:35:01Z frosch $ */
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(pos, PATHSEPCHAR);
00853           if (next == NULL) {
00854             next = pos + strlen(pos);
00855           } else {
00856             /* Terminate the substring up to the path separator character. */
00857             *next++= '\0';
00858           }
00859 
00860           if (strcmp(pos, ".") == 0) {
00861             /* Skip '.' (current dir) */
00862           } else if (strcmp(pos, "..") == 0) {
00863             /* level up */
00864             if (dest[0] == '\0') {
00865               DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
00866               break;
00867             }
00868 
00869             /* Truncate 'dest' after last PATHSEPCHAR.
00870              * This assumes that the truncated part is a real directory and not a link. */
00871             destpos = strrchr(dest, PATHSEPCHAR);
00872             if (destpos == NULL) destpos = dest;
00873             *destpos = '\0';
00874           } else {
00875             /* Append at end of 'dest' */
00876             if (destpos != dest) destpos = strecpy(destpos, PATHSEP, lastof(dest));
00877             destpos = strecpy(destpos, pos, lastof(dest));
00878           }
00879 
00880           if (destpos >= lastof(dest)) {
00881             DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename);
00882             fclose(f);
00883             return false;
00884           }
00885 
00886           pos = next;
00887         }
00888 
00889         /* Store links in temporary list */
00890         DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
00891         links.insert(TarLinkList::value_type(name, dest));
00892 
00893         break;
00894       }
00895 
00896       case '5': // directory
00897         /* Convert to lowercase and our PATHSEPCHAR */
00898         SimplifyFileName(name);
00899 
00900         /* Store the first directory name we detect */
00901         DEBUG(misc, 6, "Found dir in tar: %s", name);
00902         if (_tar_list[this->subdir][filename].dirname == NULL) _tar_list[this->subdir][filename].dirname = strdup(name);
00903         break;
00904 
00905       default:
00906         /* Ignore other types */
00907         break;
00908     }
00909 
00910     /* Skip to the next block.. */
00911     skip = Align(skip, 512);
00912     if (fseek(f, skip, SEEK_CUR) < 0) {
00913       DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename);
00914       fclose(f);
00915       return false;
00916     }
00917     pos += skip;
00918   }
00919 
00920   DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
00921   fclose(f);
00922 
00923   /* Resolve file links and store directory links.
00924    * We restrict usage of links to two cases:
00925    *  1) Links to directories:
00926    *      Both the source path and the destination path must NOT contain any further links.
00927    *      When resolving files at most one directory link is resolved.
00928    *  2) Links to files:
00929    *      The destination path must NOT contain any links.
00930    *      The source path may contain one directory link.
00931    */
00932   for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
00933     const std::string &src = link->first;
00934     const std::string &dest = link->second;
00935     TarAddLink(src, dest, this->subdir);
00936   }
00937 
00938   return true;
00939 }
00940 
00948 bool ExtractTar(const char *tar_filename, Subdirectory subdir)
00949 {
00950   TarList::iterator it = _tar_list[subdir].find(tar_filename);
00951   /* We don't know the file. */
00952   if (it == _tar_list[subdir].end()) return false;
00953 
00954   const char *dirname = (*it).second.dirname;
00955 
00956   /* The file doesn't have a sub directory! */
00957   if (dirname == NULL) return false;
00958 
00959   char filename[MAX_PATH];
00960   strecpy(filename, tar_filename, lastof(filename));
00961   char *p = strrchr(filename, PATHSEPCHAR);
00962   /* The file's path does not have a separator? */
00963   if (p == NULL) return false;
00964 
00965   p++;
00966   strecpy(p, dirname, lastof(filename));
00967   DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename, filename);
00968   FioCreateDirectory(filename);
00969 
00970   for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
00971     if (strcmp((*it2).second.tar_filename, tar_filename) != 0) continue;
00972 
00973     strecpy(p, (*it2).first.c_str(), lastof(filename));
00974 
00975     DEBUG(misc, 9, "  extracting %s", filename);
00976 
00977     /* First open the file in the .tar. */
00978     size_t to_copy = 0;
00979     FILE *in = FioFOpenFileTar(&(*it2).second, &to_copy);
00980     if (in == NULL) {
00981       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, tar_filename);
00982       return false;
00983     }
00984 
00985     /* Now open the 'output' file. */
00986     FILE *out = fopen(filename, "wb");
00987     if (out == NULL) {
00988       DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename, filename);
00989       fclose(in);
00990       return false;
00991     }
00992 
00993     /* Now read from the tar and write it into the file. */
00994     char buffer[4096];
00995     size_t read;
00996     for (; to_copy != 0; to_copy -= read) {
00997       read = fread(buffer, 1, min(to_copy, lengthof(buffer)), in);
00998       if (read <= 0 || fwrite(buffer, 1, read, out) != read) break;
00999     }
01000 
01001     /* Close everything up. */
01002     fclose(in);
01003     fclose(out);
01004 
01005     if (to_copy != 0) {
01006       DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename, (int)to_copy);
01007       return false;
01008     }
01009   }
01010 
01011   DEBUG(misc, 9, "  extraction successful");
01012   return true;
01013 }
01014 
01015 #if defined(WIN32) || defined(WINCE)
01016 
01021 extern void DetermineBasePaths(const char *exe);
01022 #else /* defined(WIN32) || defined(WINCE) */
01023 
01031 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
01032 {
01033   bool success = false;
01034 #ifdef WITH_COCOA
01035   char *app_bundle = strchr(exe, '.');
01036   while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
01037 
01038   if (app_bundle != NULL) app_bundle[0] = '\0';
01039 #endif /* WITH_COCOA */
01040   char *s = const_cast<char *>(strrchr(exe, PATHSEPCHAR));
01041   if (s != NULL) {
01042     *s = '\0';
01043 #if defined(__DJGPP__)
01044     /* If we want to go to the root, we can't use cd C:, but we must use '/' */
01045     if (s[-1] == ':') chdir("/");
01046 #endif
01047     if (chdir(exe) != 0) {
01048       DEBUG(misc, 0, "Directory with the binary does not exist?");
01049     } else {
01050       success = true;
01051     }
01052     *s = PATHSEPCHAR;
01053   }
01054 #ifdef WITH_COCOA
01055   if (app_bundle != NULL) app_bundle[0] = '.';
01056 #endif /* WITH_COCOA */
01057   return success;
01058 }
01059 
01070 bool DoScanWorkingDirectory()
01071 {
01072   /* No working directory, so nothing to do. */
01073   if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
01074 
01075   /* Working directory is root, so do nothing. */
01076   if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
01077 
01078   /* No personal/home directory, so the working directory won't be that. */
01079   if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
01080 
01081   char tmp[MAX_PATH];
01082   snprintf(tmp, lengthof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
01083   AppendPathSeparator(tmp, MAX_PATH);
01084   return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
01085 }
01086 
01091 void DetermineBasePaths(const char *exe)
01092 {
01093   char tmp[MAX_PATH];
01094 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
01095   const char *xdg_data_home = xdgDataHome(NULL);
01096   snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", xdg_data_home,
01097       PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
01098   free(xdg_data_home);
01099 
01100   AppendPathSeparator(tmp, MAX_PATH);
01101   _searchpaths[SP_PERSONAL_DIR_XDG] = strdup(tmp);
01102 #endif
01103 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
01104   _searchpaths[SP_PERSONAL_DIR] = NULL;
01105 #else
01106 #ifdef __HAIKU__
01107   BPath path;
01108   find_directory(B_USER_SETTINGS_DIRECTORY, &path);
01109   const char *homedir = strdup(path.Path());
01110 #else
01111   /* getenv is highly unsafe; duplicate it as soon as possible,
01112    * or at least before something else touches the environment
01113    * variables in any way. It can also contain all kinds of
01114    * unvalidated data we rather not want internally. */
01115   const char *homedir = getenv("HOME");
01116   if (homedir != NULL) {
01117     homedir = strndup(homedir, MAX_PATH);
01118   }
01119 
01120   if (homedir == NULL) {
01121     const struct passwd *pw = getpwuid(getuid());
01122     homedir = (pw == NULL) ? NULL : strdup(pw->pw_dir);
01123   }
01124 #endif
01125 
01126   if (homedir != NULL) {
01127     ValidateString(homedir);
01128     snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
01129     AppendPathSeparator(tmp, MAX_PATH);
01130 
01131     _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
01132     free(homedir);
01133   } else {
01134     _searchpaths[SP_PERSONAL_DIR] = NULL;
01135   }
01136 #endif
01137 
01138 #if defined(WITH_SHARED_DIR)
01139   snprintf(tmp, MAX_PATH, "%s", SHARED_DIR);
01140   AppendPathSeparator(tmp, MAX_PATH);
01141   _searchpaths[SP_SHARED_DIR] = strdup(tmp);
01142 #else
01143   _searchpaths[SP_SHARED_DIR] = NULL;
01144 #endif
01145 
01146 #if defined(__MORPHOS__) || defined(__AMIGA__)
01147   _searchpaths[SP_WORKING_DIR] = NULL;
01148 #else
01149   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
01150   AppendPathSeparator(tmp, MAX_PATH);
01151   _searchpaths[SP_WORKING_DIR] = strdup(tmp);
01152 #endif
01153 
01154   _do_scan_working_directory = DoScanWorkingDirectory();
01155 
01156   /* Change the working directory to that one of the executable */
01157   if (ChangeWorkingDirectoryToExecutable(exe)) {
01158     if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
01159     AppendPathSeparator(tmp, MAX_PATH);
01160     _searchpaths[SP_BINARY_DIR] = strdup(tmp);
01161   } else {
01162     _searchpaths[SP_BINARY_DIR] = NULL;
01163   }
01164 
01165   if (_searchpaths[SP_WORKING_DIR] != NULL) {
01166     /* Go back to the current working directory. */
01167     if (chdir(_searchpaths[SP_WORKING_DIR]) != 0) {
01168       DEBUG(misc, 0, "Failed to return to working directory!");
01169     }
01170   }
01171 
01172 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
01173   _searchpaths[SP_INSTALLATION_DIR] = NULL;
01174 #else
01175   snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
01176   AppendPathSeparator(tmp, MAX_PATH);
01177   _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
01178 #endif
01179 #ifdef WITH_COCOA
01180 extern void cocoaSetApplicationBundleDir();
01181   cocoaSetApplicationBundleDir();
01182 #else
01183   _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
01184 #endif
01185 }
01186 #endif /* defined(WIN32) || defined(WINCE) */
01187 
01188 const char *_personal_dir;
01189 
01196 void DeterminePaths(const char *exe)
01197 {
01198   DetermineBasePaths(exe);
01199 
01200 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
01201   char config_home[MAX_PATH];
01202 
01203   const char *xdg_config_home = xdgConfigHome(NULL);
01204   snprintf(config_home, MAX_PATH, "%s" PATHSEP "%s", xdg_config_home,
01205       PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR);
01206   free(xdg_config_home);
01207 
01208   AppendPathSeparator(config_home, MAX_PATH);
01209 #endif
01210 
01211   Searchpath sp;
01212   FOR_ALL_SEARCHPATHS(sp) {
01213     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
01214     DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
01215   }
01216 
01217   char *config_dir;
01218   if (_config_file != NULL) {
01219     config_dir = strdup(_config_file);
01220     char *end = strrchr(config_dir, PATHSEPCHAR);
01221     if (end == NULL) {
01222       config_dir[0] = '\0';
01223     } else {
01224       end[1] = '\0';
01225     }
01226   } else {
01227     char personal_dir[MAX_PATH];
01228     if (FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg") != NULL) {
01229       char *end = strrchr(personal_dir, PATHSEPCHAR);
01230       if (end != NULL) end[1] = '\0';
01231       config_dir = strdup(personal_dir);
01232       _config_file = str_fmt("%sopenttd.cfg", config_dir);
01233     } else {
01234 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
01235       /* No previous configuration file found. Use the configuration folder from XDG. */
01236       config_dir = config_home;
01237 #else
01238       static const Searchpath new_openttd_cfg_order[] = {
01239           SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
01240         };
01241 
01242       config_dir = NULL;
01243       for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
01244         if (IsValidSearchPath(new_openttd_cfg_order[i])) {
01245           config_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
01246           break;
01247         }
01248       }
01249       assert(config_dir != NULL);
01250 #endif
01251       _config_file = str_fmt("%sopenttd.cfg", config_dir);
01252     }
01253   }
01254 
01255   DEBUG(misc, 3, "%s found as config directory", config_dir);
01256 
01257   _highscore_file = str_fmt("%shs.dat", config_dir);
01258   extern char *_hotkeys_file;
01259   _hotkeys_file = str_fmt("%shotkeys.cfg", config_dir);
01260   extern char *_windows_file;
01261   _windows_file = str_fmt("%swindows.cfg", config_dir);
01262 
01263 #if defined(WITH_XDG_BASEDIR) && defined(WITH_PERSONAL_DIR)
01264   if (config_dir == config_home) {
01265     /* We are using the XDG configuration home for the config file,
01266      * then store the rest in the XDG data home folder. */
01267     _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
01268     FioCreateDirectory(_personal_dir);
01269   } else
01270 #endif
01271   {
01272     _personal_dir = config_dir;
01273   }
01274 
01275   /* Make the necessary folders */
01276 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
01277   FioCreateDirectory(config_dir);
01278   if (config_dir != _personal_dir) FioCreateDirectory(_personal_dir);
01279 #endif
01280 
01281   DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
01282 
01283   static const Subdirectory default_subdirs[] = {
01284     SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
01285   };
01286 
01287   for (uint i = 0; i < lengthof(default_subdirs); i++) {
01288     char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
01289     FioCreateDirectory(dir);
01290     free(dir);
01291   }
01292 
01293   /* If we have network we make a directory for the autodownloading of content */
01294   _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
01295 #ifdef ENABLE_NETWORK
01296   FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
01297 
01298   /* Create the directory for each of the types of content */
01299   const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
01300   for (uint i = 0; i < lengthof(dirs); i++) {
01301     char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
01302     FioCreateDirectory(tmp);
01303     free(tmp);
01304   }
01305 
01306   extern char *_log_file;
01307   _log_file = str_fmt("%sopenttd.log",  _personal_dir);
01308 #else /* ENABLE_NETWORK */
01309   /* If we don't have networking, we don't need to make the directory. But
01310    * if it exists we keep it, otherwise remove it from the search paths. */
01311   if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR]))  {
01312     free(_searchpaths[SP_AUTODOWNLOAD_DIR]);
01313     _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
01314   }
01315 #endif /* ENABLE_NETWORK */
01316 }
01317 
01322 void SanitizeFilename(char *filename)
01323 {
01324   for (; *filename != '\0'; filename++) {
01325     switch (*filename) {
01326       /* The following characters are not allowed in filenames
01327        * on at least one of the supported operating systems: */
01328       case ':': case '\\': case '*': case '?': case '/':
01329       case '<': case '>': case '|': case '"':
01330         *filename = '_';
01331         break;
01332     }
01333   }
01334 }
01335 
01344 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
01345 {
01346   FILE *in = fopen(filename, "rb");
01347   if (in == NULL) return NULL;
01348 
01349   fseek(in, 0, SEEK_END);
01350   size_t len = ftell(in);
01351   fseek(in, 0, SEEK_SET);
01352   if (len > maxsize) {
01353     fclose(in);
01354     return NULL;
01355   }
01356   byte *mem = MallocT<byte>(len + 1);
01357   mem[len] = 0;
01358   if (fread(mem, len, 1, in) != 1) {
01359     fclose(in);
01360     free(mem);
01361     return NULL;
01362   }
01363   fclose(in);
01364 
01365   *lenp = len;
01366   return mem;
01367 }
01368 
01375 static bool MatchesExtension(const char *extension, const char *filename)
01376 {
01377   if (extension == NULL) return true;
01378 
01379   const char *ext = strrchr(filename, extension[0]);
01380   return ext != NULL && strcasecmp(ext, extension) == 0;
01381 }
01382 
01392 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
01393 {
01394   extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
01395 
01396   uint num = 0;
01397   struct stat sb;
01398   struct dirent *dirent;
01399   DIR *dir;
01400 
01401   if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
01402 
01403   while ((dirent = readdir(dir)) != NULL) {
01404     const char *d_name = FS2OTTD(dirent->d_name);
01405     char filename[MAX_PATH];
01406 
01407     if (!FiosIsValidFile(path, dirent, &sb)) continue;
01408 
01409     snprintf(filename, lengthof(filename), "%s%s", path, d_name);
01410 
01411     if (S_ISDIR(sb.st_mode)) {
01412       /* Directory */
01413       if (!recursive) continue;
01414       if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
01415       if (!AppendPathSeparator(filename, lengthof(filename))) continue;
01416       num += ScanPath(fs, extension, filename, basepath_length, recursive);
01417     } else if (S_ISREG(sb.st_mode)) {
01418       /* File */
01419       if (MatchesExtension(extension, filename) && fs->AddFile(filename, basepath_length, NULL)) num++;
01420     }
01421   }
01422 
01423   closedir(dir);
01424 
01425   return num;
01426 }
01427 
01434 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
01435 {
01436   uint num = 0;
01437   const char *filename = (*tar).first.c_str();
01438 
01439   if (MatchesExtension(extension, filename) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
01440 
01441   return num;
01442 }
01443 
01453 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
01454 {
01455   this->subdir = sd;
01456 
01457   Searchpath sp;
01458   char path[MAX_PATH];
01459   TarFileList::iterator tar;
01460   uint num = 0;
01461 
01462   FOR_ALL_SEARCHPATHS(sp) {
01463     /* Don't search in the working directory */
01464     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
01465 
01466     FioAppendDirectory(path, MAX_PATH, sp, sd);
01467     num += ScanPath(this, extension, path, strlen(path), recursive);
01468   }
01469 
01470   if (tars && sd != NO_DIRECTORY) {
01471     FOR_ALL_TARS(tar, sd) {
01472       num += ScanTar(this, extension, tar);
01473     }
01474   }
01475 
01476   switch (sd) {
01477     case BASESET_DIR:
01478       num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
01479       /* FALL THROUGH */
01480     case NEWGRF_DIR:
01481       num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
01482       break;
01483 
01484     default: break;
01485   }
01486 
01487   return num;
01488 }
01489 
01498 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
01499 {
01500   char path[MAX_PATH];
01501   strecpy(path, directory, lastof(path));
01502   if (!AppendPathSeparator(path, lengthof(path))) return 0;
01503   return ScanPath(this, extension, path, strlen(path), recursive);
01504 }