fileio.cpp

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