fileio.cpp

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

Generated on Tue Jan 5 21:02:54 2010 for OpenTTD by  doxygen 1.5.6