fileio.cpp

Go to the documentation of this file.
00001 /* $Id: fileio.cpp 21247 2010-11-18 22:26:29Z 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 void ChangeWorkingDirectory(const char *exe)
00864 {
00865 #ifdef WITH_COCOA
00866   char *app_bundle = strchr(exe, '.');
00867   while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
00868 
00869   if (app_bundle != NULL) app_bundle[0] = '\0';
00870 #endif /* WITH_COCOA */
00871   char *s = const_cast<char *>(strrchr(exe, PATHSEPCHAR));
00872   if (s != NULL) {
00873     *s = '\0';
00874 #if defined(__DJGPP__)
00875     /* If we want to go to the root, we can't use cd C:, but we must use '/' */
00876     if (s[-1] == ':') chdir("/");
00877 #endif
00878     if (chdir(exe) != 0) DEBUG(misc, 0, "Directory with the binary does not exist?");
00879     *s = PATHSEPCHAR;
00880   }
00881 #ifdef WITH_COCOA
00882   if (app_bundle != NULL) app_bundle[0] = '.';
00883 #endif /* WITH_COCOA */
00884 }
00885 
00896 bool DoScanWorkingDirectory()
00897 {
00898   /* No working directory, so nothing to do. */
00899   if (_searchpaths[SP_WORKING_DIR] == NULL) return false;
00900 
00901   /* Working directory is root, so do nothing. */
00902   if (strcmp(_searchpaths[SP_WORKING_DIR], PATHSEP) == 0) return false;
00903 
00904   /* No personal/home directory, so the working directory won't be that. */
00905   if (_searchpaths[SP_PERSONAL_DIR] == NULL) return true;
00906 
00907   char tmp[MAX_PATH];
00908   snprintf(tmp, lengthof(tmp), "%s%s", _searchpaths[SP_WORKING_DIR], PERSONAL_DIR);
00909   AppendPathSeparator(tmp, MAX_PATH);
00910   return strcmp(tmp, _searchpaths[SP_PERSONAL_DIR]) != 0;
00911 }
00912 
00917 void DetermineBasePaths(const char *exe)
00918 {
00919   char tmp[MAX_PATH];
00920 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
00921   _searchpaths[SP_PERSONAL_DIR] = NULL;
00922 #else
00923 #ifdef __HAIKU__
00924   BPath path;
00925   find_directory(B_USER_SETTINGS_DIRECTORY, &path);
00926   const char *homedir = path.Path();
00927 #else
00928   const char *homedir = getenv("HOME");
00929 
00930   if (homedir == NULL) {
00931     const struct passwd *pw = getpwuid(getuid());
00932     homedir = (pw == NULL) ? "" : pw->pw_dir;
00933   }
00934 #endif
00935 
00936   snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
00937   AppendPathSeparator(tmp, MAX_PATH);
00938 
00939   _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
00940 #endif
00941 
00942 #if defined(WITH_SHARED_DIR)
00943   snprintf(tmp, MAX_PATH, "%s", SHARED_DIR);
00944   AppendPathSeparator(tmp, MAX_PATH);
00945   _searchpaths[SP_SHARED_DIR] = strdup(tmp);
00946 #else
00947   _searchpaths[SP_SHARED_DIR] = NULL;
00948 #endif
00949 
00950 #if defined(__MORPHOS__) || defined(__AMIGA__)
00951   _searchpaths[SP_WORKING_DIR] = NULL;
00952 #else
00953   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
00954   AppendPathSeparator(tmp, MAX_PATH);
00955   _searchpaths[SP_WORKING_DIR] = strdup(tmp);
00956 #endif
00957 
00958   _do_scan_working_directory = DoScanWorkingDirectory();
00959 
00960   /* Change the working directory to that one of the executable */
00961   ChangeWorkingDirectory(exe);
00962   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
00963   AppendPathSeparator(tmp, MAX_PATH);
00964   _searchpaths[SP_BINARY_DIR] = strdup(tmp);
00965 
00966   if (_searchpaths[SP_WORKING_DIR] != NULL) {
00967     /* Go back to the current working directory. */
00968     ChangeWorkingDirectory(_searchpaths[SP_WORKING_DIR]);
00969   }
00970 
00971 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
00972   _searchpaths[SP_INSTALLATION_DIR] = NULL;
00973 #else
00974   snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
00975   AppendPathSeparator(tmp, MAX_PATH);
00976   _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
00977 #endif
00978 #ifdef WITH_COCOA
00979 extern void cocoaSetApplicationBundleDir();
00980   cocoaSetApplicationBundleDir();
00981 #else
00982   _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
00983 #endif
00984 }
00985 #endif /* defined(WIN32) || defined(WINCE) */
00986 
00987 char *_personal_dir;
00988 
00995 void DeterminePaths(const char *exe)
00996 {
00997   DetermineBasePaths(exe);
00998 
00999   Searchpath sp;
01000   FOR_ALL_SEARCHPATHS(sp) {
01001     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
01002     DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
01003   }
01004 
01005   if (_config_file != NULL) {
01006     _personal_dir = strdup(_config_file);
01007     char *end = strrchr(_personal_dir, PATHSEPCHAR);
01008     if (end == NULL) {
01009       _personal_dir[0] = '\0';
01010     } else {
01011       end[1] = '\0';
01012     }
01013   } else {
01014     char personal_dir[MAX_PATH];
01015     FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg");
01016 
01017     if (FileExists(personal_dir)) {
01018       char *end = strrchr(personal_dir, PATHSEPCHAR);
01019       if (end != NULL) end[1] = '\0';
01020       _personal_dir = strdup(personal_dir);
01021       _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
01022     } else {
01023       static const Searchpath new_openttd_cfg_order[] = {
01024           SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
01025         };
01026 
01027       for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
01028         if (IsValidSearchPath(new_openttd_cfg_order[i])) {
01029           _personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
01030           _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
01031           break;
01032         }
01033       }
01034     }
01035   }
01036 
01037   DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
01038 
01039   _highscore_file = str_fmt("%shs.dat", _personal_dir);
01040   extern char *_hotkeys_file;
01041   _hotkeys_file = str_fmt("%shotkeys.cfg",  _personal_dir);
01042 
01043   /* Make the necessary folders */
01044 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
01045   FioCreateDirectory(_personal_dir);
01046 #endif
01047 
01048   static const Subdirectory default_subdirs[] = {
01049     SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR
01050   };
01051 
01052   for (uint i = 0; i < lengthof(default_subdirs); i++) {
01053     char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
01054     FioCreateDirectory(dir);
01055     free(dir);
01056   }
01057 
01058   /* If we have network we make a directory for the autodownloading of content */
01059   _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
01060 #ifdef ENABLE_NETWORK
01061   FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
01062 
01063   /* Create the directory for each of the types of content */
01064   const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, DATA_DIR, AI_DIR, AI_LIBRARY_DIR, GM_DIR };
01065   for (uint i = 0; i < lengthof(dirs); i++) {
01066     char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
01067     FioCreateDirectory(tmp);
01068     free(tmp);
01069   }
01070 
01071   extern char *_log_file;
01072   _log_file = str_fmt("%sopenttd.log",  _personal_dir);
01073 #else /* ENABLE_NETWORK */
01074   /* If we don't have networking, we don't need to make the directory. But
01075    * if it exists we keep it, otherwise remove it from the search paths. */
01076   if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR]))  {
01077     free((void*)_searchpaths[SP_AUTODOWNLOAD_DIR]);
01078     _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
01079   }
01080 #endif /* ENABLE_NETWORK */
01081 
01082   TarScanner::DoScan();
01083 }
01084 
01089 void SanitizeFilename(char *filename)
01090 {
01091   for (; *filename != '\0'; filename++) {
01092     switch (*filename) {
01093       /* The following characters are not allowed in filenames
01094        * on at least one of the supported operating systems: */
01095       case ':': case '\\': case '*': case '?': case '/':
01096       case '<': case '>': case '|': case '"':
01097         *filename = '_';
01098         break;
01099     }
01100   }
01101 }
01102 
01103 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
01104 {
01105   FILE *in = fopen(filename, "rb");
01106   if (in == NULL) return NULL;
01107 
01108   fseek(in, 0, SEEK_END);
01109   size_t len = ftell(in);
01110   fseek(in, 0, SEEK_SET);
01111   if (len > maxsize) {
01112     fclose(in);
01113     return NULL;
01114   }
01115   byte *mem = MallocT<byte>(len + 1);
01116   mem[len] = 0;
01117   if (fread(mem, len, 1, in) != 1) {
01118     fclose(in);
01119     free(mem);
01120     return NULL;
01121   }
01122   fclose(in);
01123 
01124   *lenp = len;
01125   return mem;
01126 }
01127 
01128 
01138 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
01139 {
01140   extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
01141 
01142   uint num = 0;
01143   struct stat sb;
01144   struct dirent *dirent;
01145   DIR *dir;
01146 
01147   if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
01148 
01149   while ((dirent = readdir(dir)) != NULL) {
01150     const char *d_name = FS2OTTD(dirent->d_name);
01151     char filename[MAX_PATH];
01152 
01153     if (!FiosIsValidFile(path, dirent, &sb)) continue;
01154 
01155     snprintf(filename, lengthof(filename), "%s%s", path, d_name);
01156 
01157     if (S_ISDIR(sb.st_mode)) {
01158       /* Directory */
01159       if (!recursive) continue;
01160       if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
01161       if (!AppendPathSeparator(filename, lengthof(filename))) continue;
01162       num += ScanPath(fs, extension, filename, basepath_length, recursive);
01163     } else if (S_ISREG(sb.st_mode)) {
01164       /* File */
01165       if (extension != NULL) {
01166         char *ext = strrchr(filename, '.');
01167 
01168         /* If no extension or extension isn't .grf, skip the file */
01169         if (ext == NULL) continue;
01170         if (strcasecmp(ext, extension) != 0) continue;
01171       }
01172 
01173       if (fs->AddFile(filename, basepath_length)) num++;
01174     }
01175   }
01176 
01177   closedir(dir);
01178 
01179   return num;
01180 }
01181 
01188 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
01189 {
01190   uint num = 0;
01191   const char *filename = (*tar).first.c_str();
01192 
01193   if (extension != NULL) {
01194     const char *ext = strrchr(filename, '.');
01195 
01196     /* If no extension or extension isn't .grf, skip the file */
01197     if (ext == NULL) return false;
01198     if (strcasecmp(ext, extension) != 0) return false;
01199   }
01200 
01201   if (fs->AddFile(filename, 0)) num++;
01202 
01203   return num;
01204 }
01205 
01215 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
01216 {
01217   Searchpath sp;
01218   char path[MAX_PATH];
01219   TarFileList::iterator tar;
01220   uint num = 0;
01221 
01222   FOR_ALL_SEARCHPATHS(sp) {
01223     /* Don't search in the working directory */
01224     if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
01225 
01226     FioAppendDirectory(path, MAX_PATH, sp, sd);
01227     num += ScanPath(this, extension, path, strlen(path), recursive);
01228   }
01229 
01230   if (tars) {
01231     FOR_ALL_TARS(tar) {
01232       num += ScanTar(this, extension, tar);
01233     }
01234   }
01235 
01236   return num;
01237 }
01238 
01247 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
01248 {
01249   char path[MAX_PATH];
01250   strecpy(path, directory, lastof(path));
01251   if (!AppendPathSeparator(path, lengthof(path))) return 0;
01252   return ScanPath(this, extension, path, strlen(path), recursive);
01253 }

Generated on Thu Jan 20 22:57:33 2011 for OpenTTD by  doxygen 1.6.1