Add: allow making heightmap screenshot via console
[openttd-github.git] / src / fileio.cpp
blob045e5618138439c4b5fc1ba5be1e8ed3963f0d9e
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 {
433 tokens.push_back(token);
437 resolved_name.clear();
438 bool first = true;
439 for (const std::string &token : tokens) {
440 if (!first) {
441 resolved_name += PATHSEP;
443 resolved_name += token;
444 first = false;
447 /* Resolve ONE directory link */
448 for (TarLinkList::iterator link = _tar_linklist[subdir].begin(); link != _tar_linklist[subdir].end(); link++) {
449 const std::string &src = link->first;
450 size_t len = src.length();
451 if (resolved_name.length() >= len && resolved_name[len - 1] == PATHSEPCHAR && src.compare(0, len, resolved_name, 0, len) == 0) {
452 /* Apply link */
453 resolved_name.replace(0, len, link->second);
454 break; // Only resolve one level
458 TarFileList::iterator it = _tar_filelist[subdir].find(resolved_name);
459 if (it != _tar_filelist[subdir].end()) {
460 f = FioFOpenFileTar(it->second, filesize);
464 /* Sometimes a full path is given. To support
465 * the 'subdirectory' must be 'removed'. */
466 if (f == nullptr && subdir != NO_DIRECTORY) {
467 switch (subdir) {
468 case BASESET_DIR:
469 f = FioFOpenFile(filename, mode, OLD_GM_DIR, filesize);
470 if (f != nullptr) break;
471 FALLTHROUGH;
472 case NEWGRF_DIR:
473 f = FioFOpenFile(filename, mode, OLD_DATA_DIR, filesize);
474 break;
476 default:
477 f = FioFOpenFile(filename, mode, NO_DIRECTORY, filesize);
478 break;
482 return f;
486 * Create a directory with the given name
487 * If the parent directory does not exist, it will try to create that as well.
488 * @param name the new name of the directory
490 void FioCreateDirectory(const std::string &name)
492 auto p = name.find_last_of(PATHSEPCHAR);
493 if (p != std::string::npos) {
494 std::string dirname = name.substr(0, p);
495 DIR *dir = ttd_opendir(dirname.c_str());
496 if (dir == nullptr) {
497 FioCreateDirectory(dirname); // Try creating the parent directory, if we couldn't open it
498 } else {
499 closedir(dir);
503 /* Ignore directory creation errors; they'll surface later on, and most
504 * of the time they are 'directory already exists' errors anyhow. */
505 #if defined(_WIN32)
506 CreateDirectory(OTTD2FS(name.c_str()), nullptr);
507 #elif defined(OS2) && !defined(__INNOTEK_LIBC__)
508 mkdir(OTTD2FS(name.c_str()));
509 #else
510 mkdir(OTTD2FS(name.c_str()), 0755);
511 #endif
515 * Appends, if necessary, the path separator character to the end of the string.
516 * It does not add the path separator to zero-sized strings.
517 * @param buf string to append the separator to
518 * @return true iff the operation succeeded
520 void AppendPathSeparator(std::string &buf)
522 if (buf.empty()) return;
524 if (buf.back() != PATHSEPCHAR) buf.push_back(PATHSEPCHAR);
527 static void TarAddLink(const std::string &srcParam, const std::string &destParam, Subdirectory subdir)
529 std::string src = srcParam;
530 std::string dest = destParam;
531 /* Tar internals assume lowercase */
532 std::transform(src.begin(), src.end(), src.begin(), tolower);
533 std::transform(dest.begin(), dest.end(), dest.begin(), tolower);
535 TarFileList::iterator dest_file = _tar_filelist[subdir].find(dest);
536 if (dest_file != _tar_filelist[subdir].end()) {
537 /* Link to file. Process the link like the destination file. */
538 _tar_filelist[subdir].insert(TarFileList::value_type(src, dest_file->second));
539 } else {
540 /* Destination file not found. Assume 'link to directory'
541 * Append PATHSEPCHAR to 'src' and 'dest' if needed */
542 const std::string src_path = ((*src.rbegin() == PATHSEPCHAR) ? src : src + PATHSEPCHAR);
543 const std::string dst_path = (dest.length() == 0 ? "" : ((*dest.rbegin() == PATHSEPCHAR) ? dest : dest + PATHSEPCHAR));
544 _tar_linklist[subdir].insert(TarLinkList::value_type(src_path, dst_path));
549 * Simplify filenames from tars.
550 * Replace '/' by #PATHSEPCHAR, and force 'name' to lowercase.
551 * @param name Filename to process.
553 static void SimplifyFileName(char *name)
555 /* Force lowercase */
556 strtolower(name);
558 /* Tar-files always have '/' path-separator, but we want our PATHSEPCHAR */
559 #if (PATHSEPCHAR != '/')
560 for (char *n = name; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
561 #endif
565 * Perform the scanning of a particular subdirectory.
566 * @param sd The subdirectory to scan.
567 * @return The number of found tar files.
569 uint TarScanner::DoScan(Subdirectory sd)
571 _tar_filelist[sd].clear();
572 _tar_list[sd].clear();
573 uint num = this->Scan(".tar", sd, false);
574 if (sd == BASESET_DIR || sd == NEWGRF_DIR) num += this->Scan(".tar", OLD_DATA_DIR, false);
575 return num;
578 /* static */ uint TarScanner::DoScan(TarScanner::Mode mode)
580 DEBUG(misc, 1, "Scanning for tars");
581 TarScanner fs;
582 uint num = 0;
583 if (mode & TarScanner::BASESET) {
584 num += fs.DoScan(BASESET_DIR);
586 if (mode & TarScanner::NEWGRF) {
587 num += fs.DoScan(NEWGRF_DIR);
589 if (mode & TarScanner::AI) {
590 num += fs.DoScan(AI_DIR);
591 num += fs.DoScan(AI_LIBRARY_DIR);
593 if (mode & TarScanner::GAME) {
594 num += fs.DoScan(GAME_DIR);
595 num += fs.DoScan(GAME_LIBRARY_DIR);
597 if (mode & TarScanner::SCENARIO) {
598 num += fs.DoScan(SCENARIO_DIR);
599 num += fs.DoScan(HEIGHTMAP_DIR);
601 DEBUG(misc, 1, "Scan complete, found %d files", num);
602 return num;
606 * Add a single file to the scanned files of a tar, circumventing the scanning code.
607 * @param sd The sub directory the file is in.
608 * @param filename The name of the file to add.
609 * @return True if the additions went correctly.
611 bool TarScanner::AddFile(Subdirectory sd, const std::string &filename)
613 this->subdir = sd;
614 return this->AddFile(filename, 0);
617 bool TarScanner::AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename)
619 /* No tar within tar. */
620 assert(tar_filename.empty());
622 /* The TAR-header, repeated for every file */
623 struct TarHeader {
624 char name[100]; ///< Name of the file
625 char mode[8];
626 char uid[8];
627 char gid[8];
628 char size[12]; ///< Size of the file, in ASCII
629 char mtime[12];
630 char chksum[8];
631 char typeflag;
632 char linkname[100];
633 char magic[6];
634 char version[2];
635 char uname[32];
636 char gname[32];
637 char devmajor[8];
638 char devminor[8];
639 char prefix[155]; ///< Path of the file
641 char unused[12];
644 /* Check if we already seen this file */
645 TarList::iterator it = _tar_list[this->subdir].find(filename);
646 if (it != _tar_list[this->subdir].end()) return false;
648 FILE *f = fopen(filename.c_str(), "rb");
649 /* Although the file has been found there can be
650 * a number of reasons we cannot open the file.
651 * Most common case is when we simply have not
652 * been given read access. */
653 if (f == nullptr) return false;
655 _tar_list[this->subdir][filename] = std::string{};
657 TarLinkList links; ///< Temporary list to collect links
659 TarHeader th;
660 char buf[sizeof(th.name) + 1], *end;
661 char name[sizeof(th.prefix) + 1 + sizeof(th.name) + 1];
662 char link[sizeof(th.linkname) + 1];
663 char dest[sizeof(th.prefix) + 1 + sizeof(th.name) + 1 + 1 + sizeof(th.linkname) + 1];
664 size_t num = 0, pos = 0;
666 /* Make a char of 512 empty bytes */
667 char empty[512];
668 memset(&empty[0], 0, sizeof(empty));
670 for (;;) { // Note: feof() always returns 'false' after 'fseek()'. Cool, isn't it?
671 size_t num_bytes_read = fread(&th, 1, 512, f);
672 if (num_bytes_read != 512) break;
673 pos += num_bytes_read;
675 /* Check if we have the new tar-format (ustar) or the old one (a lot of zeros after 'link' field) */
676 if (strncmp(th.magic, "ustar", 5) != 0 && memcmp(&th.magic, &empty[0], 512 - offsetof(TarHeader, magic)) != 0) {
677 /* If we have only zeros in the block, it can be an end-of-file indicator */
678 if (memcmp(&th, &empty[0], 512) == 0) continue;
680 DEBUG(misc, 0, "The file '%s' isn't a valid tar-file", filename.c_str());
681 fclose(f);
682 return false;
685 name[0] = '\0';
687 /* The prefix contains the directory-name */
688 if (th.prefix[0] != '\0') {
689 strecpy(name, th.prefix, lastof(name));
690 strecat(name, PATHSEP, lastof(name));
693 /* Copy the name of the file in a safe way at the end of 'name' */
694 strecat(name, th.name, lastof(name));
696 /* Calculate the size of the file.. for some strange reason this is stored as a string */
697 strecpy(buf, th.size, lastof(buf));
698 size_t skip = strtoul(buf, &end, 8);
700 switch (th.typeflag) {
701 case '\0':
702 case '0': { // regular file
703 /* Ignore empty files */
704 if (skip == 0) break;
706 if (strlen(name) == 0) break;
708 /* Store this entry in the list */
709 TarFileListEntry entry;
710 entry.tar_filename = filename;
711 entry.size = skip;
712 entry.position = pos;
714 /* Convert to lowercase and our PATHSEPCHAR */
715 SimplifyFileName(name);
717 DEBUG(misc, 6, "Found file in tar: %s (" PRINTF_SIZE " bytes, " PRINTF_SIZE " offset)", name, skip, pos);
718 if (_tar_filelist[this->subdir].insert(TarFileList::value_type(name, entry)).second) num++;
720 break;
723 case '1': // hard links
724 case '2': { // symbolic links
725 /* Copy the destination of the link in a safe way at the end of 'linkname' */
726 strecpy(link, th.linkname, lastof(link));
728 if (strlen(name) == 0 || strlen(link) == 0) break;
730 /* Convert to lowercase and our PATHSEPCHAR */
731 SimplifyFileName(name);
732 SimplifyFileName(link);
734 /* Only allow relative links */
735 if (link[0] == PATHSEPCHAR) {
736 DEBUG(misc, 1, "Ignoring absolute link in tar: %s -> %s", name, link);
737 break;
740 /* Process relative path.
741 * Note: The destination of links must not contain any directory-links. */
742 strecpy(dest, name, lastof(dest));
743 char *destpos = strrchr(dest, PATHSEPCHAR);
744 if (destpos == nullptr) destpos = dest;
745 *destpos = '\0';
747 char *pos = link;
748 while (*pos != '\0') {
749 char *next = strchr(pos, PATHSEPCHAR);
750 if (next == nullptr) {
751 next = pos + strlen(pos);
752 } else {
753 /* Terminate the substring up to the path separator character. */
754 *next++= '\0';
757 if (strcmp(pos, ".") == 0) {
758 /* Skip '.' (current dir) */
759 } else if (strcmp(pos, "..") == 0) {
760 /* level up */
761 if (dest[0] == '\0') {
762 DEBUG(misc, 1, "Ignoring link pointing outside of data directory: %s -> %s", name, link);
763 break;
766 /* Truncate 'dest' after last PATHSEPCHAR.
767 * This assumes that the truncated part is a real directory and not a link. */
768 destpos = strrchr(dest, PATHSEPCHAR);
769 if (destpos == nullptr) destpos = dest;
770 *destpos = '\0';
771 } else {
772 /* Append at end of 'dest' */
773 if (destpos != dest) destpos = strecpy(destpos, PATHSEP, lastof(dest));
774 destpos = strecpy(destpos, pos, lastof(dest));
777 if (destpos >= lastof(dest)) {
778 DEBUG(misc, 0, "The length of a link in tar-file '%s' is too large (malformed?)", filename.c_str());
779 fclose(f);
780 return false;
783 pos = next;
786 /* Store links in temporary list */
787 DEBUG(misc, 6, "Found link in tar: %s -> %s", name, dest);
788 links.insert(TarLinkList::value_type(name, dest));
790 break;
793 case '5': // directory
794 /* Convert to lowercase and our PATHSEPCHAR */
795 SimplifyFileName(name);
797 /* Store the first directory name we detect */
798 DEBUG(misc, 6, "Found dir in tar: %s", name);
799 if (_tar_list[this->subdir][filename].empty()) _tar_list[this->subdir][filename] = name;
800 break;
802 default:
803 /* Ignore other types */
804 break;
807 /* Skip to the next block.. */
808 skip = Align(skip, 512);
809 if (fseek(f, skip, SEEK_CUR) < 0) {
810 DEBUG(misc, 0, "The file '%s' can't be read as a valid tar-file", filename.c_str());
811 fclose(f);
812 return false;
814 pos += skip;
817 DEBUG(misc, 1, "Found tar '%s' with " PRINTF_SIZE " new files", filename.c_str(), num);
818 fclose(f);
820 /* Resolve file links and store directory links.
821 * We restrict usage of links to two cases:
822 * 1) Links to directories:
823 * Both the source path and the destination path must NOT contain any further links.
824 * When resolving files at most one directory link is resolved.
825 * 2) Links to files:
826 * The destination path must NOT contain any links.
827 * The source path may contain one directory link.
829 for (TarLinkList::iterator link = links.begin(); link != links.end(); link++) {
830 const std::string &src = link->first;
831 const std::string &dest = link->second;
832 TarAddLink(src, dest, this->subdir);
835 return true;
839 * Extract the tar with the given filename in the directory
840 * where the tar resides.
841 * @param tar_filename the name of the tar to extract.
842 * @param subdir The sub directory the tar is in.
843 * @return false on failure.
845 bool ExtractTar(const std::string &tar_filename, Subdirectory subdir)
847 TarList::iterator it = _tar_list[subdir].find(tar_filename);
848 /* We don't know the file. */
849 if (it == _tar_list[subdir].end()) return false;
851 const auto &dirname = (*it).second;
853 /* The file doesn't have a sub directory! */
854 if (dirname.empty()) {
855 DEBUG(misc, 1, "Extracting %s failed; archive rejected, the contents must be in a sub directory", tar_filename.c_str());
856 return false;
859 std::string filename = tar_filename;
860 auto p = filename.find_last_of(PATHSEPCHAR);
861 /* The file's path does not have a separator? */
862 if (p == std::string::npos) return false;
864 filename.replace(p + 1, std::string::npos, dirname);
865 DEBUG(misc, 8, "Extracting %s to directory %s", tar_filename.c_str(), filename.c_str());
866 FioCreateDirectory(filename);
868 for (TarFileList::iterator it2 = _tar_filelist[subdir].begin(); it2 != _tar_filelist[subdir].end(); it2++) {
869 if (tar_filename != it2->second.tar_filename) continue;
871 filename.replace(p + 1, std::string::npos, it2->first);
873 DEBUG(misc, 9, " extracting %s", filename.c_str());
875 /* First open the file in the .tar. */
876 size_t to_copy = 0;
877 std::unique_ptr<FILE, FileDeleter> in(FioFOpenFileTar(it2->second, &to_copy));
878 if (!in) {
879 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename.c_str(), tar_filename.c_str());
880 return false;
883 /* Now open the 'output' file. */
884 std::unique_ptr<FILE, FileDeleter> out(fopen(filename.c_str(), "wb"));
885 if (!out) {
886 DEBUG(misc, 6, "Extracting %s failed; could not open %s", filename.c_str(), filename.c_str());
887 return false;
890 /* Now read from the tar and write it into the file. */
891 char buffer[4096];
892 size_t read;
893 for (; to_copy != 0; to_copy -= read) {
894 read = fread(buffer, 1, std::min(to_copy, lengthof(buffer)), in.get());
895 if (read <= 0 || fwrite(buffer, 1, read, out.get()) != read) break;
898 if (to_copy != 0) {
899 DEBUG(misc, 6, "Extracting %s failed; still %i bytes to copy", filename.c_str(), (int)to_copy);
900 return false;
904 DEBUG(misc, 9, " extraction successful");
905 return true;
908 #if defined(_WIN32)
910 * Determine the base (personal dir and game data dir) paths
911 * @param exe the path from the current path to the executable
912 * @note defined in the OS related files (os2.cpp, win32.cpp, unix.cpp etc)
914 extern void DetermineBasePaths(const char *exe);
915 #else /* defined(_WIN32) */
918 * Changes the working directory to the path of the give executable.
919 * For OSX application bundles '.app' is the required extension of the bundle,
920 * so when we crop the path to there, when can remove the name of the bundle
921 * in the same way we remove the name from the executable name.
922 * @param exe the path to the executable
924 static bool ChangeWorkingDirectoryToExecutable(const char *exe)
926 char tmp[MAX_PATH];
927 strecpy(tmp, exe, lastof(tmp));
929 bool success = false;
930 #ifdef WITH_COCOA
931 char *app_bundle = strchr(tmp, '.');
932 while (app_bundle != nullptr && strncasecmp(app_bundle, ".app", 4) != 0) app_bundle = strchr(&app_bundle[1], '.');
934 if (app_bundle != nullptr) *app_bundle = '\0';
935 #endif /* WITH_COCOA */
936 char *s = strrchr(tmp, PATHSEPCHAR);
937 if (s != nullptr) {
938 *s = '\0';
939 if (chdir(tmp) != 0) {
940 DEBUG(misc, 0, "Directory with the binary does not exist?");
941 } else {
942 success = true;
945 return success;
949 * Whether we should scan the working directory.
950 * It should not be scanned if it's the root or
951 * the home directory as in both cases a big data
952 * directory can cause huge amounts of unrelated
953 * files scanned. Furthermore there are nearly no
954 * use cases for the home/root directory to have
955 * OpenTTD directories.
956 * @return true if it should be scanned.
958 bool DoScanWorkingDirectory()
960 /* No working directory, so nothing to do. */
961 if (_searchpaths[SP_WORKING_DIR].empty()) return false;
963 /* Working directory is root, so do nothing. */
964 if (_searchpaths[SP_WORKING_DIR] == PATHSEP) return false;
966 /* No personal/home directory, so the working directory won't be that. */
967 if (_searchpaths[SP_PERSONAL_DIR].empty()) return true;
969 std::string tmp = _searchpaths[SP_WORKING_DIR] + PERSONAL_DIR;
970 AppendPathSeparator(tmp);
972 return _searchpaths[SP_PERSONAL_DIR] != tmp;
976 * Gets the home directory of the user.
977 * May return an empty string in the unlikely scenario that the home directory cannot be found.
978 * @return User's home directory
980 static std::string GetHomeDir()
982 #ifdef __HAIKU__
983 BPath path;
984 find_directory(B_USER_SETTINGS_DIRECTORY, &path);
985 return std::string(path.Path());
986 #else
987 const char *home_env = getenv("HOME"); // Stack var, shouldn't be freed
988 if (home_env != nullptr) return std::string(home_env);
990 const struct passwd *pw = getpwuid(getuid());
991 if (pw != nullptr) return std::string(pw->pw_dir);
992 #endif
993 return {};
997 * Determine the base (personal dir and game data dir) paths
998 * @param exe the path to the executable
1000 void DetermineBasePaths(const char *exe)
1002 std::string tmp;
1003 const std::string homedir = GetHomeDir();
1004 #ifdef USE_XDG
1005 const char *xdg_data_home = getenv("XDG_DATA_HOME");
1006 if (xdg_data_home != nullptr) {
1007 tmp = xdg_data_home;
1008 tmp += PATHSEP;
1009 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
1010 AppendPathSeparator(tmp);
1011 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
1013 tmp += "content_download";
1014 AppendPathSeparator(tmp);
1015 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG] = tmp;
1016 } else if (!homedir.empty()) {
1017 tmp = homedir;
1018 tmp += PATHSEP ".local" PATHSEP "share" PATHSEP;
1019 tmp += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
1020 AppendPathSeparator(tmp);
1021 _searchpaths[SP_PERSONAL_DIR_XDG] = tmp;
1023 tmp += "content_download";
1024 AppendPathSeparator(tmp);
1025 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG] = tmp;
1026 } else {
1027 _searchpaths[SP_PERSONAL_DIR_XDG].clear();
1028 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR_XDG].clear();
1030 #endif
1032 #if defined(OS2) || !defined(WITH_PERSONAL_DIR)
1033 _searchpaths[SP_PERSONAL_DIR].clear();
1034 #else
1035 if (!homedir.empty()) {
1036 tmp = homedir;
1037 tmp += PATHSEP;
1038 tmp += PERSONAL_DIR;
1039 AppendPathSeparator(tmp);
1040 _searchpaths[SP_PERSONAL_DIR] = tmp;
1042 tmp += "content_download";
1043 AppendPathSeparator(tmp);
1044 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR] = tmp;
1045 } else {
1046 _searchpaths[SP_PERSONAL_DIR].clear();
1047 _searchpaths[SP_AUTODOWNLOAD_PERSONAL_DIR].clear();
1049 #endif
1051 #if defined(WITH_SHARED_DIR)
1052 tmp = SHARED_DIR;
1053 AppendPathSeparator(tmp);
1054 _searchpaths[SP_SHARED_DIR] = tmp;
1055 #else
1056 _searchpaths[SP_SHARED_DIR].clear();
1057 #endif
1059 char cwd[MAX_PATH];
1060 if (getcwd(cwd, MAX_PATH) == nullptr) *cwd = '\0';
1062 if (_config_file.empty()) {
1063 /* Get the path to working directory of OpenTTD. */
1064 tmp = cwd;
1065 AppendPathSeparator(tmp);
1066 _searchpaths[SP_WORKING_DIR] = tmp;
1068 _do_scan_working_directory = DoScanWorkingDirectory();
1069 } else {
1070 /* Use the folder of the config file as working directory. */
1071 size_t end = _config_file.find_last_of(PATHSEPCHAR);
1072 if (end == std::string::npos) {
1073 /* _config_file is not in a folder, so use current directory. */
1074 tmp = cwd;
1075 AppendPathSeparator(tmp);
1076 _searchpaths[SP_WORKING_DIR] = tmp;
1077 } else {
1078 _searchpaths[SP_WORKING_DIR] = _config_file.substr(0, end + 1);
1082 /* Change the working directory to that one of the executable */
1083 if (ChangeWorkingDirectoryToExecutable(exe)) {
1084 char buf[MAX_PATH];
1085 if (getcwd(buf, lengthof(buf)) == nullptr) {
1086 tmp.clear();
1087 } else {
1088 tmp = buf;
1090 AppendPathSeparator(tmp);
1091 _searchpaths[SP_BINARY_DIR] = tmp;
1092 } else {
1093 _searchpaths[SP_BINARY_DIR].clear();
1096 if (cwd[0] != '\0') {
1097 /* Go back to the current working directory. */
1098 if (chdir(cwd) != 0) {
1099 DEBUG(misc, 0, "Failed to return to working directory!");
1103 #if !defined(GLOBAL_DATA_DIR)
1104 _searchpaths[SP_INSTALLATION_DIR].clear();
1105 #else
1106 tmp = GLOBAL_DATA_DIR;
1107 AppendPathSeparator(tmp);
1108 _searchpaths[SP_INSTALLATION_DIR] = tmp;
1109 #endif
1110 #ifdef WITH_COCOA
1111 extern void CocoaSetApplicationBundleDir();
1112 CocoaSetApplicationBundleDir();
1113 #else
1114 _searchpaths[SP_APPLICATION_BUNDLE_DIR].clear();
1115 #endif
1117 #endif /* defined(_WIN32) */
1119 std::string _personal_dir;
1122 * Acquire the base paths (personal dir and game data dir),
1123 * fill all other paths (save dir, autosave dir etc) and
1124 * make the save and scenario directories.
1125 * @param exe the path from the current path to the executable
1127 void DeterminePaths(const char *exe)
1129 DetermineBasePaths(exe);
1131 #ifdef USE_XDG
1132 std::string config_home;
1133 const std::string homedir = GetHomeDir();
1134 const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
1135 if (xdg_config_home != nullptr) {
1136 config_home = xdg_config_home;
1137 config_home += PATHSEP;
1138 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
1139 } else if (!homedir.empty()) {
1140 /* Defaults to ~/.config */
1141 config_home = homedir;
1142 config_home += PATHSEP ".config" PATHSEP;
1143 config_home += PERSONAL_DIR[0] == '.' ? &PERSONAL_DIR[1] : PERSONAL_DIR;
1145 AppendPathSeparator(config_home);
1146 #endif
1148 Searchpath sp;
1149 FOR_ALL_SEARCHPATHS(sp) {
1150 if (sp == SP_WORKING_DIR && !_do_scan_working_directory) continue;
1151 DEBUG(misc, 4, "%s added as search path", _searchpaths[sp].c_str());
1154 std::string config_dir;
1155 if (!_config_file.empty()) {
1156 config_dir = _searchpaths[SP_WORKING_DIR];
1157 } else {
1158 std::string personal_dir = FioFindFullPath(BASE_DIR, "openttd.cfg");
1159 if (!personal_dir.empty()) {
1160 auto end = personal_dir.find_last_of(PATHSEPCHAR);
1161 if (end != std::string::npos) personal_dir.erase(end + 1);
1162 config_dir = personal_dir;
1163 } else {
1164 #ifdef USE_XDG
1165 /* No previous configuration file found. Use the configuration folder from XDG. */
1166 config_dir = config_home;
1167 #else
1168 static const Searchpath new_openttd_cfg_order[] = {
1169 SP_PERSONAL_DIR, SP_BINARY_DIR, SP_WORKING_DIR, SP_SHARED_DIR, SP_INSTALLATION_DIR
1172 config_dir.clear();
1173 for (uint i = 0; i < lengthof(new_openttd_cfg_order); i++) {
1174 if (IsValidSearchPath(new_openttd_cfg_order[i])) {
1175 config_dir = _searchpaths[new_openttd_cfg_order[i]];
1176 break;
1179 #endif
1181 _config_file = config_dir + "openttd.cfg";
1184 DEBUG(misc, 3, "%s found as config directory", config_dir.c_str());
1186 _highscore_file = config_dir + "hs.dat";
1187 extern std::string _hotkeys_file;
1188 _hotkeys_file = config_dir + "hotkeys.cfg";
1189 extern std::string _windows_file;
1190 _windows_file = config_dir + "windows.cfg";
1192 #ifdef USE_XDG
1193 if (config_dir == config_home) {
1194 /* We are using the XDG configuration home for the config file,
1195 * then store the rest in the XDG data home folder. */
1196 _personal_dir = _searchpaths[SP_PERSONAL_DIR_XDG];
1197 } else
1198 #endif
1200 _personal_dir = config_dir;
1203 /* Make the necessary folders */
1204 FioCreateDirectory(config_dir);
1205 #if defined(WITH_PERSONAL_DIR)
1206 FioCreateDirectory(_personal_dir);
1207 #endif
1209 DEBUG(misc, 3, "%s found as personal directory", _personal_dir.c_str());
1211 static const Subdirectory default_subdirs[] = {
1212 SAVE_DIR, AUTOSAVE_DIR, SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR, SCREENSHOT_DIR
1215 for (uint i = 0; i < lengthof(default_subdirs); i++) {
1216 FioCreateDirectory(_personal_dir + _subdirs[default_subdirs[i]]);
1219 /* If we have network we make a directory for the autodownloading of content */
1220 _searchpaths[SP_AUTODOWNLOAD_DIR] = _personal_dir + "content_download" PATHSEP;
1221 FioCreateDirectory(_searchpaths[SP_AUTODOWNLOAD_DIR]);
1223 /* Create the directory for each of the types of content */
1224 const Subdirectory dirs[] = { SCENARIO_DIR, HEIGHTMAP_DIR, BASESET_DIR, NEWGRF_DIR, AI_DIR, AI_LIBRARY_DIR, GAME_DIR, GAME_LIBRARY_DIR };
1225 for (uint i = 0; i < lengthof(dirs); i++) {
1226 FioCreateDirectory(FioGetDirectory(SP_AUTODOWNLOAD_DIR, dirs[i]));
1229 extern std::string _log_file;
1230 _log_file = _personal_dir + "openttd.log";
1234 * Sanitizes a filename, i.e. removes all illegal characters from it.
1235 * @param filename the "\0" terminated filename
1237 void SanitizeFilename(char *filename)
1239 for (; *filename != '\0'; filename++) {
1240 switch (*filename) {
1241 /* The following characters are not allowed in filenames
1242 * on at least one of the supported operating systems: */
1243 case ':': case '\\': case '*': case '?': case '/':
1244 case '<': case '>': case '|': case '"':
1245 *filename = '_';
1246 break;
1252 * Load a file into memory.
1253 * @param filename Name of the file to load.
1254 * @param[out] lenp Length of loaded data.
1255 * @param maxsize Maximum size to load.
1256 * @return Pointer to new memory containing the loaded data, or \c nullptr if loading failed.
1257 * @note If \a maxsize less than the length of the file, loading fails.
1259 std::unique_ptr<char> ReadFileToMem(const std::string &filename, size_t &lenp, size_t maxsize)
1261 FILE *in = fopen(filename.c_str(), "rb");
1262 if (in == nullptr) return nullptr;
1264 FileCloser fc(in);
1266 fseek(in, 0, SEEK_END);
1267 size_t len = ftell(in);
1268 fseek(in, 0, SEEK_SET);
1269 if (len > maxsize) return nullptr;
1271 /* std::unique_ptr assumes new/delete unless a custom deleter is supplied.
1272 * As we don't want to have to carry that deleter all over the place, use
1273 * new directly to allocate the memory instead of malloc. */
1274 std::unique_ptr<char> mem(static_cast<char *>(::operator new(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 const char *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 (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) 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);