fileio.cpp

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