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

Generated on Fri Jun 3 05:18:50 2011 for OpenTTD by  doxygen 1.6.1