openMSX
FileOperations.cc
Go to the documentation of this file.
1#ifdef _WIN32
2#ifndef _WIN32_IE
3#define _WIN32_IE 0x0500 // For SHGetSpecialFolderPathW with MinGW
4#endif
5#include "utf8_checked.hh"
6#include <windows.h>
7#include <shlobj.h>
8#include <shellapi.h>
9#include <io.h>
10#include <direct.h>
11#include <ctype.h>
12#include <cstdlib>
13#include <cstring>
14#include <algorithm>
15#else // ifdef _WIN32_ ...
16#include <sys/types.h>
17#include <pwd.h>
18#include <climits>
19#include <unistd.h>
20#endif // ifdef _WIN32_ ... else ...
21
22#include "openmsx.hh" // for ad_printf
23
24#include "systemfuncs.hh"
25
26#if HAVE_NFTW
27#include <ftw.h>
28#endif
29
30#if defined(PATH_MAX)
31#define MAXPATHLEN PATH_MAX
32#elif defined(MAX_PATH)
33#define MAXPATHLEN MAX_PATH
34#else
35#define MAXPATHLEN 4096
36#endif
37
38
39#ifdef __APPLE__
40#include "FileOperationsMac.hh"
41#endif
42
43#include "ReadDir.hh"
44#include "FileOperations.hh"
45#include "FileException.hh"
46#include "StringOp.hh"
47#include "unistdp.hh"
48#include "one_of.hh"
49#include "ranges.hh"
50#include "strCat.hh"
51#include "build-info.hh"
52#include <algorithm>
53#include <array>
54#include <sstream>
55#include <cerrno>
56#include <cstdlib>
57#include <stdexcept>
58#include <cassert>
59#include <iterator>
60
61#ifndef _MSC_VER
62#include <dirent.h>
63#endif
64
65#if PLATFORM_ANDROID
66#include "SDL_system.h" // for GetExternalStorage stuff
67#endif
68
69using std::string;
70using std::string_view;
71
72#ifdef _WIN32
73using namespace utf8;
74#endif
75
77
78bool needsTildeExpansion(std::string_view path)
79{
80 return !path.empty() && (path[0] == '~');
81}
82
83string expandTilde(string path)
84{
85 if (!needsTildeExpansion(path)) {
86 return path;
87 }
88 auto pos = path.find_first_of('/');
89 string_view user = ((path.size() == 1) || (pos == 1))
90 ? string_view{}
91 : string_view(path).substr(1, (pos == string::npos) ? pos : pos - 1);
92 string result = getUserHomeDir(user);
93 if (result.empty()) {
94 // failed to find homedir, return the path unchanged
95 return path;
96 }
97 if (pos == string_view::npos) {
98 return result;
99 }
100 if (result.back() != '/') {
101 result += '/';
102 }
103 string_view last = string_view(path).substr(pos + 1);
104 result.append(last.data(), last.size());
105 return result;
106}
107
108void mkdir(zstring_view path, mode_t mode)
109{
110#ifdef _WIN32
111 (void)&mode; // Suppress C4100 VC++ warning
112 if ((path == "/") || path.ends_with(':') || path.ends_with(":/")) {
113 return;
114 }
115 int result = _wmkdir(utf8to16(getNativePath(string(path))).c_str());
116#else
117 int result = ::mkdir(path.c_str(), mode);
118#endif
119 if (result && (errno != EEXIST)) {
120 throw FileException("Error creating dir ", path);
121 }
122}
123
124static bool isUNCPath(string_view path)
125{
126#ifdef _WIN32
127 return path.starts_with("//") || path.starts_with("\\\\");
128#else
129 (void)path;
130 return false;
131#endif
132}
133
134void mkdirp(string path)
135{
136 if (path.empty()) {
137 return;
138 }
139
140 // We may receive platform-specific paths here, so conventionalize early
141 path = getConventionalPath(std::move(path));
142
143 // If the directory already exists, don't try to recreate it
144 if (isDirectory(path))
145 return;
146
147 // If the path is a UNC path (e.g. \\server\share) then the first two paths in the loop below will be \ and \\server
148 // If the path is an absolute path (e.g. c:\foo\bar) then the first path in the loop will be C:
149 // None of those are valid directory paths, so we skip over them and don't call mkdir.
150 // Relative paths are fine, since each segment in the path is significant.
151 int skip = isUNCPath(path) ? 2 :
152 isAbsolutePath(path) ? 1 : 0;
153 string::size_type pos = 0;
154 do {
155 pos = path.find_first_of('/', pos + 1);
156 if (skip) {
157 skip--;
158 continue;
159 }
160 mkdir(path.substr(0, pos), 0755);
161 } while (pos != string::npos);
162
163 if (!isDirectory(path)) {
164 throw FileException("Error creating dir ", path);
165 }
166}
167
169{
170#ifdef _WIN32
171 return _wunlink(utf8to16(path).c_str());
172#else
173 return ::unlink(path.c_str());
174#endif
175}
176
178{
179#ifdef _WIN32
180 return _wrmdir(utf8to16(path).c_str());
181#else
182 return ::rmdir(path.c_str());
183#endif
184}
185
186#ifdef _WIN32
188{
189 std::wstring pathW = utf8to16(path);
190
191 SHFILEOPSTRUCTW rmdirFileOp;
192 rmdirFileOp.hwnd = nullptr;
193 rmdirFileOp.wFunc = FO_DELETE;
194 rmdirFileOp.pFrom = pathW.c_str();
195 rmdirFileOp.pTo = nullptr;
196 rmdirFileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
197 rmdirFileOp.fAnyOperationsAborted = FALSE;
198 rmdirFileOp.hNameMappings = nullptr;
199 rmdirFileOp.lpszProgressTitle = nullptr;
200
201 return SHFileOperationW(&rmdirFileOp);
202}
203#elif HAVE_NFTW
204static int deleteRecursive_cb(const char* fpath, const struct stat* /*sb*/,
205 int /*typeflag*/, struct FTW* /*ftwbuf*/)
206{
207 return remove(fpath);
208}
210{
211 return nftw(path.c_str(), deleteRecursive_cb, 64, FTW_DEPTH | FTW_PHYS);
212}
213#else
214// This is a platform independent version of deleteRecursive() (it builds on
215// top of helper routines that _are_ platform specific). Though I still prefer
216// the two platform specific deleteRecursive() routines above because they are
217// likely more optimized and likely contain less bugs than this version (e.g.
218// we're walking over the entries in a directory while simultaneously deleting
219// entries in that same directory. Although this seems to work fine, I'm not
220// 100% sure our ReadDir 'emulation code' for windows covers all corner cases.
221// While the windows version above very likely does handle everything).
222int deleteRecursive(const std::string& path)
223{
224 if (isDirectory(path)) {
225 {
226 ReadDir dir(path);
227 while (dirent* d = dir.getEntry()) {
228 int err = deleteRecursive(d->d_name);
229 if (err) return err;
230 }
231 }
232 return rmdir(path);
233 } else {
234 return unlink(path);
235 }
236}
237#endif
238
240{
241 // Mode must contain a 'b' character. On unix this doesn't make any
242 // difference. But on windows this is required to open the file
243 // in binary mode.
244 assert(mode.find('b') != std::string::npos);
245#ifdef _WIN32
246 return FILE_t(_wfopen(utf8to16(filename).c_str(),
247 utf8to16(mode).c_str()));
248#else
249 return FILE_t(fopen(filename.c_str(), mode.c_str()));
250#endif
251}
252
253void openOfStream(std::ofstream& stream, zstring_view filename)
254{
255#if defined _WIN32 && defined _MSC_VER
256 // MinGW 3.x doesn't support ofstream.open(wchar_t*)
257 // TODO - this means that unicode text may not work right here
258 stream.open(utf8to16(filename).c_str());
259#else
260 stream.open(filename.c_str());
261#endif
262}
263
264void openOfStream(std::ofstream& stream, zstring_view filename,
265 std::ios_base::openmode mode)
266{
267#if defined _WIN32 && defined _MSC_VER
268 // MinGW 3.x doesn't support ofstream.open(wchar_t*)
269 // TODO - this means that unicode text may not work right here
270 stream.open(utf8to16(filename).c_str(), mode);
271#else
272 stream.open(filename.c_str(), mode);
273#endif
274}
275
276string_view getFilename(string_view path)
277{
278 if (auto pos = path.rfind('/'); pos != string_view::npos) {
279 return path.substr(pos + 1);
280 }
281 return path;
282}
283
284string_view getDirName(string_view path)
285{
286 if (auto pos = path.rfind('/'); pos != string_view::npos) {
287 return path.substr(0, pos + 1);
288 }
289 return {};
290}
291
292string_view getExtension(string_view path)
293{
294 string_view filename = getFilename(path);
295 if (auto pos = filename.rfind('.'); pos != string_view::npos) {
296 return filename.substr(pos);
297 }
298 return {};
299}
300
301string_view stripExtension(string_view path)
302{
303 if (auto pos = path.rfind('.'); pos != string_view::npos) {
304 return path.substr(0, pos);
305 }
306 return path;
307}
308
309string join(string_view part1, string_view part2)
310{
311 if (part1.empty() || isAbsolutePath(part2)) {
312 return string(part2);
313 }
314 if (part1.back() == '/') {
315 return strCat(part1, part2);
316 }
317 return strCat(part1, '/', part2);
318}
319string join(string_view part1, string_view part2, string_view part3)
320{
321 return join(part1, join(part2, part3));
322}
323
324string join(string_view part1, string_view part2,
325 string_view part3, string_view part4)
326{
327 return join(part1, join(part2, join(part3, part4)));
328}
329
330#ifdef _WIN32
331string getNativePath(string path)
332{
333 ranges::replace(path, '/', '\\');
334 return path;
335}
336
337string getConventionalPath(string path)
338{
339 ranges::replace(path, '\\', '/');
340 return path;
341}
342#endif
343
345{
346#ifdef _WIN32
347 std::array<wchar_t, MAXPATHLEN> bufW;
348 const wchar_t* result = _wgetcwd(bufW.data(), bufW.size());
349 if (!result) {
350 throw FileException("Couldn't get current working directory.");
351 }
352 return utf16to8(result);
353#else
354 std::array<char, MAXPATHLEN> buf;
355 char* result = getcwd(buf.data(), buf.size());
356 if (!result) {
357 throw FileException("Couldn't get current working directory.");
358 }
359 return buf.data();
360#endif
361}
362
363string getAbsolutePath(string_view path)
364{
365 // In rare cases getCurrentWorkingDirectory() can throw,
366 // so only call it when really necessary.
367 if (isAbsolutePath(path)) {
368 return string(path);
369 }
370 return join(getCurrentWorkingDirectory(), path);
371}
372
373bool isAbsolutePath(string_view path)
374{
375 if (isUNCPath(path)) return true;
376#ifdef _WIN32
377 if ((path.size() >= 3) && (path[1] == ':') && (path[2] == one_of('/', '\\'))) {
378 char drive = tolower(path[0]);
379 if (('a' <= drive) && (drive <= 'z')) {
380 return true;
381 }
382 }
383#endif
384 return !path.empty() && (path[0] == '/');
385}
386
387string getUserHomeDir(string_view username)
388{
389#ifdef _WIN32
390 (void)(&username); // ignore parameter, avoid warning
391
392 std::array<wchar_t, MAXPATHLEN + 1> bufW;
393 if (!SHGetSpecialFolderPathW(nullptr, bufW.data(), CSIDL_PERSONAL, TRUE)) {
394 throw FatalError(
395 "SHGetSpecialFolderPathW failed: ", GetLastError());
396 }
397
398 return getConventionalPath(utf16to8(bufW.data()));
399#else
400 const char* dir = nullptr;
401 struct passwd* pw = nullptr;
402 if (username.empty()) {
403 dir = getenv("HOME");
404 if (!dir) {
405 pw = getpwuid(getuid());
406 }
407 } else {
408 pw = getpwnam(string(username).c_str());
409 }
410 if (pw) {
411 dir = pw->pw_dir;
412 }
413 return dir ? dir : string{};
414#endif
415}
416
417const string& getUserOpenMSXDir()
418{
419 static const string OPENMSX_DIR = []() -> string {
420 if (const char* home = getenv("OPENMSX_HOME")) {
421 return home;
422 }
423#ifdef _WIN32
424 return expandTilde("~/openMSX");
425#elif PLATFORM_ANDROID
426 // TODO: do something to query whether the storage is available
427 // via SDL_AndroidGetExternalStorageState
428 return strCat(SDL_AndroidGetExternalStoragePath(), "/openMSX");
429#else
430 return expandTilde("~/.openMSX");
431#endif
432 }();
433 return OPENMSX_DIR;
434}
435
436const string& getUserDataDir()
437{
438 static std::optional<string> result;
439 if (!result) {
440 const char* const NAME = "OPENMSX_USER_DATA";
441 const char* value = getenv(NAME);
442 result = value ? value : getUserOpenMSXDir() + "/share";
443 }
444 return *result;
445}
446
447const string& getSystemDataDir()
448{
449 static std::optional<string> result;
450 if (!result) result = []() -> string {
451 if (const char* value = getenv("OPENMSX_SYSTEM_DATA")) {
452 return value;
453 }
454#ifdef _WIN32
455 std::array<wchar_t, MAXPATHLEN + 1> bufW;
456 if (int res = GetModuleFileNameW(nullptr, bufW.data(), DWORD(bufW.size()));
457 !res) {
458 throw FatalError(
459 "Cannot detect openMSX directory. GetModuleFileNameW failed: ",
460 GetLastError());
461 }
462
463 string filename = utf16to8(bufW.data());
464 auto pos = filename.find_last_of('\\');
465 if (pos == string::npos) {
466 throw FatalError("openMSX is not in directory!?");
467 }
468 return getConventionalPath(filename.substr(0, pos)) + "/share";
469#elif defined(__APPLE__)
470 return findResourceDir("share");
471#elif PLATFORM_ANDROID
472 return getAbsolutePath("openmsx_system");
473#else
474 // defined in build-info.hh (default /opt/openMSX/share)
475 return DATADIR;
476#endif
477 }();
478 return *result;
479}
480
481const string& getSystemDocDir()
482{
483 static std::optional<string> result;
484 if (!result) result = []() -> string {
485#ifdef _WIN32
486 std::array<wchar_t, MAXPATHLEN + 1> bufW;
487 if (int res = GetModuleFileNameW(nullptr, bufW.data(), DWORD(bufW.size()));
488 !res) {
489 throw FatalError(
490 "Cannot detect openMSX directory. GetModuleFileNameW failed: ",
491 GetLastError());
492 }
493
494 string filename = utf16to8(bufW.data());
495 auto pos = filename.find_last_of('\\');
496 if (pos == string::npos) {
497 throw FatalError("openMSX is not in directory!?");
498 }
499 return getConventionalPath(filename.substr(0, pos)) + "/doc";
500#elif defined(__APPLE__)
501 return findResourceDir("doc");
502#elif PLATFORM_ANDROID
503 return getAbsolutePath("openmsx_system"); // TODO: currently no docs are installed on Android
504#else
505 // defined in build-info.hh (default /opt/openMSX/doc)
506 return DOCDIR;
507#endif
508 }();
509 return *result;
510}
511
512#ifdef _WIN32
513static bool driveExists(char driveLetter)
514{
515 std::array<char, 3> buf = {driveLetter, ':', 0};
516 return GetFileAttributesA(buf.data()) != INVALID_FILE_ATTRIBUTES;
517}
518#endif
519
520#ifdef _WIN32
521string expandCurrentDirFromDrive(string path)
522{
523 string result = path;
524 if (((path.size() == 2) && (path[1] == ':')) ||
525 ((path.size() >= 3) && (path[1] == ':') && (path[2] != '/'))) {
526 // get current directory for this drive
527 unsigned char drive = tolower(path[0]);
528 if (('a' <= drive) && (drive <= 'z')) {
529 std::array<wchar_t, MAXPATHLEN + 1> bufW;
530 if (driveExists(drive) &&
531 _wgetdcwd(drive - 'a' + 1, bufW.data(), MAXPATHLEN)) {
532 result = getConventionalPath(utf16to8(bufW.data()));
533 if (result.back() != '/') {
534 result += '/';
535 }
536 if (path.size() > 2) {
537 auto tmp = std::string_view(path).substr(2);
538 result.append(tmp.data(), tmp.size());
539 }
540 }
541 }
542 }
543 return result;
544}
545#endif
546
547std::optional<Stat> getStat(zstring_view filename)
548{
549 std::optional<Stat> st;
550 st.emplace(); // allocate relatively large 'struct stat' in the return slot
551
552#ifdef _WIN32
553 std::string filename2(filename);
554 // workaround for VC++: strip trailing slashes (but keep it if it's the
555 // only character in the path)
556 if (auto pos = filename2.find_last_not_of('/'); pos != string::npos) {
557 filename2.resize(pos + 1);
558 } else {
559 // string was either empty or a (sequence of) '/' character(s)
560 if (!filename2.empty()) filename2.resize(1);
561 }
562 if (_wstat(utf8to16(filename2).c_str(), &*st)) {
563 st.reset();
564 }
565#else
566 if (stat(filename.c_str(), &*st)) {
567 st.reset();
568 }
569#endif
570
571 return st; // we count on NRVO to eliminate memcpy of 'struct stat'
572}
573
574bool isRegularFile(const Stat& st)
575{
576 return S_ISREG(st.st_mode);
577}
579{
580 auto st = getStat(filename);
581 return st && isRegularFile(*st);
582}
583
584bool isDirectory(const Stat& st)
585{
586 return S_ISDIR(st.st_mode);
587}
588
590{
591 auto st = getStat(directory);
592 return st && isDirectory(*st);
593}
594
595bool exists(zstring_view filename)
596{
597 return static_cast<bool>(getStat(filename));
598}
599
600static unsigned getNextNum(const dirent* d, string_view prefix, string_view extension,
601 unsigned nofDigits)
602{
603 auto extensionLen = extension.size();
604 auto prefixLen = prefix.size();
605 string_view name(d->d_name);
606
607 if ((name.size() != (prefixLen + nofDigits + extensionLen)) ||
608 (!name.starts_with(prefix)) ||
609 (name.substr(prefixLen + nofDigits, extensionLen) != extension)) {
610 return 0;
611 }
612 return StringOp::stringToBase<10, unsigned>(name.substr(prefixLen, nofDigits)).value_or(0);
613}
614
616 string_view directory, string_view prefix, string_view extension, bool addSeparator)
617{
618 std::string newPrefix;
619 if (addSeparator) {
620 newPrefix = strCat(prefix, ((prefix.find(' ') != std::string_view::npos) ? ' ' : '_'));
621 prefix = newPrefix;
622 }
623
624 const unsigned nofDigits = 4;
625
626 unsigned max_num = 0;
627
628 string dirName = join(getUserOpenMSXDir(), directory);
629 try {
630 mkdirp(dirName);
631 } catch (FileException&) {
632 // ignore
633 }
634
635 ReadDir dir(dirName);
636 while (auto* d = dir.getEntry()) {
637 max_num = std::max(max_num, getNextNum(d, prefix, extension, nofDigits));
638 }
639
640 std::ostringstream os;
641 os << FileOperations::join(dirName, prefix);
642 os.width(nofDigits);
643 os.fill('0');
644 os << (max_num + 1) << extension;
645 return os.str();
646}
647
649 string_view argument, string_view directory,
650 string_view prefix, string_view extension)
651{
652 if (argument.empty()) {
653 // directory is also created when needed
654 return getNextNumberedFileName(directory, prefix, extension);
655 }
656
657 string filename(argument);
658 if (getDirName(filename).empty()) {
659 // no dir given, use standard dir (and create it)
660 string dir = strCat(getUserOpenMSXDir(), '/', directory);
661 mkdirp(dir);
662 filename = strCat(dir, '/', filename);
663 } else {
664 filename = expandTilde(std::move(filename));
665 }
666
667 if (!filename.ends_with(extension) && !exists(filename)) {
668 // Expected extension not already given, append it. But only
669 // when the filename without extension doesn't already exist.
670 // Without this exception stuff like 'soundlog start /dev/null'
671 // reports an error " ... error opening file /dev/null.wav."
672 filename.append(extension.data(), extension.size());
673 }
674 return filename;
675}
676
678{
679#ifdef _WIN32
680 if (DWORD len = GetTempPathW(0, nullptr)) {
681 std::wstring bufW(len, L'\0'); // TODO use c++23 resize_and_overwrite()
682 if (int len2 = GetTempPathW(len, bufW.data())) {
683 bufW.resize(len2);
684 // Strip last backslash
685 if (bufW.ends_with(L'\\')) {
686 bufW.pop_back();
687 }
688 return utf16to8(bufW);
689 }
690 }
691 throw FatalError("GetTempPathW failed: ", GetLastError());
692#elif PLATFORM_ANDROID
693 string result = getSystemDataDir() + "/tmp";
694 return result;
695#else
696 const char* result = nullptr;
697 if (!result) result = getenv("TMPDIR");
698 if (!result) result = getenv("TMP");
699 if (!result) result = getenv("TEMP");
700 if (!result) {
701 result = "/tmp";
702 }
703 return result;
704#endif
705}
706
707FILE_t openUniqueFile(const std::string& directory, std::string& filename)
708{
709#ifdef _WIN32
710 std::wstring directoryW = utf8to16(directory);
711 std::array<wchar_t, MAX_PATH> filenameW;
712 if (!GetTempFileNameW(directoryW.c_str(), L"msx", 0, filenameW.data())) {
713 throw FileException("GetTempFileNameW failed: ", GetLastError());
714 }
715 filename = utf16to8(filenameW.data());
716 return FILE_t(_wfopen(filenameW.data(), L"wb"));
717#else
718 filename = directory + "/XXXXXX";
719 auto oldMask = umask(S_IRWXO | S_IRWXG);
720 int fd = mkstemp(const_cast<char*>(filename.c_str()));
721 umask(oldMask);
722 if (fd == -1) {
723 throw FileException("Couldnt get temp file name");
724 }
725 return FILE_t(fdopen(fd, "wb"));
726#endif
727}
728
729} // namespace openmsx::FileOperations
#define MAXPATHLEN
Simple wrapper around opendir() / readdir() / closedir() functions.
Definition ReadDir.hh:18
struct dirent * getEntry()
Get directory entry for next file.
Definition ReadDir.hh:27
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr bool ends_with(std::string_view sv) const
constexpr const char * c_str() const
constexpr auto find(char c, size_type pos=0) const
string parseCommandFileArgument(string_view argument, string_view directory, string_view prefix, string_view extension)
Helper function for parsing filename arguments in Tcl commands.
std::string findResourceDir(const std::string &resourceDirName)
bool exists(zstring_view filename)
Does this file (directory) exists?
FILE_t openUniqueFile(const std::string &directory, std::string &filename)
Open a new file with a unique name in the provided directory.
void mkdir(zstring_view path, mode_t mode)
Create the specified directory.
string expandTilde(string path)
Expand the '~' character to the users home directory.
string_view getDirName(string_view path)
Returns the directory portion of a path.
string getNextNumberedFileName(string_view directory, string_view prefix, string_view extension, bool addSeparator)
Gets the next numbered file name with the specified prefix in the specified directory,...
const std::string & getNativePath(const std::string &path)
Returns the path in native path-delimiter.
string getCurrentWorkingDirectory()
Returns the current working directory.
const std::string & expandCurrentDirFromDrive(const std::string &path)
Get the current directory of the specified drive Linux: return the given string unchanged.
string getUserHomeDir(string_view username)
Get user's home directory.
bool isRegularFile(const Stat &st)
string_view getExtension(string_view path)
Returns the extension portion of a path.
const string & getUserOpenMSXDir()
Get the openMSX dir in the user's home directory.
int rmdir(zstring_view path)
Call rmdir() in a platform-independent manner.
string getAbsolutePath(string_view path)
Transform given path into an absolute path.
string_view stripExtension(string_view path)
Returns the path without extension.
string getTempDir()
Get the name of the temp directory on the system.
int deleteRecursive(const std::string &path)
bool isDirectory(const Stat &st)
void openOfStream(std::ofstream &stream, zstring_view filename)
Open an ofstream in a platform-independent manner.
bool isAbsolutePath(string_view path)
Checks whether it's a absolute path or not.
string_view getFilename(string_view path)
Returns the file portion of a path name.
const string & getSystemDataDir()
Get system directory.
std::optional< Stat > getStat(zstring_view filename)
Call stat() and return the stat structure.
FILE_t openFile(zstring_view filename, zstring_view mode)
Call fopen() in a platform-independent manner.
const string & getSystemDocDir()
Get system doc directory.
bool needsTildeExpansion(std::string_view path)
Returns true iff expandTilde(s) would have an effect.
const std::string & getConventionalPath(const std::string &path)
Returns the path in conventional path-delimiter.
void mkdirp(string path)
Acts like the unix command "mkdir -p".
const string & getUserDataDir()
Get the openMSX data dir in the user's home directory.
std::unique_ptr< FILE, FClose > FILE_t
int unlink(zstring_view path)
Call unlink() in a platform-independent manner.
string join(string_view part1, string_view part2)
Join two paths.
constexpr void replace(ForwardRange &&range, const T &old_value, const T &new_value)
Definition ranges.hh:303
octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result)
std::string strCat()
Definition strCat.hh:703