fileio.cpp

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

Generated on Fri Apr 30 21:55:20 2010 for OpenTTD by  doxygen 1.6.1