fileio.cpp

Go to the documentation of this file.
00001 /* $Id: fileio.cpp 19155 2010-02-17 21:29:31Z 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 #ifdef OPENBSD
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   assert(f != NULL);
00567 
00568   const char *dupped_filename = strdup(filename);
00569   _tar_list[filename].filename = dupped_filename;
00570   _tar_list[filename].dirname = NULL;
00571 
00572   TarLinkList links; 
00573 
00574   TarHeader th;
00575   char buf[sizeof(th.name) + 1], *end;
00576   char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
00577   char link[sizeof(th.linkname) + 1];
00578   char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
00579   size_t num = 0, pos = 0;
00580 
00581   /* Make a char of 512 empty bytes */
00582   char empty[512];
00583   memset(&empty[0], 0, sizeof(empty));
00584 
00585   for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
00586     size_t num_bytes_read = fread(&th, 1, 512, f);
00587     if (num_bytes_read != 512) break;
00588     pos += num_bytes_read;
00589 
00590     /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
00591     if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
00592       /* If we have only zeros in the block, it can be an end-of-file indicator */
00593       if (memcmp(&th, &empty[0], 512) == 0) continue;
00594 
00595       DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename);
00596       return false;
00597     }
00598 
00599     name[0] = '\0';
00600     size_t len = 0;
00601 
00602     /* The prefix contains the directory-name */
00603     if (th.prefix[0] != '\0') {
00604       memcpy(name, th.prefix, sizeof(th.prefix));
00605       name[sizeof(th.prefix)] = '\0';
00606       len = strlen(name);
00607       name[len] = PATHSEPCHAR;
00608       len++;
00609     }
00610 
00611     /* Copy the name of the file in a safe way at the end of 'name' */
00612     memcpy(&name[len], th.name, sizeof(th.name));
00613     name[len + sizeof(th.name)] = '\0';
00614 
00615     /* Calculate the size of the file.. for some strange reason this is stored as a string */
00616     memcpy(buf, th.size, sizeof(th.size));
00617     buf[sizeof(th.size)] = '\0';
00618     size_t skip = strtoul(buf, &end, 8);
00619 
00620     switch (th.typeflag) {
00621       case '\0':
00622       case '0': { // regular file
00623         /* Ignore empty files */
00624         if (skip == 0) break;
00625 
00626         if (strlen(name) == 0) break;
00627 
00628         /* Store this entry in the list */
00629         TarFileListEntry entry;
00630         entry.tar_filename = dupped_filename;
00631         entry.size         = skip;
00632         entry.position     = pos;
00633 
00634         /* Convert to lowercase and our PATHSEPCHAR */
00635         SimplifyFileName(name);
00636 
00637         DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
00638         if (_tar_filelist.insert(TarFileList::value_type(name, entry)).second) num++;
00639 
00640         break;
00641       }
00642 
00643       case '1': // hard links
00644       case '2': { // symbolic links
00645         /* Copy the destination of the link in a safe way at the end of 'linkname' */
00646         memcpy(link, th.linkname, sizeof(th.linkname));
00647         link[sizeof(th.linkname)] = '\0';
00648 
00649         if (strlen(name) == 0 || strlen(link) == 0) break;
00650 
00651         /* Convert to lowercase and our PATHSEPCHAR */
00652         SimplifyFileName(name);
00653         SimplifyFileName(link);
00654 
00655         /* Only allow relative links */
00656         if (link[0] == PATHSEPCHAR) {
00657           DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
00658           break;
00659         }
00660 
00661         /* Process relative path.
00662          * Note: The destination of links must not contain any directory-links. */
00663         strecpy(dest, name, lastof(dest));
00664         char *destpos = strrchr(dest, PATHSEPCHAR);
00665         if (destpos == NULL) destpos = dest;
00666         *destpos = '\0';
00667 
00668         char *pos = link;
00669         while (*pos != '\0') {
00670           char *next = strchr(link, PATHSEPCHAR);
00671           if (next == NULL) next = pos + strlen(pos);
00672 
00673           /* Skip '.' (current dir) */
00674           if (next != pos + 1 || pos[0] != '.') {
00675             if (next == pos + 2 && pos[0] == '.' && pos[1] == '.') {
00676               /* level up */
00677               if (dest[0] == '\0') {
00678                 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
00679                 break;
00680               }
00681 
00682               /* Truncate 'dest' after last PATHSEPCHAR.
00683                * This assumes, that the truncated part is a real directory and not a link */
00684               destpos = strrchr(dest, PATHSEPCHAR);
00685               if (destpos == NULL) destpos = dest;
00686             } else {
00687               /* Append at end of 'dest' */
00688               if (destpos != dest) *(destpos++) = PATHSEPCHAR;
00689               strncpy(destpos, pos, next - pos); // Safe as we do '\0'-termination ourselves
00690               destpos += next - pos;
00691             }
00692             *destpos = '\0';
00693           }
00694 
00695           pos = next;
00696         }
00697 
00698         /* Store links in temporary list */
00699         DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
00700         links.insert(TarLinkList::value_type(name, dest));
00701 
00702         break;
00703       }
00704 
00705       case '5': // directory
00706         /* Convert to lowercase and our PATHSEPCHAR */
00707         SimplifyFileName(name);
00708 
00709         /* Store the first directory name we detect */
00710         DEBUG(misc, 6, "Found dir in tar: %s", name);
00711         if (_tar_list[filename].dirname == NULL) _tar_list[filename].dirname = strdup(name);
00712         break;
00713 
00714       default:
00715         /* Ignore other types */
00716         break;
00717     }
00718 
00719     /* Skip to the next block.. */
00720     skip = Align(skip, 512);
00721     fseek(f, skip, SEEK_CUR);
00722     pos += skip;
00723   }
00724 
00725   DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename, num);
00726   fclose(f);
00727 
00728   /* Resolve file links and store directory links.
00729    * We restrict usage of links to two cases:
00730    *  1) Links to directories:
00731    *      Both the source path and the destination path must NOT contain any further links.
00732    *      When resolving files at most one directory link is resolved.
00733    *  2) Links to files:
00734    *      The destination path must NOT contain any links.
00735    *      The source path may contain one directory link.
00736    */
00737   for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
00738     const std::string &src = link->first;
00739     const std::string &dest = link->second;
00740     TarAddLink(src, dest);
00741   }
00742 
00743   return true;
00744 }
00745 
00746 static int ScanPathForTarFiles(const char *path, size_t basepath_length)
00747 {
00748   extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
00749 
00750   uint num = 0;
00751   struct stat sb;
00752   struct dirent *dirent;
00753   DIR *dir;
00754 
00755   if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
00756 
00757   while ((dirent = readdir(dir)) != NULL) {
00758     const char *d_name = FS2OTTD(dirent->d_name);
00759     char filename[MAX_PATH];
00760 
00761     if (!FiosIsValidFile(path, dirent, &sb)) continue;
00762 
00763     snprintf(filename, lengthof(filename), "%s%s", path, d_name);
00764 
00765     if (S_ISDIR(sb.st_mode)) {
00766       /* Directory */
00767       if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
00768       AppendPathSeparator(filename, lengthof(filename));
00769       num += ScanPathForTarFiles(filename, basepath_length);
00770     } else if (S_ISREG(sb.st_mode)) {
00771       /* File */
00772       char *ext = strrchr(filename, '.');
00773 
00774       /* If no extension or extension isn't .tar, skip the file */
00775       if (ext == NULL) continue;
00776       if (strcasecmp(ext, ".tar") != 0) continue;
00777 
00778       if (TarListAddFile(filename)) num++;
00779     }
00780   }
00781 
00782   closedir(dir);
00783   return num;
00784 }
00785 
00786 void ScanForTarFiles()
00787 {
00788   Searchpath sp;
00789   char path[MAX_PATH];
00790   uint num = 0;
00791 
00792   DEBUG(misc, 1, "Scanning for tars");
00793   FOR_ALL_SEARCHPATHS(sp) {
00794     FioAppendDirectory(path, MAX_PATH, sp, DATA_DIR);
00795     num += ScanPathForTarFiles(path, strlen(path));
00796     FioAppendDirectory(path, MAX_PATH, sp, AI_DIR);
00797     num += ScanPathForTarFiles(path, strlen(path));
00798     FioAppendDirectory(path, MAX_PATH, sp, AI_LIBRARY_DIR);
00799     num += ScanPathForTarFiles(path, strlen(path));
00800     FioAppendDirectory(path, MAX_PATH, sp, SCENARIO_DIR);
00801     num += ScanPathForTarFiles(path, strlen(path));
00802   }
00803   DEBUG(misc, 1, "Scan complete, found %d files", num);
00804 }
00805 
00806 #if defined(WIN32) || defined(WINCE)
00807 
00812 extern void DetermineBasePaths(const char *exe);
00813 #else /* defined(WIN32) || defined(WINCE) */
00814 
00822 void ChangeWorkingDirectory(const char *exe)
00823 {
00824 #ifdef WITH_COCOA
00825   char *app_bundle = strchr(exe, '.');
00826   while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
00827 
00828   if (app_bundle != NULL) app_bundle[0] = '\0';
00829 #endif /* WITH_COCOA */
00830   char *s = const_cast<char *>(strrchr(exe, PATHSEPCHAR));
00831   if (s != NULL) {
00832     *s = '\0';
00833 #if defined(__DJGPP__)
00834     /* If we want to go to the root, we can't use cd C:, but we must use '/' */
00835     if (s[-1] == ':') chdir("/");
00836 #endif
00837     if (chdir(exe) != 0) DEBUG(misc, 0, "Directory with the binary does not exist?");
00838     *s = PATHSEPCHAR;
00839   }
00840 #ifdef WITH_COCOA
00841   if (app_bundle != NULL) app_bundle[0] = '.';
00842 #endif /* WITH_COCOA */
00843 }
00844 
00849 void DetermineBasePaths(const char *exe)
00850 {
00851   char tmp[MAX_PATH];
00852 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2) || !defined(WITH_PERSONAL_DIR)
00853   _searchpaths[SP_PERSONAL_DIR] = NULL;
00854 #else
00855 #ifdef __HAIKU__
00856   BPath path;
00857   find_directory(B_USER_SETTINGS_DIRECTORY, &path);
00858   const char *homedir = path.Path();
00859 #else
00860   const char *homedir = getenv("HOME");
00861 
00862   if (homedir == NULL) {
00863     const struct passwd *pw = getpwuid(getuid());
00864     homedir = (pw == NULL) ? "" : pw->pw_dir;
00865   }
00866 #endif
00867 
00868   snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
00869   AppendPathSeparator(tmp, MAX_PATH);
00870 
00871   _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
00872 #endif
00873 
00874 #if defined(WITH_SHARED_DIR)
00875   snprintf(tmp, MAX_PATH, "%s", SHARED_DIR);
00876   AppendPathSeparator(tmp, MAX_PATH);
00877   _searchpaths[SP_SHARED_DIR] = strdup(tmp);
00878 #else
00879   _searchpaths[SP_SHARED_DIR] = NULL;
00880 #endif
00881 
00882 #if defined(__MORPHOS__) || defined(__AMIGA__)
00883   _searchpaths[SP_WORKING_DIR] = NULL;
00884 #else
00885   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
00886   AppendPathSeparator(tmp, MAX_PATH);
00887   _searchpaths[SP_WORKING_DIR] = strdup(tmp);
00888 #endif
00889 
00890   /* Change the working directory to that one of the executable */
00891   ChangeWorkingDirectory(exe);
00892   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
00893   AppendPathSeparator(tmp, MAX_PATH);
00894   _searchpaths[SP_BINARY_DIR] = strdup(tmp);
00895 
00896   if (_searchpaths[SP_WORKING_DIR] != NULL) {
00897     /* Go back to the current working directory. */
00898     ChangeWorkingDirectory(_searchpaths[SP_WORKING_DIR]);
00899   }
00900 
00901 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || defined(OS2)
00902   _searchpaths[SP_INSTALLATION_DIR] = NULL;
00903 #else
00904   snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
00905   AppendPathSeparator(tmp, MAX_PATH);
00906   _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
00907 #endif
00908 #ifdef WITH_COCOA
00909 extern void cocoaSetApplicationBundleDir();
00910   cocoaSetApplicationBundleDir();
00911 #else
00912   _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
00913 #endif
00914 }
00915 #endif /* defined(WIN32) || defined(WINCE) */
00916 
00917 char *_personal_dir;
00918 
00925 void DeterminePaths(const char *exe)
00926 {
00927   DetermineBasePaths(exe);
00928 
00929   Searchpath sp;
00930   FOR_ALL_SEARCHPATHS(sp) DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
00931 
00932   if (_config_file != NULL) {
00933     _personal_dir = strdup(_config_file);
00934     char *end = strrchr(_personal_dir, PATHSEPCHAR);
00935     if (end == NULL) {
00936       _personal_dir[0] = '\0';
00937     } else {
00938       end[1] = '\0';
00939     }
00940   } else {
00941     char personal_dir[MAX_PATH];
00942     FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg");
00943 
00944     if (FileExists(personal_dir)) {
00945       char *end = strrchr(personal_dir, PATHSEPCHAR);
00946       if (end != NULL) end[1] = '\0';
00947       _personal_dir = strdup(personal_dir);
00948       _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
00949     } else {
00950       static const Searchpath new_openttd_cfg_order[] = {
00951           SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
00952         };
00953 
00954       for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
00955         if (IsValidSearchPath(new_openttd_cfg_order[i])) {
00956           _personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
00957           _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
00958           break;
00959         }
00960       }
00961     }
00962   }
00963 
00964   DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
00965 
00966   _highscore_file = str_fmt("%shs.dat", _personal_dir);
00967   _log_file = str_fmt("%sopenttd.log",  _personal_dir);
00968 
00969   /* Make the necessary folders */
00970 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
00971   FioCreateDirectory(_personal_dir);
00972 #endif
00973 
00974   static const Subdirectory default_subdirs[] = {
00975     SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR
00976   };
00977 
00978   for (uint i = 0; i < lengthof(default_subdirs); i++) {
00979     char *dir = str_fmt("%s%s", _personal_dir, _subdirs[default_subdirs[i]]);
00980     FioCreateDirectory(dir);
00981     free(dir);
00982   }
00983 
00984   /* If we have network we make a directory for the autodownloading of content */
00985   _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
00986 #ifdef ENABLE_NETWORK
00987   FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
00988 
00989   /* Create the directory for each of the types of content */
00990   const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, DATA_DIR, AI_DIR, AI_LIBRARY_DIR, GM_DIR };
00991   for (uint i = 0; i < lengthof(dirs); i++) {
00992     char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], _subdirs[dirs[i]]);
00993     FioCreateDirectory(tmp);
00994     free(tmp);
00995   }
00996 #else /* ENABLE_NETWORK */
00997   /* If we don't have networking, we don't need to make the directory. But
00998    * if it exists we keep it, otherwise remove it from the search paths. */
00999   if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR]))  {
01000     free((void*)_searchpaths[SP_AUTODOWNLOAD_DIR]);
01001     _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
01002   }
01003 #endif /* ENABLE_NETWORK */
01004 
01005   ScanForTarFiles();
01006 }
01007 
01012 void SanitizeFilename(char *filename)
01013 {
01014   for (; *filename != '\0'; filename++) {
01015     switch (*filename) {
01016       /* The following characters are not allowed in filenames
01017        * on at least one of the supported operating systems: */
01018       case ':': case '\\': case '*': case '?': case '/':
01019       case '<': case '>': case '|': case '"':
01020         *filename = '_';
01021         break;
01022     }
01023   }
01024 }
01025 
01026 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
01027 {
01028   FILE *in = fopen(filename, "rb");
01029   if (in == NULL) return NULL;
01030 
01031   fseek(in, 0, SEEK_END);
01032   size_t len = ftell(in);
01033   fseek(in, 0, SEEK_SET);
01034   if (len > maxsize) {
01035     fclose(in);
01036     return NULL;
01037   }
01038   byte *mem = MallocT<byte>(len + 1);
01039   mem[len] = 0;
01040   if (fread(mem, len, 1, in) != 1) {
01041     fclose(in);
01042     free(mem);
01043     return NULL;
01044   }
01045   fclose(in);
01046 
01047   *lenp = len;
01048   return mem;
01049 }
01050 
01051 
01061 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
01062 {
01063   extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
01064 
01065   uint num = 0;
01066   struct stat sb;
01067   struct dirent *dirent;
01068   DIR *dir;
01069 
01070   if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0;
01071 
01072   while ((dirent = readdir(dir)) != NULL) {
01073     const char *d_name = FS2OTTD(dirent->d_name);
01074     char filename[MAX_PATH];
01075 
01076     if (!FiosIsValidFile(path, dirent, &sb)) continue;
01077 
01078     snprintf(filename, lengthof(filename), "%s%s", path, d_name);
01079 
01080     if (S_ISDIR(sb.st_mode)) {
01081       /* Directory */
01082       if (!recursive) continue;
01083       if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
01084       AppendPathSeparator(filename, lengthof(filename));
01085       num += ScanPath(fs, extension, filename, basepath_length, recursive);
01086     } else if (S_ISREG(sb.st_mode)) {
01087       /* File */
01088       if (extension != NULL) {
01089         char *ext = strrchr(filename, '.');
01090 
01091         /* If no extension or extension isn't .grf, skip the file */
01092         if (ext == NULL) continue;
01093         if (strcasecmp(ext, extension) != 0) continue;
01094       }
01095 
01096       if (fs->AddFile(filename, basepath_length)) num++;
01097     }
01098   }
01099 
01100   closedir(dir);
01101 
01102   return num;
01103 }
01104 
01111 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
01112 {
01113   uint num = 0;
01114   const char *filename = (*tar).first.c_str();
01115 
01116   if (extension != NULL) {
01117     const char *ext = strrchr(filename, '.');
01118 
01119     /* If no extension or extension isn't .grf, skip the file */
01120     if (ext == NULL) return false;
01121     if (strcasecmp(ext, extension) != 0) return false;
01122   }
01123 
01124   if (fs->AddFile(filename, 0)) num++;
01125 
01126   return num;
01127 }
01128 
01138 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
01139 {
01140   Searchpath sp;
01141   char path[MAX_PATH];
01142   TarFileList::iterator tar;
01143   uint num = 0;
01144 
01145   FOR_ALL_SEARCHPATHS(sp) {
01146     FioAppendDirectory(path, MAX_PATH, sp, sd);
01147     num += ScanPath(this, extension, path, strlen(path), recursive);
01148   }
01149 
01150   if (tars) {
01151     FOR_ALL_TARS(tar) {
01152       num += ScanTar(this, extension, tar);
01153     }
01154   }
01155 
01156   return num;
01157 }
01158 
01167 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
01168 {
01169   char path[MAX_PATH];
01170   strecpy(path, directory, lastof(path));
01171   AppendPathSeparator(path, lengthof(path));
01172   return ScanPath(this, extension, path, strlen(path), recursive);
01173 }

Generated on Wed Feb 17 23:06:46 2010 for OpenTTD by  doxygen 1.6.1