fileio.cpp

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

Generated on Mon Aug 30 19:36:54 2010 for OpenTTD by  doxygen 1.6.1