tron@2186: /* $Id$ */ tron@2186: belugas@6527: /** @file fileio.cpp Standard In/Out file operations */ belugas@6505: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@1093: #include "fileio.h" tron@2153: #include "variables.h" truelight@6218: #include "debug.h" rubidium@6624: #include "fios.h" rubidium@8626: #include "core/alloc_func.hpp" rubidium@8627: #include "core/math_func.hpp" rubidium@8710: #include "string_func.h" smatz@11061: #include "tar_type.h" rubidium@7425: #ifdef WIN32 rubidium@7425: #include rubidium@7425: #else rubidium@6649: #include rubidium@6643: #include glx@8085: #endif rubidium@6624: #include truelight@0: truelight@0: /*************************************************/ truelight@0: /* FILE IO ROUTINES ******************************/ truelight@0: /*************************************************/ truelight@0: truelight@0: #define FIO_BUFFER_SIZE 512 truelight@0: rubidium@6574: struct Fio { rubidium@8301: byte *buffer, *buffer_end; ///< position pointer in local buffer and last valid byte of buffer glx@10465: size_t pos; ///< current (system) position in file rubidium@8301: FILE *cur_fh; ///< current file handle rubidium@8301: const char *filename; ///< current filename rubidium@8301: FILE *handles[MAX_FILE_SLOTS]; ///< array of file handles we can have open rubidium@8301: byte buffer_start[FIO_BUFFER_SIZE]; ///< local buffer when read from file rubidium@8301: const char *filenames[MAX_FILE_SLOTS]; ///< array of filenames we (should) have open peter1138@8870: char *shortnames[MAX_FILE_SLOTS];///< array of short names for spriteloader's use truelight@6218: #if defined(LIMITED_FDS) rubidium@8301: uint open_handles; ///< current amount of open handles rubidium@8301: uint usage_count[MAX_FILE_SLOTS]; ///< count how many times this file has been opened truelight@6218: #endif /* LIMITED_FDS */ rubidium@6574: }; truelight@0: truelight@0: static Fio _fio; truelight@0: belugas@6505: /* Get current position in file */ rubidium@10751: size_t FioGetPos() truelight@0: { rubidium@10421: return _fio.pos + (_fio.buffer - _fio.buffer_end); truelight@0: } truelight@0: peter1138@8870: const char *FioGetFilename(uint8 slot) truelight@7392: { peter1138@8870: return _fio.shortnames[slot]; truelight@7392: } truelight@7392: rubidium@10751: void FioSeekTo(size_t pos, int mode) truelight@0: { truelight@0: if (mode == SEEK_CUR) pos += FioGetPos(); truelight@0: _fio.buffer = _fio.buffer_end = _fio.buffer_start + FIO_BUFFER_SIZE; Darkvater@4203: _fio.pos = pos; Darkvater@4203: fseek(_fio.cur_fh, _fio.pos, SEEK_SET); truelight@0: } truelight@0: truelight@6218: #if defined(LIMITED_FDS) truelight@6218: static void FioRestoreFile(int slot) truelight@6218: { truelight@6218: /* Do we still have the file open, or should we reopen it? */ truelight@6218: if (_fio.handles[slot] == NULL) { truelight@7848: DEBUG(misc, 6, "Restoring file '%s' in slot '%d' from disk", _fio.filenames[slot], slot); truelight@7848: FioOpenFile(slot, _fio.filenames[slot]); truelight@6218: } truelight@6218: _fio.usage_count[slot]++; truelight@6218: } truelight@6218: #endif /* LIMITED_FDS */ truelight@6218: belugas@6505: /* Seek to a file and a position */ smatz@10753: void FioSeekToFile(uint8 slot, size_t pos) truelight@0: { truelight@6218: FILE *f; truelight@6218: #if defined(LIMITED_FDS) truelight@6218: /* Make sure we have this file open */ truelight@8066: FioRestoreFile(slot); truelight@6218: #endif /* LIMITED_FDS */ truelight@8066: f = _fio.handles[slot]; truelight@0: assert(f != NULL); truelight@0: _fio.cur_fh = f; truelight@8066: _fio.filename = _fio.filenames[slot]; truelight@8066: FioSeekTo(pos, SEEK_SET); truelight@0: } truelight@0: rubidium@6573: byte FioReadByte() truelight@0: { truelight@0: if (_fio.buffer == _fio.buffer_end) { rubidium@10418: _fio.buffer = _fio.buffer_start; rubidium@10421: size_t size = fread(_fio.buffer, 1, FIO_BUFFER_SIZE, _fio.cur_fh); rubidium@10421: _fio.pos += size; rubidium@10421: _fio.buffer_end = _fio.buffer_start + size; rubidium@10421: rubidium@10421: if (size == 0) return 0; truelight@0: } truelight@0: return *_fio.buffer++; truelight@0: } truelight@0: truelight@0: void FioSkipBytes(int n) truelight@0: { tron@2952: for (;;) { truelight@0: int m = min(_fio.buffer_end - _fio.buffer, n); truelight@0: _fio.buffer += m; truelight@0: n -= m; truelight@0: if (n == 0) break; truelight@0: FioReadByte(); truelight@0: n--; truelight@0: } truelight@0: } truelight@0: rubidium@6573: uint16 FioReadWord() truelight@0: { truelight@0: byte b = FioReadByte(); truelight@0: return (FioReadByte() << 8) | b; truelight@0: } truelight@0: rubidium@6573: uint32 FioReadDword() truelight@0: { truelight@0: uint b = FioReadWord(); truelight@0: return (FioReadWord() << 16) | b; truelight@0: } truelight@0: glx@10465: void FioReadBlock(void *ptr, size_t size) truelight@0: { truelight@0: FioSeekTo(FioGetPos(), SEEK_SET); rubidium@10418: _fio.pos += fread(ptr, 1, size, _fio.cur_fh); truelight@0: } truelight@0: darkvater@1039: static inline void FioCloseFile(int slot) darkvater@1039: { darkvater@1039: if (_fio.handles[slot] != NULL) { tron@1109: fclose(_fio.handles[slot]); peter1138@8870: peter1138@8870: free(_fio.shortnames[slot]); peter1138@8870: _fio.shortnames[slot] = NULL; peter1138@8870: darkvater@1039: _fio.handles[slot] = NULL; truelight@6218: #if defined(LIMITED_FDS) truelight@6218: _fio.open_handles--; truelight@6218: #endif /* LIMITED_FDS */ darkvater@1039: } darkvater@1039: } darkvater@1039: rubidium@6573: void FioCloseAll() truelight@0: { truelight@0: int i; truelight@0: darkvater@1039: for (i = 0; i != lengthof(_fio.handles); i++) darkvater@1039: FioCloseFile(i); truelight@0: } truelight@0: truelight@6218: #if defined(LIMITED_FDS) truelight@6218: static void FioFreeHandle() truelight@6218: { truelight@6218: /* If we are about to open a file that will exceed the limit, close a file */ truelight@6218: if (_fio.open_handles + 1 == LIMITED_FDS) { truelight@6218: uint i, count; truelight@6218: int slot; truelight@6218: truelight@6218: count = UINT_MAX; truelight@6218: slot = -1; truelight@6218: /* Find the file that is used the least */ truelight@6218: for (i = 0; i < lengthof(_fio.handles); i++) { truelight@6218: if (_fio.handles[i] != NULL && _fio.usage_count[i] < count) { truelight@6218: count = _fio.usage_count[i]; truelight@6218: slot = i; truelight@6218: } truelight@6218: } truelight@6218: assert(slot != -1); truelight@7848: DEBUG(misc, 6, "Closing filehandler '%s' in slot '%d' because of fd-limit", _fio.filenames[slot], slot); truelight@6218: FioCloseFile(slot); truelight@6218: } truelight@6218: } truelight@6218: #endif /* LIMITED_FDS */ truelight@6218: rubidium@6625: void FioOpenFile(int slot, const char *filename) rubidium@6625: { rubidium@6625: FILE *f; rubidium@6625: rubidium@6625: #if defined(LIMITED_FDS) rubidium@6625: FioFreeHandle(); rubidium@6625: #endif /* LIMITED_FDS */ rubidium@6625: f = FioFOpenFile(filename); glx@10839: if (f == NULL) usererror("Cannot open file '%s'", filename); truelight@8066: uint32 pos = ftell(f); rubidium@6625: rubidium@6625: FioCloseFile(slot); // if file was opened before, close it rubidium@6625: _fio.handles[slot] = f; truelight@7392: _fio.filenames[slot] = filename; peter1138@8870: peter1138@8870: /* Store the filename without path and extension */ peter1138@8870: const char *t = strrchr(filename, PATHSEPCHAR); peter1138@8870: _fio.shortnames[slot] = strdup(t == NULL ? filename : t); peter1138@8870: char *t2 = strrchr(_fio.shortnames[slot], '.'); peter1138@8870: if (t2 != NULL) *t2 = '\0'; peter1138@8870: strtolower(_fio.shortnames[slot]); peter1138@8870: rubidium@6625: #if defined(LIMITED_FDS) rubidium@6625: _fio.usage_count[slot] = 0; rubidium@6625: _fio.open_handles++; rubidium@6625: #endif /* LIMITED_FDS */ truelight@8066: FioSeekToFile(slot, pos); rubidium@6625: } rubidium@6625: rubidium@7425: const char *_subdirs[NUM_SUBDIRS] = { rubidium@7425: "", rubidium@7425: "save" PATHSEP, rubidium@7425: "save" PATHSEP "autosave" PATHSEP, rubidium@7425: "scenario" PATHSEP, rubidium@7425: "scenario" PATHSEP "heightmap" PATHSEP, rubidium@7425: "gm" PATHSEP, rubidium@7425: "data" PATHSEP, terom@11179: "lang" PATHSEP, terom@11179: "cache" PATHSEP rubidium@7425: }; rubidium@7425: rubidium@7425: const char *_searchpaths[NUM_SEARCHPATHS]; truelight@8088: TarList _tar_list; truelight@8088: TarFileList _tar_filelist; rubidium@7425: frosch@11139: typedef std::map TarLinkList; frosch@11139: static TarLinkList _tar_linklist; ///< List of directory links frosch@11139: rubidium@6625: /** rubidium@6625: * Check whether the given file exists rubidium@6625: * @param filename the file to try for existance rubidium@7425: * @param subdir the subdirectory to look in rubidium@6625: * @return true if and only if the file can be opened rubidium@6625: */ rubidium@7425: bool FioCheckFileExists(const char *filename, Subdirectory subdir) rubidium@6625: { rubidium@7425: FILE *f = FioFOpenFile(filename, "rb", subdir); rubidium@6625: if (f == NULL) return false; rubidium@6625: truelight@8088: FioFCloseFile(f); truelight@8088: return true; truelight@8088: } truelight@8088: truelight@8088: /** truelight@8088: * Close a file in a safe way. truelight@8088: */ truelight@8088: void FioFCloseFile(FILE *f) truelight@8088: { rubidium@6625: fclose(f); rubidium@6625: } rubidium@6625: rubidium@7425: char *FioGetFullPath(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir, const char *filename) bjarni@2736: { rubidium@7425: assert(subdir < NUM_SUBDIRS); rubidium@7425: assert(sp < NUM_SEARCHPATHS); bjarni@2736: rubidium@7437: snprintf(buf, buflen, "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename); rubidium@7425: return buf; rubidium@7425: } rubidium@7425: rubidium@7425: char *FioFindFullPath(char *buf, size_t buflen, Subdirectory subdir, const char *filename) rubidium@7425: { rubidium@7425: Searchpath sp; rubidium@7425: assert(subdir < NUM_SUBDIRS); rubidium@7425: rubidium@7425: FOR_ALL_SEARCHPATHS(sp) { rubidium@7425: FioGetFullPath(buf, buflen, sp, subdir, filename); rubidium@7425: if (FileExists(buf)) break; rubidium@6920: } bjarni@2736: rubidium@7425: return buf; rubidium@7425: } rubidium@7425: rubidium@7425: char *FioAppendDirectory(char *buf, size_t buflen, Searchpath sp, Subdirectory subdir) rubidium@7425: { rubidium@7425: assert(subdir < NUM_SUBDIRS); rubidium@7425: assert(sp < NUM_SEARCHPATHS); rubidium@7425: rubidium@7425: snprintf(buf, buflen, "%s%s", _searchpaths[sp], _subdirs[subdir]); rubidium@7425: return buf; rubidium@7425: } rubidium@7425: rubidium@7425: char *FioGetDirectory(char *buf, size_t buflen, Subdirectory subdir) rubidium@7425: { rubidium@7425: Searchpath sp; rubidium@7425: rubidium@7425: /* Find and return the first valid directory */ rubidium@7425: FOR_ALL_SEARCHPATHS(sp) { rubidium@7425: char *ret = FioAppendDirectory(buf, buflen, sp, subdir); rubidium@7425: if (FileExists(buf)) return ret; rubidium@7425: } rubidium@7425: rubidium@7425: /* Could not find the directory, fall back to a base path */ rubidium@7425: ttd_strlcpy(buf, _personal_dir, buflen); rubidium@7425: rubidium@7425: return buf; rubidium@7425: } rubidium@7425: truelight@8070: FILE *FioFOpenFileSp(const char *filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize) rubidium@7425: { rubidium@7425: #if defined(WIN32) && defined(UNICODE) rubidium@7425: /* fopen is implemented as a define with ellipses for rubidium@7425: * Unicode support (prepend an L). As we are not sending rubidium@7425: * a string, but a variable, it 'renames' the variable, rubidium@7425: * so make that variable to makes it compile happily */ rubidium@7425: wchar_t Lmode[5]; rubidium@7425: MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode)); rubidium@7425: #endif rubidium@7425: FILE *f = NULL; rubidium@7425: char buf[MAX_PATH]; rubidium@7425: rubidium@7431: if (subdir == NO_DIRECTORY) { rubidium@7425: ttd_strlcpy(buf, filename, lengthof(buf)); rubidium@7425: } else { rubidium@7425: snprintf(buf, lengthof(buf), "%s%s%s", _searchpaths[sp], _subdirs[subdir], filename); rubidium@7425: } rubidium@7425: rubidium@8120: #if defined(WIN32) glx@8124: if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf)) == INVALID_FILE_ATTRIBUTES) return NULL; rubidium@8120: #endif rubidium@8120: rubidium@7425: f = fopen(buf, mode); bjarni@2736: #if !defined(WIN32) bjarni@2736: if (f == NULL) { rubidium@10401: strtolower(buf + ((subdir == NO_DIRECTORY) ? 0 : strlen(_searchpaths[sp]) - 1)); rubidium@7425: f = fopen(buf, mode); bjarni@2736: } bjarni@2736: #endif truelight@8070: if (f != NULL && filesize != NULL) { truelight@8070: /* Find the size of the file */ truelight@8070: fseek(f, 0, SEEK_END); truelight@8070: *filesize = ftell(f); truelight@8070: fseek(f, 0, SEEK_SET); truelight@8070: } rubidium@7425: return f; rubidium@7425: } rubidium@7425: truelight@8088: FILE *FioFOpenFileTar(TarFileListEntry *entry, size_t *filesize) truelight@8077: { truelight@8088: FILE *f = fopen(entry->tar->filename, "rb"); truelight@8077: assert(f != NULL); truelight@8077: truelight@8088: fseek(f, entry->position, SEEK_SET); truelight@8088: if (filesize != NULL) *filesize = entry->size; truelight@8088: return f; truelight@8077: } truelight@8077: rubidium@7425: /** Opens OpenTTD files somewhere in a personal or global directory */ truelight@8071: FILE *FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize) rubidium@7425: { rubidium@7425: FILE *f = NULL; rubidium@7425: Searchpath sp; rubidium@7425: rubidium@7431: assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY); rubidium@7425: rubidium@7425: FOR_ALL_SEARCHPATHS(sp) { truelight@8071: f = FioFOpenFileSp(filename, mode, sp, subdir, filesize); rubidium@7431: if (f != NULL || subdir == NO_DIRECTORY) break; rubidium@7425: } truelight@8088: truelight@8077: /* We can only use .tar in case of data-dir, and read-mode */ truelight@8077: if (f == NULL && subdir == DATA_DIR && mode[0] == 'r') { frosch@11139: static const uint MAX_RESOLVED_LENGTH = 2 * (100 + 100 + 155) + 1; // Enough space to hold two filenames plus link. See 'TarHeader'. frosch@11139: char resolved_name[MAX_RESOLVED_LENGTH]; frosch@11139: truelight@8088: /* Filenames in tars are always forced to be lowercase */ frosch@11139: strcpy(resolved_name, filename); frosch@11139: strtolower(resolved_name); frosch@11139: frosch@11139: uint resolved_len = strlen(resolved_name); frosch@11139: frosch@11139: /* Resolve ONE directory link */ frosch@11139: for (TarLinkList::iterator link = _tar_linklist.begin(); link != _tar_linklist.end(); link++) { frosch@11139: const std::string &src = link->first; frosch@11139: uint len = src.length(); frosch@11140: if (resolved_len >= len && resolved_name[len - 1] == PATHSEPCHAR && strncmp(src.c_str(), resolved_name, len) == 0) { frosch@11139: /* Apply link */ frosch@11139: char resolved_name2[MAX_RESOLVED_LENGTH]; frosch@11139: const std::string &dest = link->second; frosch@11139: strcpy(resolved_name2, &(resolved_name[len])); frosch@11139: strcpy(resolved_name, dest.c_str()); frosch@11139: strcpy(&(resolved_name[dest.length()]), resolved_name2); frosch@11139: break; // Only resolve one level frosch@11139: } frosch@11139: } frosch@11139: frosch@11139: TarFileList::iterator it = _tar_filelist.find(resolved_name); truelight@8088: if (it != _tar_filelist.end()) { truelight@8088: f = FioFOpenFileTar(&((*it).second), filesize); truelight@8077: } truelight@8077: } bjarni@2736: rubidium@8505: /* Sometimes a full path is given. To support rubidium@8505: * the 'subdirectory' must be 'removed'. */ rubidium@8505: if (f == NULL && subdir != NO_DIRECTORY) { rubidium@8505: f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize); rubidium@8505: } rubidium@8505: bjarni@2736: return f; bjarni@2736: } bjarni@2736: rubidium@6624: /** rubidium@6624: * Create a directory with the given name rubidium@6624: * @param name the new name of the directory rubidium@6624: */ rubidium@6624: void FioCreateDirectory(const char *name) rubidium@6624: { rubidium@6624: #if defined(WIN32) || defined(WINCE) rubidium@6624: CreateDirectory(OTTD2FS(name), NULL); rubidium@6624: #elif defined(OS2) && !defined(__INNOTEK_LIBC__) rubidium@6624: mkdir(OTTD2FS(name)); rubidium@8407: #elif defined(__MORPHOS__) || defined(__AMIGAOS__) rubidium@8407: char buf[MAX_PATH]; rubidium@8407: ttd_strlcpy(buf, name, MAX_PATH); rubidium@8407: rubidium@8407: size_t len = strlen(name) - 1; rubidium@8407: if (buf[len] == '/') { rubidium@8407: buf[len] = '\0'; // Kill pathsep, so mkdir() will not fail rubidium@8407: } rubidium@8407: rubidium@8407: mkdir(OTTD2FS(buf), 0755); rubidium@6624: #else rubidium@6624: mkdir(OTTD2FS(name), 0755); rubidium@6624: #endif rubidium@6624: } rubidium@6624: rubidium@6624: /** rubidium@6624: * Appends, if necessary, the path separator character to the end of the string. rubidium@6624: * It does not add the path separator to zero-sized strings. rubidium@6624: * @param buf string to append the separator to rubidium@6624: * @param buflen the length of the buf rubidium@6624: */ rubidium@6624: void AppendPathSeparator(char *buf, size_t buflen) rubidium@6624: { rubidium@6624: size_t s = strlen(buf); rubidium@6624: rubidium@6624: /* Length of string + path separator + '\0' */ rubidium@6624: if (s != 0 && buf[s - 1] != PATHSEPCHAR && s + 2 < buflen) { rubidium@6625: buf[s] = PATHSEPCHAR; rubidium@6625: buf[s + 1] = '\0'; rubidium@6624: } rubidium@6624: } rubidium@6624: rubidium@7330: /** rubidium@7330: * Allocates and files a variable with the full path rubidium@7330: * based on the given directory. rubidium@7330: * @param dir the directory to base the path on rubidium@7330: * @return the malloced full path rubidium@7330: */ rubidium@7330: char *BuildWithFullPath(const char *dir) rubidium@7330: { rubidium@7330: char *dest = MallocT(MAX_PATH); rubidium@7330: ttd_strlcpy(dest, dir, MAX_PATH); rubidium@7330: rubidium@7330: /* Check if absolute or relative path */ rubidium@7330: const char *s = strchr(dest, PATHSEPCHAR); rubidium@7330: rubidium@7330: /* Add absolute path */ rubidium@7330: if (s == NULL || dest != s) { rubidium@10418: if (getcwd(dest, MAX_PATH) == NULL) *dest = '\0'; rubidium@7330: AppendPathSeparator(dest, MAX_PATH); rubidium@7330: ttd_strlcat(dest, dir, MAX_PATH); rubidium@7330: } rubidium@7330: AppendPathSeparator(dest, MAX_PATH); rubidium@7330: rubidium@7330: return dest; rubidium@7330: } rubidium@7330: frosch@11139: /** frosch@11139: * Simplify filenames from tars. frosch@11139: * Replace '/' by PATHSEPCHAR, and force 'name' to lowercase. frosch@11139: * @param name Filename to process. frosch@11139: */ frosch@11139: static void SimplifyFileName(char *name) frosch@11139: { frosch@11139: /* Force lowercase */ frosch@11139: strtolower(name); frosch@11139: frosch@11139: /* Tar-files always have '/' path-seperator, but we want our PATHSEPCHAR */ frosch@11139: #if (PATHSEPCHAR != '/') frosch@11139: for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR; frosch@11139: #endif frosch@11139: } frosch@11139: terom@11180: static bool TarListAddFile(const char *filename, Subdirectory subdir) truelight@8077: { truelight@8088: /* The TAR-header, repeated for every file */ truelight@8088: typedef struct TarHeader { truelight@8088: char name[100]; ///< Name of the file truelight@8088: char mode[8]; truelight@8088: char uid[8]; truelight@8088: char gid[8]; truelight@8088: char size[12]; ///< Size of the file, in ASCII truelight@8088: char mtime[12]; truelight@8088: char chksum[8]; truelight@8088: char typeflag; truelight@8088: char linkname[100]; truelight@8088: char magic[6]; truelight@8088: char version[2]; truelight@8088: char uname[32]; truelight@8088: char gname[32]; truelight@8088: char devmajor[8]; truelight@8088: char devminor[8]; truelight@8088: char prefix[155]; ///< Path of the file truelight@8088: truelight@8088: char unused[12]; truelight@8088: } TarHeader; truelight@8088: truelight@8088: /* Check if we already seen this file */ truelight@8088: TarList::iterator it = _tar_list.find(filename); truelight@8088: if (it != _tar_list.end()) return false; truelight@8088: truelight@8088: FILE *f = fopen(filename, "rb"); truelight@8088: assert(f != NULL); truelight@8088: truelight@8088: TarListEntry *tar_entry = MallocT(1); truelight@8088: tar_entry->filename = strdup(filename); terom@11180: tar_entry->subdir = subdir; truelight@8088: _tar_list.insert(TarList::value_type(filename, tar_entry)); truelight@8088: frosch@11139: TarLinkList links; ///< Temporary list to collect links frosch@11139: truelight@8088: TarHeader th; truelight@8088: char buf[sizeof(th.name) + 1], *end; truelight@8088: char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1]; frosch@11139: char link[sizeof(th.linkname) + 1]; frosch@11139: char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1]; glx@10465: size_t num = 0, pos = 0; truelight@8088: truelight@8088: /* Make a char of 512 empty bytes */ truelight@8088: char empty[512]; truelight@8088: memset(&empty[0], 0, sizeof(empty)); truelight@8088: frosch@11135: for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it? frosch@11135: size_t num_bytes_read = fread(&th, 1, 512, f); frosch@11135: if (num_bytes_read != 512) break; frosch@11135: pos += num_bytes_read; truelight@8088: truelight@8088: /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */ truelight@8088: if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) { truelight@8088: /* If we have only zeros in the block, it can be an end-of-file indicator */ truelight@8088: if (memcmp(&th, &empty[0], 512) == 0) continue; truelight@8088: truelight@8088: DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename); truelight@8088: return false; truelight@8088: } truelight@8088: truelight@8088: name[0] = '\0'; glx@10465: size_t len = 0; truelight@8088: truelight@8088: /* The prefix contains the directory-name */ truelight@8088: if (th.prefix[0] != '\0') { truelight@8088: memcpy(name, th.prefix, sizeof(th.prefix)); truelight@8088: name[sizeof(th.prefix)] = '\0'; truelight@8088: len = strlen(name); truelight@8088: name[len] = PATHSEPCHAR; truelight@8088: len++; truelight@8088: } truelight@8088: truelight@8088: /* Copy the name of the file in a safe way at the end of 'name' */ truelight@8088: memcpy(&name[len], th.name, sizeof(th.name)); truelight@8088: name[len + sizeof(th.name)] = '\0'; truelight@8088: truelight@8088: /* Calculate the size of the file.. for some strange reason this is stored as a string */ truelight@8088: memcpy(buf, th.size, sizeof(th.size)); truelight@8088: buf[sizeof(th.size)] = '\0'; truelight@8088: int skip = strtol(buf, &end, 8); truelight@8088: frosch@11139: switch (th.typeflag) { frosch@11139: case '\0': frosch@11139: case '0': { // regular file frosch@11139: /* Ignore empty files */ frosch@11139: if (skip == 0) break; truelight@8088: frosch@11139: if (strlen(name) == 0) break; truelight@8088: frosch@11139: /* Store this entry in the list */ frosch@11139: TarFileListEntry entry; frosch@11139: entry.tar = tar_entry; frosch@11139: entry.size = skip; frosch@11139: entry.position = pos; truelight@8088: frosch@11139: /* Convert to lowercase and our PATHSEPCHAR */ frosch@11139: SimplifyFileName(name); frosch@11139: frosch@11139: DEBUG(misc, 6, "Found file in tar: %s (%d bytes, %d offset)", name, skip, pos); frosch@11139: if (_tar_filelist.insert(TarFileList::value_type(name, entry)).second) num++; frosch@11139: frosch@11139: break; frosch@11139: } frosch@11139: frosch@11139: case '1': // hard links frosch@11139: case '2': { // symbolic links frosch@11139: /* Copy the destination of the link in a safe way at the end of 'linkname' */ frosch@11139: memcpy(link, th.linkname, sizeof(th.linkname)); frosch@11139: link[sizeof(th.linkname)] = '\0'; frosch@11139: frosch@11139: if (strlen(name) == 0 || strlen(link) == 0) break; frosch@11139: frosch@11139: /* Convert to lowercase and our PATHSEPCHAR */ frosch@11139: SimplifyFileName(name); frosch@11139: SimplifyFileName(link); frosch@11139: frosch@11139: /* Only allow relative links */ frosch@11139: if (link[0] == PATHSEPCHAR) { frosch@11139: DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link); frosch@11139: break; frosch@11139: } frosch@11139: frosch@11139: /* Process relative path. frosch@11139: * Note: The destination of links must not contain any directory-links. */ frosch@11139: strcpy(dest, name); frosch@11139: char *destpos = strrchr(dest, PATHSEPCHAR); frosch@11139: if (destpos == NULL) destpos = dest; frosch@11139: *destpos = '\0'; frosch@11139: frosch@11139: char *pos = link; frosch@11139: while (*pos != '\0') { frosch@11139: char *next = strchr(link, PATHSEPCHAR); frosch@11139: if (next == NULL) next = pos + strlen(pos); frosch@11139: frosch@11139: /* Skip '.' (current dir) */ frosch@11139: if (next != pos + 1 || pos[0] != '.') { frosch@11139: if (next == pos + 2 && pos[0] == '.' && pos[1] == '.') { frosch@11139: /* level up */ frosch@11139: if (dest[0] == '\0') { frosch@11139: DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link); frosch@11139: break; frosch@11139: } frosch@11139: frosch@11139: /* Truncate 'dest' after last PATHSEPCHAR. frosch@11139: * This assumes, that the truncated part is a real directory and not a link */ frosch@11139: destpos = strrchr(dest, PATHSEPCHAR); frosch@11139: if (destpos == NULL) destpos = dest; frosch@11139: } else { frosch@11139: /* Append at end of 'dest' */ frosch@11139: if (destpos != dest) *(destpos++) = PATHSEPCHAR; frosch@11139: strncpy(destpos, pos, next - pos); frosch@11139: destpos += next - pos; frosch@11139: } frosch@11139: *destpos = '\0'; frosch@11139: } frosch@11139: frosch@11139: pos = next; frosch@11139: } frosch@11139: frosch@11139: /* Store links in temporary list */ frosch@11139: DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest); frosch@11139: links.insert(TarLinkList::value_type(name, dest)); frosch@11139: frosch@11139: break; frosch@11139: } frosch@11139: frosch@11139: default: frosch@11139: /* Ignore other types */ frosch@11139: break; frosch@11139: } truelight@8088: truelight@8088: /* Skip to the next block.. */ skidd13@8423: skip = Align(skip, 512); truelight@8088: fseek(f, skip, SEEK_CUR); truelight@8088: pos += skip; truelight@8077: } truelight@8077: truelight@8088: DEBUG(misc, 1, "Found tar '%s' with %d new files", filename, num); truelight@8088: fclose(f); truelight@8077: frosch@11139: /* Resolve file links and store directory links. frosch@11139: * We restrict usage of links to two cases: frosch@11139: * 1) Links to directories: frosch@11139: * Both the source path and the destination path must NOT contain any further links. frosch@11139: * When resolving files at most one directory link is resolved. frosch@11139: * 2) Links to files: frosch@11139: * The destination path must NOT contain any links. frosch@11139: * The source path may contain one directory link. frosch@11139: */ frosch@11139: for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) { frosch@11139: const std::string &src = link->first; frosch@11139: const std::string &dest = link->second; frosch@11139: frosch@11139: TarFileList::iterator dest_file = _tar_filelist.find(dest); frosch@11139: if (dest_file != _tar_filelist.end()) { frosch@11139: /* Link to file. Process the link like the destination file. */ frosch@11139: _tar_filelist.insert(TarFileList::value_type(src, dest_file->second)); frosch@11139: } else { frosch@11139: /* Destination file not found. Assume 'link to directory' */ frosch@11139: /* Append PATHSEPCHAR to 'src' and 'dest' */ frosch@11139: const std::string src_path = src + PATHSEPCHAR; frosch@11139: const std::string dst_path = (dest.length() == 0 ? "" : dest + PATHSEPCHAR); frosch@11139: _tar_linklist.insert(TarLinkList::value_type(src_path, dst_path)); frosch@11139: } frosch@11139: } frosch@11139: truelight@8077: return true; truelight@8077: } truelight@8077: terom@11180: static int ScanPathForTarFiles(const char *path, size_t basepath_length, Subdirectory subdir) truelight@8077: { truelight@8077: extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb); truelight@8077: truelight@8077: uint num = 0; truelight@8077: struct stat sb; truelight@8077: struct dirent *dirent; truelight@8077: DIR *dir; truelight@8077: truelight@8077: if (path == NULL || (dir = ttd_opendir(path)) == NULL) return 0; truelight@8077: truelight@8077: while ((dirent = readdir(dir)) != NULL) { truelight@8077: const char *d_name = FS2OTTD(dirent->d_name); truelight@8077: char filename[MAX_PATH]; truelight@8077: truelight@8077: if (!FiosIsValidFile(path, dirent, &sb)) continue; truelight@8077: truelight@8077: snprintf(filename, lengthof(filename), "%s%s", path, d_name); truelight@8077: truelight@8077: if (sb.st_mode & S_IFDIR) { truelight@8077: /* Directory */ truelight@8077: if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue; truelight@8077: AppendPathSeparator(filename, lengthof(filename)); terom@11180: num += ScanPathForTarFiles(filename, basepath_length, subdir); truelight@8077: } else if (sb.st_mode & S_IFREG) { truelight@8077: /* File */ truelight@8077: char *ext = strrchr(filename, '.'); truelight@8077: truelight@8077: /* If no extension or extension isn't .tar, skip the file */ truelight@8077: if (ext == NULL) continue; truelight@8077: if (strcasecmp(ext, ".tar") != 0) continue; truelight@8077: terom@11180: if (TarListAddFile(filename, subdir)) num++; truelight@8077: } truelight@8077: } truelight@8077: truelight@8077: closedir(dir); truelight@8077: return num; truelight@8077: } truelight@8077: glx@8085: void ScanForTarFiles() truelight@8077: { truelight@8077: Searchpath sp; truelight@8077: char path[MAX_PATH]; truelight@8077: uint num = 0; truelight@8077: truelight@8077: DEBUG(misc, 1, "Scanning for tars"); truelight@8077: FOR_ALL_SEARCHPATHS(sp) { truelight@8077: FioAppendDirectory(path, MAX_PATH, sp, DATA_DIR); terom@11180: num += ScanPathForTarFiles(path, strlen(path), DATA_DIR); terom@11180: terom@11180: FioAppendDirectory(path, MAX_PATH, sp, CACHE_DIR); terom@11180: num += ScanPathForTarFiles(path, strlen(path), CACHE_DIR); truelight@8077: } truelight@8077: DEBUG(misc, 1, "Scan complete, found %d files", num); truelight@8077: } truelight@8077: glx@8085: #if defined(WIN32) || defined(WINCE) glx@8085: /** glx@8085: * Determine the base (personal dir and game data dir) paths glx@8085: * @param exe the path from the current path to the executable glx@8085: * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc) glx@8085: */ glx@8085: extern void DetermineBasePaths(const char *exe); glx@8085: #else /* defined(WIN32) || defined(WINCE) */ glx@8085: glx@8085: /** glx@8085: * Changes the working directory to the path of the give executable. glx@8085: * For OSX application bundles '.app' is the required extension of the bundle, glx@8085: * so when we crop the path to there, when can remove the name of the bundle glx@8085: * in the same way we remove the name from the executable name. glx@8085: * @param exe the path to the executable glx@8085: */ glx@8085: void ChangeWorkingDirectory(const char *exe) glx@8085: { glx@8085: #ifdef WITH_COCOA glx@8085: char *app_bundle = strchr(exe, '.'); glx@8085: while (app_bundle != NULL && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.'); glx@8085: glx@8085: if (app_bundle != NULL) app_bundle[0] = '\0'; glx@8085: #endif /* WITH_COCOA */ glx@8085: char *s = strrchr(exe, PATHSEPCHAR); glx@8085: if (s != NULL) { glx@8085: *s = '\0'; rubidium@10418: if (chdir(exe) != 0) DEBUG(misc, 0, "Directory with the binary does not exist?"); glx@8085: *s = PATHSEPCHAR; glx@8085: } glx@8085: #ifdef WITH_COCOA glx@8085: if (app_bundle != NULL) app_bundle[0] = '.'; glx@8085: #endif /* WITH_COCOA */ glx@8085: } glx@8085: rubidium@6643: /** rubidium@6643: * Determine the base (personal dir and game data dir) paths rubidium@6643: * @param exe the path to the executable rubidium@6643: */ rubidium@6643: void DetermineBasePaths(const char *exe) rubidium@6643: { rubidium@7425: char tmp[MAX_PATH]; rubidium@7486: #if defined(__MORPHOS__) || defined(__AMIGA__) || !defined(WITH_PERSONAL_DIR) rubidium@7486: _searchpaths[SP_PERSONAL_DIR] = NULL; rubidium@7486: #else rubidium@6643: const char *homedir = getenv("HOME"); rubidium@6643: rubidium@6643: if (homedir == NULL) { rubidium@6643: const struct passwd *pw = getpwuid(getuid()); rubidium@7425: homedir = (pw == NULL) ? "" : pw->pw_dir; rubidium@6643: } rubidium@6643: rubidium@7425: snprintf(tmp, MAX_PATH, "%s" PATHSEP "%s", homedir, PERSONAL_DIR); rubidium@7425: AppendPathSeparator(tmp, MAX_PATH); rubidium@7425: rubidium@7425: _searchpaths[SP_PERSONAL_DIR] = strdup(tmp); rubidium@7425: #endif bjarni@8769: bjarni@8769: #if defined(WITH_SHARED_DIR) bjarni@8769: snprintf(tmp, MAX_PATH, "%s", SHARED_DIR); bjarni@8769: AppendPathSeparator(tmp, MAX_PATH); bjarni@8769: _searchpaths[SP_SHARED_DIR] = strdup(tmp); bjarni@8769: #else rubidium@7425: _searchpaths[SP_SHARED_DIR] = NULL; bjarni@8769: #endif rubidium@7425: rubidium@7486: #if defined(__MORPHOS__) || defined(__AMIGA__) rubidium@7486: _searchpaths[SP_WORKING_DIR] = NULL; rubidium@7486: #else rubidium@10418: if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0'; rubidium@7425: AppendPathSeparator(tmp, MAX_PATH); rubidium@7425: _searchpaths[SP_WORKING_DIR] = strdup(tmp); rubidium@7486: #endif rubidium@7425: rubidium@7425: /* Change the working directory to that one of the executable */ rubidium@7425: ChangeWorkingDirectory((char*)exe); rubidium@10418: if (getcwd(tmp, MAX_PATH) == NULL) *tmp = '\0'; rubidium@7425: AppendPathSeparator(tmp, MAX_PATH); rubidium@7425: _searchpaths[SP_BINARY_DIR] = strdup(tmp); rubidium@7425: rubidium@7486: #if defined(__MORPHOS__) || defined(__AMIGA__) rubidium@7486: _searchpaths[SP_INSTALLATION_DIR] = NULL; rubidium@7486: #else rubidium@7425: snprintf(tmp, MAX_PATH, "%s", GLOBAL_DATA_DIR); rubidium@7425: AppendPathSeparator(tmp, MAX_PATH); rubidium@7425: _searchpaths[SP_INSTALLATION_DIR] = strdup(tmp); rubidium@7486: #endif rubidium@7425: #ifdef WITH_COCOA rubidium@7425: extern void cocoaSetApplicationBundleDir(); rubidium@7425: cocoaSetApplicationBundleDir(); rubidium@7425: #else rubidium@7425: _searchpaths[SP_APPLICATION_BUNDLE_DIR] = NULL; rubidium@7425: #endif truelight@8077: truelight@8077: ScanForTarFiles(); rubidium@6643: } rubidium@6643: #endif /* defined(WIN32) || defined(WINCE) */ rubidium@6624: rubidium@7425: char *_personal_dir; rubidium@7425: rubidium@6624: /** rubidium@6624: * Acquire the base paths (personal dir and game data dir), rubidium@6624: * fill all other paths (save dir, autosave dir etc) and rubidium@6624: * make the save and scenario directories. rubidium@7425: * @param exe the path from the current path to the executable rubidium@6624: */ rubidium@6643: void DeterminePaths(const char *exe) rubidium@6624: { rubidium@6643: DetermineBasePaths(exe); rubidium@6624: rubidium@7425: Searchpath sp; rubidium@7425: FOR_ALL_SEARCHPATHS(sp) DEBUG(misc, 4, "%s added as search path", _searchpaths[sp]); rubidium@7425: rubidium@7437: if (_config_file != NULL) { rubidium@7437: _personal_dir = strdup(_config_file); rubidium@7437: char *end = strrchr(_personal_dir , PATHSEPCHAR); rubidium@7437: if (end == NULL) { rubidium@7437: _personal_dir[0] = '\0'; rubidium@7437: } else { rubidium@7437: end[1] = '\0'; rubidium@7437: } rubidium@7437: } else { rubidium@7437: char personal_dir[MAX_PATH]; rubidium@7437: FioFindFullPath(personal_dir, lengthof(personal_dir), BASE_DIR, "openttd.cfg"); rubidium@7437: rubidium@7437: if (FileExists(personal_dir)) { rubidium@7437: char *end = strrchr(personal_dir, PATHSEPCHAR); rubidium@7437: if (end != NULL) end[1] = '\0'; rubidium@7437: _personal_dir = strdup(personal_dir); rubidium@7437: _config_file = str_fmt("%sopenttd.cfg", _personal_dir); rubidium@7437: } else { rubidium@7437: static const Searchpath new_openttd_cfg_order[] = { rubidium@7437: SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR rubidium@7437: }; rubidium@7437: rubidium@7437: for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) { rubidium@7437: if (IsValidSearchPath(new_openttd_cfg_order[i])) { rubidium@7437: _personal_dir = strdup(_searchpaths[new_openttd_cfg_order[i]]); rubidium@7437: _config_file = str_fmt("%sopenttd.cfg", _personal_dir); rubidium@7437: break; rubidium@7437: } rubidium@7437: } rubidium@7437: } rubidium@7425: } rubidium@6624: rubidium@7437: DEBUG(misc, 3, "%s found as personal directory", _personal_dir); rubidium@6624: rubidium@7425: _highscore_file = str_fmt("%shs.dat", _personal_dir); rubidium@7425: _log_file = str_fmt("%sopenttd.log", _personal_dir); rubidium@6624: rubidium@7425: char *save_dir = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(SAVE_DIR)); rubidium@7425: char *autosave_dir = str_fmt("%s%s", _personal_dir, FioGetSubdirectory(AUTOSAVE_DIR)); rubidium@7425: rubidium@7425: /* Make the necessary folders */ rubidium@8407: #if !defined(__MORPHOS__) && !defined(__AMIGA__) && defined(WITH_PERSONAL_DIR) rubidium@7425: FioCreateDirectory(_personal_dir); rubidium@8407: #endif rubidium@8407: rubidium@7425: FioCreateDirectory(save_dir); rubidium@7425: FioCreateDirectory(autosave_dir); rubidium@7425: rubidium@7425: free(save_dir); rubidium@7425: free(autosave_dir); rubidium@6624: } rubidium@7371: rubidium@7371: /** rubidium@7371: * Sanitizes a filename, i.e. removes all illegal characters from it. rubidium@7371: * @param filename the "\0" terminated filename rubidium@7371: */ rubidium@7371: void SanitizeFilename(char *filename) rubidium@7371: { rubidium@7371: for (; *filename != '\0'; filename++) { rubidium@7371: switch (*filename) { rubidium@7371: /* The following characters are not allowed in filenames rubidium@7371: * on at least one of the supported operating systems: */ rubidium@7371: case ':': case '\\': case '*': case '?': case '/': glx@7512: case '<': case '>': case '|': case '"': rubidium@7371: *filename = '_'; rubidium@7371: break; rubidium@7371: } rubidium@7371: } rubidium@7371: } rubidium@8627: rubidium@8627: void *ReadFileToMem(const char *filename, size_t *lenp, size_t maxsize) rubidium@8627: { rubidium@8627: FILE *in; rubidium@8627: byte *mem; rubidium@8627: size_t len; rubidium@8627: rubidium@8627: in = fopen(filename, "rb"); rubidium@8627: if (in == NULL) return NULL; rubidium@8627: rubidium@8627: fseek(in, 0, SEEK_END); rubidium@8627: len = ftell(in); rubidium@8627: fseek(in, 0, SEEK_SET); rubidium@8627: if (len > maxsize || (mem = MallocT(len + 1)) == NULL) { rubidium@8627: fclose(in); rubidium@8627: return NULL; rubidium@8627: } rubidium@8627: mem[len] = 0; rubidium@8627: if (fread(mem, len, 1, in) != 1) { rubidium@8627: fclose(in); rubidium@8627: free(mem); rubidium@8627: return NULL; rubidium@8627: } rubidium@8627: fclose(in); rubidium@8627: rubidium@8627: *lenp = len; rubidium@8627: return mem; rubidium@8627: }