fileio.cpp

Go to the documentation of this file.
00001 /* $Id: fileio.cpp 15299 2009-01-31 20:16:06Z smatz $ */
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 && (subdir == DATA_DIR || subdir == AI_DIR || subdir == AI_LIBRARY_DIR) && 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     strcpy(resolved_name, filename);
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         strcpy(resolved_name2, &(resolved_name[len]));
00380         strcpy(resolved_name, dest.c_str());
00381         strcpy(&(resolved_name[dest.length()]), resolved_name2);
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     int skip = strtol(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 (%d bytes, %d 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         strcpy(dest, name);
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 %d 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   }
00781   DEBUG(misc, 1, "Scan complete, found %d files", num);
00782 }
00783 
00784 #if defined(WIN32) || defined(WINCE)
00785 
00790 extern void DetermineBasePaths(const char *exe);
00791 #else /* defined(WIN32) || defined(WINCE) */
00792 
00800 void ChangeWorkingDirectory(const char *exe)
00801 {
00802 #ifdef WITH_COCOA
00803   char *app_bundle = strchr(exe, '.');
00804   while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
00805 
00806   if (app_bundle != NULL) app_bundle[0] = '\0';
00807 #endif /* WITH_COCOA */
00808   char *s = strrchr(exe, PATHSEPCHAR);
00809   if (s != NULL) {
00810     *s = '\0';
00811 #if defined(__DJGPP__)
00812     /* If we want to go to the root, we can't use cd C:, but we must use '/' */
00813     if (s[-1] == ':') chdir("/");
00814 #endif
00815     if (chdir(exe) != 0) DEBUG(misc, 0, "Directory with the binary does not exist?");
00816     *s = PATHSEPCHAR;
00817   }
00818 #ifdef WITH_COCOA
00819   if (app_bundle != NULL) app_bundle[0] = '.';
00820 #endif /* WITH_COCOA */
00821 }
00822 
00827 void DetermineBasePaths(const char *exe)
00828 {
00829   char tmp[MAX_PATH];
00830 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS) || !defined(WITH_PERSONAL_DIR)
00831   _searchpaths[SP_PERSONAL_DIR] = NULL;
00832 #else
00833   const char *homedir = getenv("HOME");
00834 
00835   if (homedir == NULL) {
00836     const struct passwd *pw = getpwuid(getuid());
00837     homedir = (pw == NULL) ? "" : pw->pw_dir;
00838   }
00839 
00840   snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR);
00841   AppendPathSeparator(tmp, MAX_PATH);
00842 
00843   _searchpaths[SP_PERSONAL_DIR] = strdup(tmp);
00844 #endif
00845 
00846 #if defined(WITH_SHARED_DIR)
00847   snprintf(tmp, MAX_PATH, "%s", SHARED_DIR);
00848   AppendPathSeparator(tmp, MAX_PATH);
00849   _searchpaths[SP_SHARED_DIR] = strdup(tmp);
00850 #else
00851   _searchpaths[SP_SHARED_DIR] = NULL;
00852 #endif
00853 
00854 #if defined(__MORPHOS__) || defined(__AMIGA__)
00855   _searchpaths[SP_WORKING_DIR] = NULL;
00856 #else
00857   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
00858   AppendPathSeparator(tmp, MAX_PATH);
00859   _searchpaths[SP_WORKING_DIR] = strdup(tmp);
00860 #endif
00861 
00862   /* Change the working directory to that one of the executable */
00863   ChangeWorkingDirectory((char*)exe);
00864   if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0';
00865   AppendPathSeparator(tmp, MAX_PATH);
00866   _searchpaths[SP_BINARY_DIR] = strdup(tmp);
00867 
00868 #if defined(__MORPHOS__) || defined(__AMIGA__) || defined(DOS)
00869   _searchpaths[SP_INSTALLATION_DIR] = NULL;
00870 #else
00871   snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR);
00872   AppendPathSeparator(tmp, MAX_PATH);
00873   _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp);
00874 #endif
00875 #ifdef WITH_COCOA
00876 extern void cocoaSetApplicationBundleDir();
00877   cocoaSetApplicationBundleDir();
00878 #else
00879   _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL;
00880 #endif
00881 }
00882 #endif /* defined(WIN32) || defined(WINCE) */
00883 
00884 char *_personal_dir;
00885 
00892 void DeterminePaths(const char *exe)
00893 {
00894   DetermineBasePaths(exe);
00895 
00896   Searchpath sp;
00897   FOR_ALL_SEARCHPATHS(sp) DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]);
00898 
00899   if (_config_file != NULL) {
00900     _personal_dir = strdup(_config_file);
00901     char *end = strrchr(_personal_dir , PATHSEPCHAR);
00902     if (end == NULL) {
00903       _personal_dir[0] = '\0';
00904     } else {
00905       end[1] = '\0';
00906     }
00907   } else {
00908     char personal_dir[MAX_PATH];
00909     FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg");
00910 
00911     if (FileExists(personal_dir)) {
00912       char *end = strrchr(personal_dir, PATHSEPCHAR);
00913       if (end != NULL) end[1] = '\0';
00914       _personal_dir = strdup(personal_dir);
00915       _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
00916     } else {
00917       static const Searchpath new_openttd_cfg_order[] = {
00918           SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
00919         };
00920 
00921       for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
00922         if (IsValidSearchPath(new_openttd_cfg_order[i])) {
00923           _personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]);
00924           _config_file = str_fmt("%sopenttd.cfg", _personal_dir);
00925           break;
00926         }
00927       }
00928     }
00929   }
00930 
00931   DEBUG(misc, 3, "%s found as personal directory", _personal_dir);
00932 
00933   _highscore_file = str_fmt("%shs.dat", _personal_dir);
00934   _log_file = str_fmt("%sopenttd.log",  _personal_dir);
00935 
00936   char *save_dir     = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(SAVE_DIR));
00937   char *autosave_dir = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(AUTOSAVE_DIR));
00938 
00939   /* Make the necessary folders */
00940 #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR)
00941   FioCreateDirectory(_personal_dir);
00942 #endif
00943 
00944   FioCreateDirectory(save_dir);
00945   FioCreateDirectory(autosave_dir);
00946 
00947   free(save_dir);
00948   free(autosave_dir);
00949 
00950   /* If we have network we make a directory for the autodownloading of content */
00951   _searchpaths[SP_AUTODOWNLOAD_DIR] = str_fmt("%s%s", _personal_dir, "content_download" PATHSEP);
00952 #ifdef ENABLE_NETWORK
00953   FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
00954 
00955   /* Create the directory for each of the types of content */
00956   const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, DATA_DIR, AI_DIR, AI_LIBRARY_DIR };
00957   for (uint i = 0; i < lengthof(dirs); i++) {
00958     char *tmp = str_fmt("%s%s", _searchpaths[SP_AUTODOWNLOAD_DIR], FioGetSubdirectory(dirs[i]));
00959     FioCreateDirectory(tmp);
00960     free(tmp);
00961   }
00962 #else /* ENABLE_NETWORK */
00963   /* If we don't have networking, we don't need to make the directory. But
00964    * if it exists we keep it, otherwise remove it from the search paths. */
00965   if (!FileExists(_searchpaths[SP_AUTODOWNLOAD_DIR]))  {
00966     free((void*)_searchpaths[SP_AUTODOWNLOAD_DIR]);
00967     _searchpaths[SP_AUTODOWNLOAD_DIR] = NULL;
00968   }
00969 #endif /* ENABLE_NETWORK */
00970 
00971   ScanForTarFiles();
00972 }
00973 
00978 void SanitizeFilename(char *filename)
00979 {
00980   for (; *filename != '\0'; filename++) {
00981     switch (*filename) {
00982       /* The following characters are not allowed in filenames
00983        * on at least one of the supported operating systems: */
00984       case ':': case '\\': case '*': case '?': case '/':
00985       case '<': case '>': case '|': case '"':
00986         *filename = '_';
00987         break;
00988     }
00989   }
00990 }
00991 
00992 void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize)
00993 {
00994   FILE *in;
00995   byte *mem;
00996   size_t len;
00997 
00998   in = fopen(filename, "rb");
00999   if (in == NULL) return NULL;
01000 
01001   fseek(in, 0, SEEK_END);
01002   len = ftell(in);
01003   fseek(in, 0, SEEK_SET);
01004   if (len > maxsize || (mem = MallocT<byte>(len + 1)) == NULL) {
01005     fclose(in);
01006     return NULL;
01007   }
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)
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 (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
01050       AppendPathSeparator(filename, lengthof(filename));
01051       num += ScanPath(fs, extension, filename, basepath_length);
01052     } else if (S_ISREG(sb.st_mode)) {
01053       /* File */
01054       char *ext = strrchr(filename, '.');
01055 
01056       /* If no extension or extension isn't .grf, skip the file */
01057       if (ext == NULL) continue;
01058       if (strcasecmp(ext, extension) != 0) continue;
01059 
01060       if (fs->AddFile(filename, basepath_length)) num++;
01061     }
01062   }
01063 
01064   closedir(dir);
01065 
01066   return num;
01067 }
01068 
01074 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
01075 {
01076   uint num = 0;
01077   const char *filename = (*tar).first.c_str();
01078   const char *ext = strrchr(filename, '.');
01079 
01080   /* If no extension or extension isn't .grf, skip the file */
01081   if (ext == NULL) return false;
01082   if (strcasecmp(ext, extension) != 0) return false;
01083 
01084   if (fs->AddFile(filename, 0)) num++;
01085 
01086   return num;
01087 }
01088 
01097 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars)
01098 {
01099   Searchpath sp;
01100   char path[MAX_PATH];
01101   TarFileList::iterator tar;
01102   uint num = 0;
01103 
01104   FOR_ALL_SEARCHPATHS(sp) {
01105     FioAppendDirectory(path, MAX_PATH, sp, sd);
01106     num += ScanPath(this, extension, path, strlen(path));
01107   }
01108 
01109   if (tars) {
01110     FOR_ALL_TARS(tar) {
01111       num += ScanTar(this, extension, tar);
01112     }
01113   }
01114 
01115   return num;
01116 }
01117 
01125 uint FileScanner::Scan(const char *extension, const char *directory)
01126 {
01127   char path[MAX_PATH];
01128   strecpy(path, directory, lastof(path));
01129   AppendPathSeparator(path, lengthof(path));
01130   return ScanPath(this, extension, path, strlen(path));
01131 }

Generated on Mon Feb 16 23:12:06 2009 for openttd by  doxygen 1.5.6