Fix: Data races on cursor state in OpenGL backends
[openttd-github.git] / src / fileio.cpp
blobf2a2b14f3a3c53b622e15576e68048b0b75f42b3
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
8 /** @file fileio.cpp Standard In/Out file operations */
10 #include "stdafx.h"
11 #include "fileio_func.h"
12 #include "debug.h"
13 #include "fios.h"
14 #include "string_func.h"
15 #include "tar_type.h"
16 #ifdef _WIN32
17 #include <windows.h>
18 # define access _taccess
19 #elif defined(__HAIKU__)
20 #include <Path.h>
21 #include <storage/FindDirectory.h>
22 #else
23 #include <unistd.h>
24 #include <pwd.h>
25 #endif
26 #include <sys/stat.h>
27 #include <array>
28 #include <sstream>
30 #include "safeguards.h"
32 /** Size of the #Fio data buffer. */
33 #define FIO_BUFFER_SIZE 512
35 /** Structure for keeping several open files with just one data buffer. */
36 struct Fio {
37 byte *buffer, *buffer_end; ///< position pointer in local buffer and last valid byte of buffer
38 byte buffer_start[FIO_BUFFER_SIZE]; ///< local buffer when read from file
39 size_t pos; ///< current (system) position in file
40 FILE *cur_fh; ///< current file handle
41 std::string filename; ///< current filename
42 std::array<FILE *, MAX_FILE_SLOTS> handles; ///< array of file handles we can have open
43 std::array<std::string, MAX_FILE_SLOTS> filenames; ///< array of filenames we (should) have open
44 std::array<std::string, MAX_FILE_SLOTS> shortnames;///< array of short names for spriteloader's use
47 static Fio _fio; ///< #Fio instance.
49 /** Whether the working directory should be scanned. */
50 static bool _do_scan_working_directory = true;
52 extern std::string _config_file;
53 extern std::string _highscore_file;
55 /**
56 * Get position in the current file.
57 * @return Position in the file.
59 size_t FioGetPos()
61 return _fio.pos + (_fio.buffer - _fio.buffer_end);
64 /**
65 * Get the filename associated with a slot.
66 * @param slot Index of queried file.
67 * @return Name of the file.
69 const char *FioGetFilename(uint8 slot)
71 return _fio.shortnames[slot].c_str();
74 /**
75 * Seek in the current file.
76 * @param pos New position.
77 * @param mode Type of seek (\c SEEK_CUR means \a pos is relative to current position, \c SEEK_SET means \a pos is absolute).
79 void FioSeekTo(size_t pos, int mode)
81 if (mode == SEEK_CUR) pos += FioGetPos();
82 _fio.buffer = _fio.buffer_end = _fio.buffer_start + FIO_BUFFER_SIZE;
83 _fio.pos = pos;
84 if (fseek(_fio.cur_fh, _fio.pos, SEEK_SET) < 0) {
85 DEBUG(misc, 0, "Seeking in %s failed", _fio.filename.c_str());
89 /**
90 * Switch to a different file and seek to a position.
91 * @param slot Slot number of the new file.
92 * @param pos New absolute position in the new file.
94 void FioSeekToFile(uint8 slot, size_t pos)
96 FILE *f = _fio.handles[slot];
97 assert(f != nullptr);
98 _fio.cur_fh = f;
99 _fio.filename = _fio.filenames[slot];
100 FioSeekTo(pos, SEEK_SET);
104 * Read a byte from the file.
105 * @return Read byte.
107 byte FioReadByte()
109 if (_fio.buffer == _fio.buffer_end) {
110 _fio.buffer = _fio.buffer_start;
111 size_t size = fread(_fio.buffer, 1, FIO_BUFFER_SIZE, _fio.cur_fh);
112 _fio.pos += size;
113 _fio.buffer_end = _fio.buffer_start + size;
115 if (size == 0) return 0;
117 return *_fio.buffer++;
121 * Skip \a n bytes ahead in the file.
122 * @param n Number of bytes to skip reading.
124 void FioSkipBytes(int n)
126 for (;;) {
127 int m = std::min<int>(_fio.buffer_end - _fio.buffer, n);
128 _fio.buffer += m;
129 n -= m;
130 if (n == 0) break;
131 FioReadByte();
132 n--;
137 * Read a word (16 bits) from the file (in low endian format).
138 * @return Read word.
140 uint16 FioReadWord()
142 byte b = FioReadByte();
143 return (FioReadByte() << 8) | b;
147 * Read a double word (32 bits) from the file (in low endian format).
148 * @return Read word.
150 uint32 FioReadDword()
152 uint b = FioReadWord();
153 return (FioReadWord() << 16) | b;
157 * Read a block.
158 * @param ptr Destination buffer.
159 * @param size Number of bytes to read.
161 void FioReadBlock(void *ptr, size_t size)
163 FioSeekTo(FioGetPos(), SEEK_SET);
164 _fio.pos += fread(ptr, 1, size, _fio.cur_fh);
168 * Close the file at the given slot number.
169 * @param slot File index to close.
171 static inline void FioCloseFile(int slot)
173 if (_fio.handles[slot] != nullptr) {
174 fclose(_fio.handles[slot]);
176 _fio.shortnames[slot].clear();
178 _fio.handles[slot] = nullptr;
182 /** Close all slotted open files. */
183 void FioCloseAll()
185 for (int i = 0; i != lengthof(_fio.handles); i++) {
186 FioCloseFile(i);
191 * Open a slotted file.
192 * @param slot Index to assign.
193 * @param filename Name of the file at the disk.
194 * @param subdir The sub directory to search this file in.
196 void FioOpenFile(int slot, const std::string &filename, Subdirectory subdir)
198 FILE *f;
200 f = FioFOpenFile(filename, "rb", subdir);
201 if (f == nullptr) usererror("Cannot open file '%s'", filename.c_str());
202 long pos = ftell(f);
203 if (pos < 0) usererror("Cannot read file '%s'", filename.c_str());
205 FioCloseFile(slot); // if file was opened before, close it
206 _fio.handles[slot] = f;
207 _fio.filenames[slot] = filename;
209 /* Store the filename without path and extension */
210 auto t = filename.rfind(PATHSEPCHAR);
211 std::string sn = filename.substr(t != std::string::npos ? t + 1 : 0);
212 _fio.shortnames[slot] = sn.substr(0, sn.rfind('.'));
213 strtolower(_fio.shortnames[slot]);
215 FioSeekToFile(slot, (size_t)pos);
218 static const char * const _subdirs[] = {
220 "save" PATHSEP,
221 "save" PATHSEP "autosave" PATHSEP,
222 "scenario" PATHSEP,
223 "scenario" PATHSEP "heightmap" PATHSEP,
224 "gm" PATHSEP,
225 "data" PATHSEP,
226 "baseset" PATHSEP,
227 "newgrf" PATHSEP,
228 "lang" PATHSEP,
229 "ai" PATHSEP,
230 "ai" PATHSEP "library" PATHSEP,
231 "game" PATHSEP,
232 "game" PATHSEP "library" PATHSEP,
233 "screenshot" PATHSEP,
235 static_assert(lengthof(_subdirs) == NUM_SUBDIRS);
238 * The search paths OpenTTD could search through.
239 * At least one of the slots has to be filled with a path.
240 * An empty string tells that there is no such path for the
241 * current operating system.
243 std::array<std::string, NUM_SEARCHPATHS> _searchpaths;
244 std::array<TarList, NUM_SUBDIRS> _tar_list;
245 TarFileList _tar_filelist[NUM_SUBDIRS];
247 typedef std::map<std::string, std::string> TarLinkList;
248 static TarLinkList _tar_linklist[NUM_SUBDIRS]; ///< List of directory links
251 * Checks whether the given search path is a valid search path
252 * @param sp the search path to check
253 * @return true if the search path is valid
255 bool IsValidSearchPath(Searchpath sp)
257 return sp < _searchpaths.size() && !_searchpaths[sp].empty();
261 * Check whether the given file exists
262 * @param filename the file to try for existence.
263 * @param subdir the subdirectory to look in
264 * @return true if and only if the file can be opened
266 bool FioCheckFileExists(const std::string &filename, Subdirectory subdir)
268 FILE *f = FioFOpenFile(filename, "rb", subdir);
269 if (f == nullptr) return false;
271 FioFCloseFile(f);
272 return true;
276 * Test whether the given filename exists.
277 * @param filename the file to test.
278 * @return true if and only if the file exists.
280 bool FileExists(const std::string &filename)
282 return access(OTTD2FS(filename).c_str(), 0) == 0;
286 * Close a file in a safe way.
288 void FioFCloseFile(FILE *f)
290 fclose(f);
294 * Find a path to the filename in one of the search directories.
295 * @param subdir Subdirectory to try.
296 * @param filename Filename to look for.
297 * @return String containing the path if the path was found, else an empty string.
299 std::string FioFindFullPath(Subdirectory subdir, const char *filename)
301 Searchpath sp;
302 assert(subdir < NUM_SUBDIRS);
304 FOR_ALL_SEARCHPATHS(sp) {
305 std::string buf = FioGetDirectory(sp, subdir);
306 buf += filename;
307 if (FileExists(buf)) return buf;
308 #if !defined(_WIN32)
309 /* Be, as opening files, aware that sometimes the filename
310 * might be in uppercase when it is in lowercase on the
311 * disk. Of course Windows doesn't care about casing. */
312 if (strtolower(buf, _searchpaths[sp].size() - 1) && FileExists(buf)) return buf;
313 #endif
316 return {};
319 std::string FioGetDirectory(Searchpath sp, Subdirectory subdir)
321 assert(subdir < NUM_SUBDIRS);
322 assert(sp < NUM_SEARCHPATHS);
324 return _searchpaths[sp] + _subdirs[subdir];
327 std::string FioFindDirectory(Subdirectory subdir)
329 Searchpath sp;
331 /* Find and return the first valid directory */
332 FOR_ALL_SEARCHPATHS(sp) {
333 std::string ret = FioGetDirectory(sp, subdir);
334 if (FileExists(ret)) return ret;
337 /* Could not find the directory, fall back to a base path */
338 return _personal_dir;
341 static FILE *FioFOpenFileSp(const std::string &filename, const char *mode, Searchpath sp, Subdirectory subdir, size_t *filesize)
343 #if defined(_WIN32)
344 /* fopen is implemented as a define with ellipses for
345 * Unicode support (prepend an L). As we are not sending
346 * a string, but a variable, it 'renames' the variable,
347 * so make that variable to makes it compile happily */
348 wchar_t Lmode[5];
349 MultiByteToWideChar(CP_ACP, 0, mode, -1, Lmode, lengthof(Lmode));
350 #endif
351 FILE *f = nullptr;
352 std::string buf;
354 if (subdir == NO_DIRECTORY) {
355 buf = filename;
356 } else {
357 buf = _searchpaths[sp] + _subdirs[subdir] + filename;
360 #if defined(_WIN32)
361 if (mode[0] == 'r' && GetFileAttributes(OTTD2FS(buf).c_str()) == INVALID_FILE_ATTRIBUTES) return nullptr;
362 #endif
364 f = fopen(buf.c_str(), mode);
365 #if !defined(_WIN32)
366 if (f == nullptr && strtolower(buf, subdir == NO_DIRECTORY ? 0 : _searchpaths[sp].size() - 1) ) {
367 f = fopen(buf.c_str(), mode);
369 #endif
370 if (f != nullptr && filesize != nullptr) {
371 /* Find the size of the file */
372 fseek(f, 0, SEEK_END);
373 *filesize = ftell(f);
374 fseek(f, 0, SEEK_SET);
376 return f;
380 * Opens a file from inside a tar archive.
381 * @param entry The entry to open.
382 * @param[out] filesize If not \c nullptr, size of the opened file.
383 * @return File handle of the opened file, or \c nullptr if the file is not available.
384 * @note The file is read from within the tar file, and may not return \c EOF after reading the whole file.
386 FILE *FioFOpenFileTar(const TarFileListEntry &entry, size_t *filesize)
388 FILE *f = fopen(entry.tar_filename.c_str(), "rb");
389 if (f == nullptr) return f;
391 if (fseek(f, entry.position, SEEK_SET) < 0) {
392 fclose(f);
393 return nullptr;
396 if (filesize != nullptr) *filesize = entry.size;
397 return f;
401 * Opens a OpenTTD file somewhere in a personal or global directory.
402 * @param filename Name of the file to open.
403 * @param subdir Subdirectory to open.
404 * @return File handle of the opened file, or \c nullptr if the file is not available.
406 FILE *FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
408 FILE *f = nullptr;
409 Searchpath sp;
411 assert(subdir < NUM_SUBDIRS || subdir == NO_DIRECTORY);
413 FOR_ALL_SEARCHPATHS(sp) {
414 f = FioFOpenFileSp(filename, mode, sp, subdir, filesize);
415 if (f != nullptr || subdir == NO_DIRECTORY) break;
418 /* We can only use .tar in case of data-dir, and read-mode */
419 if (f == nullptr && mode[0] == 'r' && subdir != NO_DIRECTORY) {
420 /* Filenames in tars are always forced to be lowercase */
421 std::string resolved_name = filename;
422 strtolower(resolved_name);
424 /* Resolve ".." */
425 std::istringstream ss(resolved_name);
426 std::vector<std::string> tokens;
427 std::string token;
428 while (std::getline(ss, token, PATHSEPCHAR)) {
429 if (token == "..") {
430 if (tokens.size() < 2) return nullptr;
431 tokens.pop_back();
432 } else if (token == ".") {
433 /* Do nothing. "." means current folder, but you can create tar files with "." in the path.
434 * This confuses our file resolver. So, act like this folder doesn't exist. */
435 } else {
436 tokens.push_back(token);
440 resolved_name.clear();
441 bool first = true;
442 for (const std::string &token : tokens) {
443 if (!first) {
444 resolved_name += PATHSEP;
446 resolved_name += token;
447 first = false;
450 /* Resolve ONE directory link */
451 for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
452 const std::string &src = link->first;
453 size_t len = src.length();
454 if (resolved_name.length() >= len && resolved_name[len - 1] == PATHSEPCHAR && src.compare(0, len, resolved_name, 0, len) == 0) {
455 /* Apply link */
456 resolved_name.replace(0, len, link->second);
457 break; // Only resolve one level
461 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
462 if (it != _tar_filelist[subdir].end()) {
463 f = FioFOpenFileTar(it->second, filesize);
467 /* Sometimes a full path is given. To support
468 * the 'subdirectory' must be 'removed'. */
469 if (f == nullptr && subdir != NO_DIRECTORY) {
470 switch (subdir) {
471 case BASESET_DIR:
472 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
473 if (f != nullptr) break;
474 FALLTHROUGH;
475 case NEWGRF_DIR:
476 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
477 break;
479 default:
480 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
481 break;
485 return f;
489 * Create a directory with the given name
490 * If the parent directory does not exist, it will try to create that as well.
491 * @param name the new name of the directory
493 void FioCreateDirectory(const std::string &name)
495 auto p = name.find_last_of(PATHSEPCHAR);
496 if (p != std::string::npos) {
497 std::string dirname = name.substr(0, p);
498 DIR *dir = ttd_opendir(dirname.c_str());
499 if (dir == nullptr) {
500 FioCreateDirectory(dirname); // Try creating the parent directory, if we couldn't open it
501 } else {
502 closedir(dir);
506 /* Ignore directory creation errors; they'll surface later on, and most
507 * of the time they are 'directory already exists' errors anyhow. */
508 #if defined(_WIN32)
509 CreateDirectory(OTTD2FS(name).c_str(), nullptr);
510 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
511 mkdir(OTTD2FS(name).c_str());
512 #else
513 mkdir(OTTD2FS(name).c_str(), 0755);
514 #endif
518 * Appends, if necessary, the path separator character to the end of the string.
519 * It does not add the path separator to zero-sized strings.
520 * @param buf string to append the separator to
521 * @return true iff the operation succeeded
523 void AppendPathSeparator(std::string &buf)
525 if (buf.empty()) return;
527 if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
530 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
532 std::string src = srcParam;
533 std::string dest = destParam;
534 /* Tar internals assume lowercase */
535 std::transform(src.begin(), src.end(), src.begin(), tolower);
536 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
538 TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
539 if (dest_file != _tar_filelist[subdir].end()) {
540 /* Link to file. Process the link like the destination file. */
541 _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
542 } else {
543 /* Destination file not found. Assume 'link to directory'
544 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
545 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
546 const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
547 _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
552 * Simplify filenames from tars.
553 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
554 * @param name Filename to process.
556 static void SimplifyFileName(char *name)
558 /* Force lowercase */
559 strtolower(name);
561 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
562 #if (PATHSEPCHAR != '/')
563 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
564 #endif
568 * Perform the scanning of a particular subdirectory.
569 * @param sd The subdirectory to scan.
570 * @return The number of found tar files.
572 uint TarScanner::DoScan(Subdirectory sd)
574 _tar_filelist[sd].clear();
575 _tar_list[sd].clear();
576 uint num = this->Scan(".tar", sd, false);
577 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
578 return num;
581 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
583 DEBUG(misc, 1, "Scanning for tars");
584 TarScanner fs;
585 uint num = 0;
586 if (mode & TarScanner::BASESET) {
587 num += fs.DoScan(BASESET_DIR);
589 if (mode & TarScanner::NEWGRF) {
590 num += fs.DoScan(NEWGRF_DIR);
592 if (mode & TarScanner::AI) {
593 num += fs.DoScan(AI_DIR);
594 num += fs.DoScan(AI_LIBRARY_DIR);
596 if (mode & TarScanner::GAME) {
597 num += fs.DoScan(GAME_DIR);
598 num += fs.DoScan(GAME_LIBRARY_DIR);
600 if (mode & TarScanner::SCENARIO) {
601 num += fs.DoScan(SCENARIO_DIR);
602 num += fs.DoScan(HEIGHTMAP_DIR);
604 DEBUG(misc, 1, "Scan complete, found %d files", num);
605 return num;
609 * Add a single file to the scanned files of a tar, circumventing the scanning code.
610 * @param sd The sub directory the file is in.
611 * @param filename The name of the file to add.
612 * @return True if the additions went correctly.
614 bool TarScanner::AddFile(Subdirectory sd, const std::string &filename)
616 this->subdir = sd;
617 return this->AddFile(filename, 0);
620 bool TarScanner::AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)
622 /* No tar within tar. */
623 assert(tar_filename.empty());
625 /* The TAR-header, repeated for every file */
626 struct TarHeader {
627 char name[100]; ///< Name of the file
628 char mode[8];
629 char uid[8];
630 char gid[8];
631 char size[12]; ///< Size of the file, in ASCII
632 char mtime[12];
633 char chksum[8];
634 char typeflag;
635 char linkname[100];
636 char magic[6];
637 char version[2];
638 char uname[32];
639 char gname[32];
640 char devmajor[8];
641 char devminor[8];
642 char prefix[155]; ///< Path of the file
644 char unused[12];
647 /* Check if we already seen this file */
648 TarList::iterator it = _tar_list[this->subdir].find(filename);
649 if (it != _tar_list[this->subdir].end()) return false;
651 FILE *f = fopen(filename.c_str(), "rb");
652 /* Although the file has been found there can be
653 * a number of reasons we cannot open the file.
654 * Most common case is when we simply have not
655 * been given read access. */
656 if (f == nullptr) return false;
658 _tar_list[this->subdir][filename] = std::string{};
660 TarLinkList links; ///< Temporary list to collect links
662 TarHeader th;
663 char buf[sizeof(th.name) + 1], *end;
664 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
665 char link[sizeof(th.linkname) + 1];
666 char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
667 size_t num = 0, pos = 0;
669 /* Make a char of 512 empty bytes */
670 char empty[512];
671 memset(&empty[0], 0, sizeof(empty));
673 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
674 size_t num_bytes_read = fread(&th, 1, 512, f);
675 if (num_bytes_read != 512) break;
676 pos += num_bytes_read;
678 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
679 if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
680 /* If we have only zeros in the block, it can be an end-of-file indicator */
681 if (memcmp(&th, &empty[0], 512) == 0) continue;
683 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename.c_str());
684 fclose(f);
685 return false;
688 name[0] = '\0';
690 /* The prefix contains the directory-name */
691 if (th.prefix[0] != '\0') {
692 strecpy(name, th.prefix, lastof(name));
693 strecat(name, PATHSEP, lastof(name));
696 /* Copy the name of the file in a safe way at the end of 'name' */
697 strecat(name, th.name, lastof(name));
699 /* Calculate the size of the file.. for some strange reason this is stored as a string */
700 strecpy(buf, th.size, lastof(buf));
701 size_t skip = strtoul(buf, &end, 8);
703 switch (th.typeflag) {
704 case '\0':
705 case '0': { // regular file
706 /* Ignore empty files */
707 if (skip == 0) break;
709 if (strlen(name) == 0) break;
711 /* Store this entry in the list */
712 TarFileListEntry entry;
713 entry.tar_filename = filename;
714 entry.size = skip;
715 entry.position = pos;
717 /* Convert to lowercase and our PATHSEPCHAR */
718 SimplifyFileName(name);
720 DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
721 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
723 break;
726 case '1': // hard links
727 case '2': { // symbolic links
728 /* Copy the destination of the link in a safe way at the end of 'linkname' */
729 strecpy(link, th.linkname, lastof(link));
731 if (strlen(name) == 0 || strlen(link) == 0) break;
733 /* Convert to lowercase and our PATHSEPCHAR */
734 SimplifyFileName(name);
735 SimplifyFileName(link);
737 /* Only allow relative links */
738 if (link[0] == PATHSEPCHAR) {
739 DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
740 break;
743 /* Process relative path.
744 * Note: The destination of links must not contain any directory-links. */
745 strecpy(dest, name, lastof(dest));
746 char *destpos = strrchr(dest, PATHSEPCHAR);
747 if (destpos == nullptr) destpos = dest;
748 *destpos = '\0';
750 char *pos = link;
751 while (*pos != '\0') {
752 char *next = strchr(pos, PATHSEPCHAR);
753 if (next == nullptr) {
754 next = pos + strlen(pos);
755 } else {
756 /* Terminate the substring up to the path separator character. */
757 *next++= '\0';
760 if (strcmp(pos, ".") == 0) {
761 /* Skip '.' (current dir) */
762 } else if (strcmp(pos, "..") == 0) {
763 /* level up */
764 if (dest[0] == '\0') {
765 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
766 break;
769 /* Truncate 'dest' after last PATHSEPCHAR.
770 * This assumes that the truncated part is a real directory and not a link. */
771 destpos = strrchr(dest, PATHSEPCHAR);
772 if (destpos == nullptr) destpos = dest;
773 *destpos = '\0';
774 } else {
775 /* Append at end of 'dest' */
776 if (destpos != dest) destpos = strecpy(destpos, PATHSEP, lastof(dest));
777 destpos = strecpy(destpos, pos, lastof(dest));
780 if (destpos >= lastof(dest)) {
781 DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename.c_str());
782 fclose(f);
783 return false;
786 pos = next;
789 /* Store links in temporary list */
790 DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
791 links.insert(TarLinkList::value_type(name, dest));
793 break;
796 case '5': // directory
797 /* Convert to lowercase and our PATHSEPCHAR */
798 SimplifyFileName(name);
800 /* Store the first directory name we detect */
801 DEBUG(misc, 6, "Found dir in tar: %s", name);
802 if (_tar_list[this->subdir][filename].empty()) _tar_list[this->subdir][filename] = name;
803 break;
805 default:
806 /* Ignore other types */
807 break;
810 /* Skip to the next block.. */
811 skip = Align(skip, 512);
812 if (fseek(f, skip, SEEK_CUR) < 0) {
813 DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename.c_str());
814 fclose(f);
815 return false;
817 pos += skip;
820 DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename.c_str(), num);
821 fclose(f);
823 /* Resolve file links and store directory links.
824 * We restrict usage of links to two cases:
825 * 1) Links to directories:
826 * Both the source path and the destination path must NOT contain any further links.
827 * When resolving files at most one directory link is resolved.
828 * 2) Links to files:
829 * The destination path must NOT contain any links.
830 * The source path may contain one directory link.
832 for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
833 const std::string &src = link->first;
834 const std::string &dest = link->second;
835 TarAddLink(src, dest, this->subdir);
838 return true;
842 * Extract the tar with the given filename in the directory
843 * where the tar resides.
844 * @param tar_filename the name of the tar to extract.
845 * @param subdir The sub directory the tar is in.
846 * @return false on failure.
848 bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
850 TarList::iterator it = _tar_list[subdir].find(tar_filename);
851 /* We don't know the file. */
852 if (it == _tar_list[subdir].end()) return false;
854 const auto &dirname = (*it).second;
856 /* The file doesn't have a sub directory! */
857 if (dirname.empty()) {
858 DEBUG(misc, 1, "Extracting %s failed; archive rejected, the contents must be in a sub directory", tar_filename.c_str());
859 return false;
862 std::string filename = tar_filename;
863 auto p = filename.find_last_of(PATHSEPCHAR);
864 /* The file's path does not have a separator? */
865 if (p == std::string::npos) return false;
867 filename.replace(p + 1, std::string::npos, dirname);
868 DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename.c_str(), filename.c_str());
869 FioCreateDirectory(filename);
871 for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
872 if (tar_filename != it2->second.tar_filename) continue;
874 filename.replace(p + 1, std::string::npos, it2->first);
876 DEBUG(misc, 9, " extracting %s", filename.c_str());
878 /* First open the file in the .tar. */
879 size_t to_copy = 0;
880 std::unique_ptr<FILE, FileDeleter> in(FioFOpenFileTar(it2->second, &to_copy));
881 if (!in) {
882 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename.c_str(), tar_filename.c_str());
883 return false;
886 /* Now open the 'output' file. */
887 std::unique_ptr<FILE, FileDeleter> out(fopen(filename.c_str(), "wb"));
888 if (!out) {
889 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename.c_str(), filename.c_str());
890 return false;
893 /* Now read from the tar and write it into the file. */
894 char buffer[4096];
895 size_t read;
896 for (; to_copy != 0; to_copy -= read) {
897 read = fread(buffer, 1, std::min(to_copy, lengthof(buffer)), in.get());
898 if (read <= 0 || fwrite(buffer, 1, read, out.get()) != read) break;
901 if (to_copy != 0) {
902 DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename.c_str(), (int)to_copy);
903 return false;
907 DEBUG(misc, 9, " extraction successful");
908 return true;
911 #if defined(_WIN32)
913 * Determine the base (personal dir and game data dir) paths
914 * @param exe the path from the current path to the executable
915 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
917 extern void DetermineBasePaths(const char *exe);
918 #else /* defined(_WIN32) */
921 * Changes the working directory to the path of the give executable.
922 * For OSX application bundles '.app' is the required extension of the bundle,
923 * so when we crop the path to there, when can remove the name of the bundle
924 * in the same way we remove the name from the executable name.
925 * @param exe the path to the executable
927 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
929 char tmp[MAX_PATH];
930 strecpy(tmp, exe, lastof(tmp));
932 bool success = false;
933 #ifdef WITH_COCOA
934 char *app_bundle = strchr(tmp, '.');
935 while (app_bundle != nullptr && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
937 if (app_bundle != nullptr) *app_bundle = '\0';
938 #endif /* WITH_COCOA */
939 char *s = strrchr(tmp, PATHSEPCHAR);
940 if (s != nullptr) {
941 *s = '\0';
942 if (chdir(tmp) != 0) {
943 DEBUG(misc, 0, "Directory with the binary does not exist?");
944 } else {
945 success = true;
948 return success;
952 * Whether we should scan the working directory.
953 * It should not be scanned if it's the root or
954 * the home directory as in both cases a big data
955 * directory can cause huge amounts of unrelated
956 * files scanned. Furthermore there are nearly no
957 * use cases for the home/root directory to have
958 * OpenTTD directories.
959 * @return true if it should be scanned.
961 bool DoScanWorkingDirectory()
963 /* No working directory, so nothing to do. */
964 if (_searchpaths[SP_WORKING_DIR].empty()) return false;
966 /* Working directory is root, so do nothing. */
967 if (_searchpaths[SP_WORKING_DIR] == PATHSEP) return false;
969 /* No personal/home directory, so the working directory won't be that. */
970 if (_searchpaths[SP_PERSONAL_DIR].empty()) return true;
972 std::string tmp = _searchpaths[SP_WORKING_DIR] + PERSONAL_DIR;
973 AppendPathSeparator(tmp);
975 return _searchpaths[SP_PERSONAL_DIR] != tmp;
979 * Gets the home directory of the user.
980 * May return an empty string in the unlikely scenario that the home directory cannot be found.
981 * @return User's home directory
983 static std::string GetHomeDir()
985 #ifdef __HAIKU__
986 BPath path;
987 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
988 return std::string(path.Path());
989 #else
990 const char *home_env = getenv("HOME"); // Stack var, shouldn't be freed
991 if (home_env != nullptr) return std::string(home_env);
993 const struct passwd *pw = getpwuid(getuid());
994 if (pw != nullptr) return std::string(pw->pw_dir);
995 #endif
996 return {};
1000 * Determine the base (personal dir and game data dir) paths
1001 * @param exe the path to the executable
1003 void DetermineBasePaths(const char *exe)
1005 std::string tmp;
1006 const std::string homedir = GetHomeDir();
1007 #ifdef USE_XDG
1008 const char *xdg_data_home = getenv("XDG_DATA_HOME");
1009 if (xdg_data_home != nullptr) {
1010 tmp = xdg_data_home;
1011 tmp += PATHSEP;
1012 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
1013 AppendPathSeparator(tmp);
1014 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
1016 tmp += "content_download";
1017 AppendPathSeparator(tmp);
1018 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG] = tmp;
1019 } else if (!homedir.empty()) {
1020 tmp = homedir;
1021 tmp += PATHSEP ".local" PATHSEP "share" PATHSEP;
1022 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
1023 AppendPathSeparator(tmp);
1024 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
1026 tmp += "content_download";
1027 AppendPathSeparator(tmp);
1028 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG] = tmp;
1029 } else {
1030 _searchpaths[SP_PERSONAL_DIR_XDG].clear();
1031 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG].clear();
1033 #endif
1035 #if defined(OS2) || !defined(WITH_PERSONAL_DIR)
1036 _searchpaths[SP_PERSONAL_DIR].clear();
1037 #else
1038 if (!homedir.empty()) {
1039 tmp = homedir;
1040 tmp += PATHSEP;
1041 tmp += PERSONAL_DIR;
1042 AppendPathSeparator(tmp);
1043 _searchpaths[SP_PERSONAL_DIR] = tmp;
1045 tmp += "content_download";
1046 AppendPathSeparator(tmp);
1047 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR] = tmp;
1048 } else {
1049 _searchpaths[SP_PERSONAL_DIR].clear();
1050 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR].clear();
1052 #endif
1054 #if defined(WITH_SHARED_DIR)
1055 tmp = SHARED_DIR;
1056 AppendPathSeparator(tmp);
1057 _searchpaths[SP_SHARED_DIR] = tmp;
1058 #else
1059 _searchpaths[SP_SHARED_DIR].clear();
1060 #endif
1062 char cwd[MAX_PATH];
1063 if (getcwd(cwd, MAX_PATH) == nullptr) *cwd = '\0';
1065 if (_config_file.empty()) {
1066 /* Get the path to working directory of OpenTTD. */
1067 tmp = cwd;
1068 AppendPathSeparator(tmp);
1069 _searchpaths[SP_WORKING_DIR] = tmp;
1071 _do_scan_working_directory = DoScanWorkingDirectory();
1072 } else {
1073 /* Use the folder of the config file as working directory. */
1074 size_t end = _config_file.find_last_of(PATHSEPCHAR);
1075 if (end == std::string::npos) {
1076 /* _config_file is not in a folder, so use current directory. */
1077 tmp = cwd;
1078 AppendPathSeparator(tmp);
1079 _searchpaths[SP_WORKING_DIR] = tmp;
1080 } else {
1081 _searchpaths[SP_WORKING_DIR] = _config_file.substr(0, end + 1);
1085 /* Change the working directory to that one of the executable */
1086 if (ChangeWorkingDirectoryToExecutable(exe)) {
1087 char buf[MAX_PATH];
1088 if (getcwd(buf, lengthof(buf)) == nullptr) {
1089 tmp.clear();
1090 } else {
1091 tmp = buf;
1093 AppendPathSeparator(tmp);
1094 _searchpaths[SP_BINARY_DIR] = tmp;
1095 } else {
1096 _searchpaths[SP_BINARY_DIR].clear();
1099 if (cwd[0] != '\0') {
1100 /* Go back to the current working directory. */
1101 if (chdir(cwd) != 0) {
1102 DEBUG(misc, 0, "Failed to return to working directory!");
1106 #if !defined(GLOBAL_DATA_DIR)
1107 _searchpaths[SP_INSTALLATION_DIR].clear();
1108 #else
1109 tmp = GLOBAL_DATA_DIR;
1110 AppendPathSeparator(tmp);
1111 _searchpaths[SP_INSTALLATION_DIR] = tmp;
1112 #endif
1113 #ifdef WITH_COCOA
1114 extern void CocoaSetApplicationBundleDir();
1115 CocoaSetApplicationBundleDir();
1116 #else
1117 _searchpaths[SP_APPLICATION_BUNDLE_DIR].clear();
1118 #endif
1120 #endif /* defined(_WIN32) */
1122 std::string _personal_dir;
1125 * Acquire the base paths (personal dir and game data dir),
1126 * fill all other paths (save dir, autosave dir etc) and
1127 * make the save and scenario directories.
1128 * @param exe the path from the current path to the executable
1130 void DeterminePaths(const char *exe)
1132 DetermineBasePaths(exe);
1134 #ifdef USE_XDG
1135 std::string config_home;
1136 const std::string homedir = GetHomeDir();
1137 const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
1138 if (xdg_config_home != nullptr) {
1139 config_home = xdg_config_home;
1140 config_home += PATHSEP;
1141 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
1142 } else if (!homedir.empty()) {
1143 /* Defaults to ~/.config */
1144 config_home = homedir;
1145 config_home += PATHSEP ".config" PATHSEP;
1146 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
1148 AppendPathSeparator(config_home);
1149 #endif
1151 Searchpath sp;
1152 FOR_ALL_SEARCHPATHS(sp) {
1153 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1154 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp].c_str());
1157 std::string config_dir;
1158 if (!_config_file.empty()) {
1159 config_dir = _searchpaths[SP_WORKING_DIR];
1160 } else {
1161 std::string personal_dir = FioFindFullPath(BASE_DIR, "openttd.cfg");
1162 if (!personal_dir.empty()) {
1163 auto end = personal_dir.find_last_of(PATHSEPCHAR);
1164 if (end != std::string::npos) personal_dir.erase(end + 1);
1165 config_dir = personal_dir;
1166 } else {
1167 #ifdef USE_XDG
1168 /* No previous configuration file found. Use the configuration folder from XDG. */
1169 config_dir = config_home;
1170 #else
1171 static const Searchpath new_openttd_cfg_order[] = {
1172 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1175 config_dir.clear();
1176 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
1177 if (IsValidSearchPath(new_openttd_cfg_order[i])) {
1178 config_dir = _searchpaths[new_openttd_cfg_order[i]];
1179 break;
1182 #endif
1184 _config_file = config_dir + "openttd.cfg";
1187 DEBUG(misc, 3, "%s found as config directory", config_dir.c_str());
1189 _highscore_file = config_dir + "hs.dat";
1190 extern std::string _hotkeys_file;
1191 _hotkeys_file = config_dir + "hotkeys.cfg";
1192 extern std::string _windows_file;
1193 _windows_file = config_dir + "windows.cfg";
1195 #ifdef USE_XDG
1196 if (config_dir == config_home) {
1197 /* We are using the XDG configuration home for the config file,
1198 * then store the rest in the XDG data home folder. */
1199 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1200 } else
1201 #endif
1203 _personal_dir = config_dir;
1206 /* Make the necessary folders */
1207 FioCreateDirectory(config_dir);
1208 #if defined(WITH_PERSONAL_DIR)
1209 FioCreateDirectory(_personal_dir);
1210 #endif
1212 DEBUG(misc, 3, "%s found as personal directory", _personal_dir.c_str());
1214 static const Subdirectory default_subdirs[] = {
1215 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
1218 for (uint i = 0; i < lengthof(default_subdirs); i++) {
1219 FioCreateDirectory(_personal_dir + _subdirs[default_subdirs[i]]);
1222 /* If we have network we make a directory for the autodownloading of content */
1223 _searchpaths[SP_AUTODOWNLOAD_DIR] = _personal_dir + "content_download" PATHSEP;
1224 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1226 /* Create the directory for each of the types of content */
1227 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1228 for (uint i = 0; i < lengthof(dirs); i++) {
1229 FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR, dirs[i]));
1232 extern std::string _log_file;
1233 _log_file = _personal_dir + "openttd.log";
1237 * Sanitizes a filename, i.e. removes all illegal characters from it.
1238 * @param filename the "\0" terminated filename
1240 void SanitizeFilename(char *filename)
1242 for (; *filename != '\0'; filename++) {
1243 switch (*filename) {
1244 /* The following characters are not allowed in filenames
1245 * on at least one of the supported operating systems: */
1246 case ':': case '\\': case '*': case '?': case '/':
1247 case '<': case '>': case '|': case '"':
1248 *filename = '_';
1249 break;
1255 * Load a file into memory.
1256 * @param filename Name of the file to load.
1257 * @param[out] lenp Length of loaded data.
1258 * @param maxsize Maximum size to load.
1259 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1260 * @note If \a maxsize less than the length of the file, loading fails.
1262 std::unique_ptr<char[]> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
1264 FILE *in = fopen(filename.c_str(), "rb");
1265 if (in == nullptr) return nullptr;
1267 FileCloser fc(in);
1269 fseek(in, 0, SEEK_END);
1270 size_t len = ftell(in);
1271 fseek(in, 0, SEEK_SET);
1272 if (len > maxsize) return nullptr;
1274 std::unique_ptr<char[]> mem = std::make_unique<char[]>(len + 1);
1276 mem.get()[len] = 0;
1277 if (fread(mem.get(), len, 1, in) != 1) return nullptr;
1279 lenp = len;
1280 return mem;
1284 * Helper to see whether a given filename matches the extension.
1285 * @param extension The extension to look for.
1286 * @param filename The filename to look in for the extension.
1287 * @return True iff the extension is nullptr, or the filename ends with it.
1289 static bool MatchesExtension(const char *extension, const char *filename)
1291 if (extension == nullptr) return true;
1293 const char *ext = strrchr(filename, extension[0]);
1294 return ext != nullptr && strcasecmp(ext, extension) == 0;
1298 * Scan a single directory (and recursively its children) and add
1299 * any graphics sets that are found.
1300 * @param fs the file scanner to add the files to
1301 * @param extension the extension of files to search for.
1302 * @param path full path we're currently at
1303 * @param basepath_length from where in the path are we 'based' on the search path
1304 * @param recursive whether to recursively search the sub directories
1306 static uint ScanPath(FileScanner *fs, const char *extension, const char *path, size_t basepath_length, bool recursive)
1308 extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
1310 uint num = 0;
1311 struct stat sb;
1312 struct dirent *dirent;
1313 DIR *dir;
1315 if (path == nullptr || (dir = ttd_opendir(path)) == nullptr) return 0;
1317 while ((dirent = readdir(dir)) != nullptr) {
1318 std::string d_name = FS2OTTD(dirent->d_name);
1320 if (!FiosIsValidFile(path, dirent, &sb)) continue;
1322 std::string filename(path);
1323 filename += d_name;
1325 if (S_ISDIR(sb.st_mode)) {
1326 /* Directory */
1327 if (!recursive) continue;
1328 if (d_name == "." || d_name == "..") continue;
1329 AppendPathSeparator(filename);
1330 num += ScanPath(fs, extension, filename.c_str(), basepath_length, recursive);
1331 } else if (S_ISREG(sb.st_mode)) {
1332 /* File */
1333 if (MatchesExtension(extension, filename.c_str()) && fs->AddFile(filename, basepath_length, {})) num++;
1337 closedir(dir);
1339 return num;
1343 * Scan the given tar and add graphics sets when it finds one.
1344 * @param fs the file scanner to scan for
1345 * @param extension the extension of files to search for.
1346 * @param tar the tar to search in.
1348 static uint ScanTar(FileScanner *fs, const char *extension, TarFileList::iterator tar)
1350 uint num = 0;
1351 const auto &filename = (*tar).first;
1353 if (MatchesExtension(extension, filename.c_str()) && fs->AddFile(filename, 0, (*tar).second.tar_filename)) num++;
1355 return num;
1359 * Scan for files with the given extension in the given search path.
1360 * @param extension the extension of files to search for.
1361 * @param sd the sub directory to search in.
1362 * @param tars whether to search in the tars too.
1363 * @param recursive whether to search recursively
1364 * @return the number of found files, i.e. the number of times that
1365 * AddFile returned true.
1367 uint FileScanner::Scan(const char *extension, Subdirectory sd, bool tars, bool recursive)
1369 this->subdir = sd;
1371 Searchpath sp;
1372 TarFileList::iterator tar;
1373 uint num = 0;
1375 FOR_ALL_SEARCHPATHS(sp) {
1376 /* Don't search in the working directory */
1377 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1379 std::string path = FioGetDirectory(sp, sd);
1380 num += ScanPath(this, extension, path.c_str(), path.size(), recursive);
1383 if (tars && sd != NO_DIRECTORY) {
1384 FOR_ALL_TARS(tar, sd) {
1385 num += ScanTar(this, extension, tar);
1389 switch (sd) {
1390 case BASESET_DIR:
1391 num += this->Scan(extension, OLD_GM_DIR, tars, recursive);
1392 FALLTHROUGH;
1393 case NEWGRF_DIR:
1394 num += this->Scan(extension, OLD_DATA_DIR, tars, recursive);
1395 break;
1397 default: break;
1400 return num;
1404 * Scan for files with the given extension in the given search path.
1405 * @param extension the extension of files to search for.
1406 * @param directory the sub directory to search in.
1407 * @param recursive whether to search recursively
1408 * @return the number of found files, i.e. the number of times that
1409 * AddFile returned true.
1411 uint FileScanner::Scan(const char *extension, const char *directory, bool recursive)
1413 std::string path(directory);
1414 AppendPathSeparator(path);
1415 return ScanPath(this, extension, path.c_str(), path.size(), recursive);