fileio.cpp

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

Generated on Fri May 27 04:19:41 2011 for OpenTTD by  doxygen 1.6.1