# fileio.cpp

Go to the documentation of this file.

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