fileio.cpp

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

Generated on Mon Jun 8 23:04:03 2009 for OpenTTD by  doxygen 1.5.6