# fileio.cpp

Go to the documentation of this file.

    00001 /* $Id: fileio.cpp 15641 2009-03-07 23:02:28Z 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     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     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(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)
    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 }