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 "vla.hh"
7 #include <windows.h>
8 #include <shlobj.h>
9 #include <shellapi.h>
10 #include <io.h>
11 #include <direct.h>
12 #include <ctype.h>
13 #include <cstdlib>
14 #include <cstring>
15 #include <algorithm>
16 #else // ifdef _WIN32_ ...
17 #include <sys/types.h>
18 #include <pwd.h>
19 #include <climits>
20 #include <unistd.h>
21 #endif // ifdef _WIN32_ ... else ...
22 
23 #include "openmsx.hh" // for ad_printf
24 
25 #include "systemfuncs.hh"
26 
27 #if HAVE_NFTW
28 #include <ftw.h>
29 #endif
30 
31 #if defined(PATH_MAX)
32 #define MAXPATHLEN PATH_MAX
33 #elif defined(MAX_PATH)
34 #define MAXPATHLEN MAX_PATH
35 #else
36 #define MAXPATHLEN 4096
37 #endif
38 
39 
40 #ifdef __APPLE__
41 #include <Carbon/Carbon.h>
42 #endif
43 
44 #include "ReadDir.hh"
45 #include "FileOperations.hh"
46 #include "FileException.hh"
47 #include "StringOp.hh"
48 #include "statp.hh"
49 #include "unistdp.hh"
50 #include "ranges.hh"
51 #include "strCat.hh"
52 #include "build-info.hh"
53 #include <algorithm>
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 
69 using std::string;
70 using std::string_view;
71 
72 #ifdef _WIN32
73 using namespace utf8;
74 #endif
75 
77 
78 #ifdef __APPLE__
79 
80 static std::string findShareDir()
81 {
82  // Find bundle location:
83  // for an app folder, this is the outer directory,
84  // for an unbundled executable, it is the executable itself.
85  ProcessSerialNumber psn;
86  if (GetCurrentProcess(&psn) != noErr) {
87  throw FatalError("Failed to get process serial number");
88  }
89  FSRef location;
90  if (GetProcessBundleLocation(&psn, &location) != noErr) {
91  throw FatalError("Failed to get process bundle location");
92  }
93  // Get info about the location.
94  FSCatalogInfo catalogInfo;
95  FSRef parentRef;
96  if (FSGetCatalogInfo(
97  &location, kFSCatInfoVolume | kFSCatInfoNodeFlags,
98  &catalogInfo, nullptr, nullptr, &parentRef
99  ) != noErr) {
100  throw FatalError("Failed to get info about bundle path");
101  }
102  // Get reference to root directory of the volume we are searching.
103  // We will need this later to know when to give up.
104  FSRef root;
105  if (FSGetVolumeInfo(
106  catalogInfo.volume, 0, nullptr, kFSVolInfoNone, nullptr, nullptr, &root
107  ) != noErr) {
108  throw FatalError("Failed to get reference to root directory");
109  }
110  // Make sure we are looking at a directory.
111  if (~catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) {
112  // Location is not a directory, so it is the path to the executable.
113  location = parentRef;
114  }
115  while (true) {
116  // Iterate through the files in the directory.
117  FSIterator iterator;
118  if (FSOpenIterator(&location, kFSIterateFlat, &iterator) != noErr) {
119  throw FatalError("Failed to open iterator");
120  }
121  bool filesLeft = true; // iterator has files left for next call
122  while (filesLeft) {
123  // Get info about several files at a time.
124  const int MAX_SCANNED_FILES = 100;
125  ItemCount actualObjects;
126  FSRef refs[MAX_SCANNED_FILES];
127  FSCatalogInfo catalogInfos[MAX_SCANNED_FILES];
128  HFSUniStr255 names[MAX_SCANNED_FILES];
129  OSErr err = FSGetCatalogInfoBulk(
130  iterator,
131  MAX_SCANNED_FILES,
132  &actualObjects,
133  nullptr /*containerChanged*/,
134  kFSCatInfoNodeFlags,
135  catalogInfos,
136  refs,
137  nullptr /*specs*/,
138  names
139  );
140  if (err == errFSNoMoreItems) {
141  filesLeft = false;
142  } else if (err != noErr) {
143  throw FatalError("Catalog get failed");
144  }
145  for (ItemCount i = 0; i < actualObjects; i++) {
146  // We're only interested in subdirectories.
147  if (catalogInfos[i].nodeFlags & kFSNodeIsDirectoryMask) {
148  // Convert the name to a CFString.
149  CFStringRef name = CFStringCreateWithCharactersNoCopy(
150  kCFAllocatorDefault,
151  names[i].unicode,
152  names[i].length,
153  kCFAllocatorNull // do not deallocate character buffer
154  );
155  // Is this the directory we are looking for?
156  static const CFStringRef SHARE = CFSTR("share");
157  CFComparisonResult cmp = CFStringCompare(SHARE, name, 0);
158  CFRelease(name);
159  if (cmp == kCFCompareEqualTo) {
160  // Clean up.
161  OSErr closeErr = FSCloseIterator(iterator);
162  assert(closeErr == noErr); (void)closeErr;
163  // Get full path of directory.
164  UInt8 path[256];
165  if (FSRefMakePath(
166  &refs[i], path, sizeof(path)) != noErr
167  ) {
168  throw FatalError("Path too long");
169  }
170  return std::string(reinterpret_cast<char*>(path));
171  }
172  }
173  }
174  }
175  OSErr closeErr = FSCloseIterator(iterator);
176  assert(closeErr == noErr); (void)closeErr;
177  // Are we in the root yet?
178  if (FSCompareFSRefs(&location, &root) == noErr) {
179  throw FatalError("Could not find \"share\" directory anywhere");
180  }
181  // Go up one level.
182  if (FSGetCatalogInfo(
183  &location, kFSCatInfoNone, nullptr, nullptr, nullptr, &parentRef
184  ) != noErr
185  ) {
186  throw FatalError("Failed to get parent directory");
187  }
188  location = parentRef;
189  }
190 }
191 
192 #endif // __APPLE__
193 
194 string expandTilde(string_view path)
195 {
196  if (path.empty() || path[0] != '~') {
197  return string(path);
198  }
199  auto pos = path.find_first_of('/');
200  string_view user = ((path.size() == 1) || (pos == 1))
201  ? string_view{}
202  : path.substr(1, (pos == string_view::npos) ? pos : pos - 1);
203  string result = getUserHomeDir(user);
204  if (result.empty()) {
205  // failed to find homedir, return the path unchanged
206  return string(path);
207  }
208  if (pos == string_view::npos) {
209  return result;
210  }
211  if (result.back() != '/') {
212  result += '/';
213  }
214  string_view last = path.substr(pos + 1);
215  result.append(last.data(), last.size());
216  return result;
217 }
218 
219 void mkdir(const string& path, mode_t mode)
220 {
221 #ifdef _WIN32
222  (void)&mode; // Suppress C4100 VC++ warning
223  if ((path == "/") ||
224  StringOp::endsWith(path, ':') ||
225  StringOp::endsWith(path, ":/")) {
226  return;
227  }
228  int result = _wmkdir(utf8to16(getNativePath(path)).c_str());
229 #else
230  int result = ::mkdir(path.c_str(), mode);
231 #endif
232  if (result && (errno != EEXIST)) {
233  throw FileException("Error creating dir ", path);
234  }
235 }
236 
237 static bool isUNCPath(string_view path)
238 {
239 #ifdef _WIN32
240  return StringOp::startsWith(path, "//") || StringOp::startsWith(path, "\\\\");
241 #else
242  (void)path;
243  return false;
244 #endif
245 }
246 
247 void mkdirp(string_view path_)
248 {
249  if (path_.empty()) {
250  return;
251  }
252 
253  // We may receive platform-specific paths here, so conventionalize early
254  string path = getConventionalPath(expandTilde(path_));
255 
256  // If the directory already exists, don't try to recreate it
257  if (isDirectory(path))
258  return;
259 
260  // If the path is a UNC path (e.g. \\server\share) then the first two paths in the loop below will be \ and \\server
261  // If the path is an absolute path (e.g. c:\foo\bar) then the first path in the loop will be C:
262  // None of those are valid directory paths, so we skip over them and don't call mkdir.
263  // Relative paths are fine, since each segment in the path is significant.
264  int skip = isUNCPath(path) ? 2 :
265  isAbsolutePath(path) ? 1 : 0;
266  string::size_type pos = 0;
267  do {
268  pos = path.find_first_of('/', pos + 1);
269  if (skip) {
270  skip--;
271  continue;
272  }
273  mkdir(path.substr(0, pos), 0755);
274  } while (pos != string::npos);
275 
276  if (!isDirectory(path)) {
277  throw FileException("Error creating dir ", path);
278  }
279 }
280 
281 int unlink(const std::string& path)
282 {
283 #ifdef _WIN32
284  return _wunlink(utf8to16(path).c_str());
285 #else
286  return ::unlink(path.c_str());
287 #endif
288 }
289 
290 int rmdir(const std::string& path)
291 {
292 #ifdef _WIN32
293  return _wrmdir(utf8to16(path).c_str());
294 #else
295  return ::rmdir(path.c_str());
296 #endif
297 }
298 
299 #ifdef _WIN32
300 int deleteRecursive(const std::string& path)
301 {
302  std::wstring pathW = utf8to16(path);
303 
304  SHFILEOPSTRUCTW rmdirFileop;
305  rmdirFileop.hwnd = nullptr;
306  rmdirFileop.wFunc = FO_DELETE;
307  rmdirFileop.pFrom = pathW.c_str();
308  rmdirFileop.pTo = nullptr;
309  rmdirFileop.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
310  rmdirFileop.fAnyOperationsAborted = FALSE;
311  rmdirFileop.hNameMappings = nullptr;
312  rmdirFileop.lpszProgressTitle = nullptr;
313 
314  return SHFileOperationW(&rmdirFileop);
315 }
316 #elif HAVE_NFTW
317 static int deleteRecursive_cb(const char* fpath, const struct stat* /*sb*/,
318  int /*typeflag*/, struct FTW* /*ftwbuf*/)
319 {
320  return remove(fpath);
321 }
322 int deleteRecursive(const std::string& path)
323 {
324  return nftw(path.c_str(), deleteRecursive_cb, 64, FTW_DEPTH | FTW_PHYS);
325 }
326 #else
327 // This is a platform independent version of deleteRecursive() (it builds on
328 // top of helper routines that _are_ platform specific). Though I still prefer
329 // the two platform specific deleteRecursive() routines above because they are
330 // likely more optimized and likely contain less bugs than this version (e.g.
331 // we're walking over the entries in a directory while simultaneously deleting
332 // entries in that same directory. Although this seems to work fine, I'm not
333 // 100% sure our ReadDir 'emulation code' for windows covers all corner cases.
334 // While the windows version above very likely does handle everything).
335 int deleteRecursive(const std::string& path)
336 {
337  if (isDirectory(path)) {
338  {
339  ReadDir dir(path);
340  while (dirent* d = dir.getEntry()) {
341  int err = deleteRecursive(d->d_name);
342  if (err) return err;
343  }
344  }
345  return rmdir(path);
346  } else {
347  return unlink(path);
348  }
349 }
350 #endif
351 
352 FILE_t openFile(const std::string& filename, const std::string& mode)
353 {
354  // Mode must contain a 'b' character. On unix this doesn't make any
355  // difference. But on windows this is required to open the file
356  // in binary mode.
357  assert(mode.find('b') != std::string::npos);
358 #ifdef _WIN32
359  return FILE_t(_wfopen(utf8to16(filename).c_str(),
360  utf8to16(mode).c_str()));
361 #else
362  return FILE_t(fopen(filename.c_str(), mode.c_str()));
363 #endif
364 }
365 
366 void openofstream(std::ofstream& stream, const std::string& filename)
367 {
368 #if defined _WIN32 && defined _MSC_VER
369  // MinGW 3.x doesn't support ofstream.open(wchar_t*)
370  // TODO - this means that unicode text may not work right here
371  stream.open(utf8to16(filename).c_str());
372 #else
373  stream.open(filename.c_str());
374 #endif
375 }
376 
377 void openofstream(std::ofstream& stream, const std::string& filename,
378  std::ios_base::openmode mode)
379 {
380 #if defined _WIN32 && defined _MSC_VER
381  // MinGW 3.x doesn't support ofstream.open(wchar_t*)
382  // TODO - this means that unicode text may not work right here
383  stream.open(utf8to16(filename).c_str(), mode);
384 #else
385  stream.open(filename.c_str(), mode);
386 #endif
387 }
388 
389 string_view getFilename(string_view path)
390 {
391  auto pos = path.rfind('/');
392  if (pos == string_view::npos) {
393  return path;
394  } else {
395  return path.substr(pos + 1);
396  }
397 }
398 
399 string_view getDirName(string_view path)
400 {
401  auto pos = path.rfind('/');
402  if (pos == string_view::npos) {
403  return {};
404  } else {
405  return path.substr(0, pos + 1);
406  }
407 }
408 
409 string_view getExtension(string_view path)
410 {
411  string_view filename = getFilename(path);
412  auto pos = filename.rfind('.');
413  if (pos == string_view::npos) {
414  return string_view();
415  } else {
416  return filename.substr(pos);
417  }
418 }
419 
420 string_view stripExtension(string_view path)
421 {
422  auto pos = path.rfind('.');
423  if (pos == string_view::npos) {
424  return path;
425  } else {
426  return path.substr(0, pos);
427  }
428 }
429 
430 string join(string_view part1, string_view part2)
431 {
432  if (part1.empty() || isAbsolutePath(part2)) {
433  return string(part2);
434  }
435  if (part1.back() == '/') {
436  return strCat(part1, part2);
437  }
438  return strCat(part1, '/', part2);
439 }
440 string join(string_view part1, string_view part2, string_view part3)
441 {
442  return join(part1, join(part2, part3));
443 }
444 
445 string join(string_view part1, string_view part2,
446  string_view part3, string_view part4)
447 {
448  return join(part1, join(part2, join(part3, part4)));
449 }
450 
451 string getNativePath(string_view path)
452 {
453  string result(path);
454 #ifdef _WIN32
455  ranges::replace(result, '/', '\\');
456 #endif
457  return result;
458 }
459 
460 string getConventionalPath(string_view path)
461 {
462  string result(path);
463 #ifdef _WIN32
464  ranges::replace(result, '\\', '/');
465 #endif
466  return result;
467 }
468 
470 {
471 #ifdef _WIN32
472  wchar_t bufW[MAXPATHLEN];
473  wchar_t* result = _wgetcwd(bufW, MAXPATHLEN);
474  string buf;
475  if (result) {
476  buf = utf16to8(result);
477  }
478 #else
479  char buf[MAXPATHLEN];
480  char* result = getcwd(buf, MAXPATHLEN);
481 #endif
482  if (!result) {
483  throw FileException("Couldn't get current working directory.");
484  }
485  return buf;
486 }
487 
488 string getAbsolutePath(string_view path)
489 {
490  // In rare cases getCurrentWorkingDirectory() can throw,
491  // so only call it when really necessary.
492  if (isAbsolutePath(path)) {
493  return string(path);
494  }
495  string currentDir = getCurrentWorkingDirectory();
496  return join(currentDir, path);
497 }
498 
499 bool isAbsolutePath(string_view path)
500 {
501  if (isUNCPath(path)) return true;
502 #ifdef _WIN32
503  if ((path.size() >= 3) && (((path[1] == ':') &&
504  ((path[2] == '/') || (path[2] == '\\'))))) {
505  char drive = tolower(path[0]);
506  if (('a' <= drive) && (drive <= 'z')) {
507  return true;
508  }
509  }
510 #endif
511  return !path.empty() && (path[0] == '/');
512 }
513 
514 string getUserHomeDir(string_view username)
515 {
516 #ifdef _WIN32
517  (void)(&username); // ignore parameter, avoid warning
518 
519  wchar_t bufW[MAXPATHLEN + 1];
520  if (!SHGetSpecialFolderPathW(nullptr, bufW, CSIDL_PERSONAL, TRUE)) {
521  throw FatalError(
522  "SHGetSpecialFolderPathW failed: ", GetLastError());
523  }
524 
525  return getConventionalPath(utf16to8(bufW));
526 #else
527  const char* dir = nullptr;
528  struct passwd* pw = nullptr;
529  if (username.empty()) {
530  dir = getenv("HOME");
531  if (!dir) {
532  pw = getpwuid(getuid());
533  }
534  } else {
535  pw = getpwnam(string(username).c_str());
536  }
537  if (pw) {
538  dir = pw->pw_dir;
539  }
540  return dir ? dir : string{};
541 #endif
542 }
543 
544 const string& getUserOpenMSXDir()
545 {
546 #ifdef _WIN32
547  static const string OPENMSX_DIR = expandTilde("~/openMSX");
548 #elif PLATFORM_ANDROID
549  // TODO: do something to query whether the storage is available
550  // via SDL_AndroidGetExternalStorageState
551  static const string OPENMSX_DIR = strCat(SDL_AndroidGetExternalStoragePath(), "/openMSX");
552 #else
553  static const string OPENMSX_DIR = expandTilde("~/.openMSX");
554 #endif
555  return OPENMSX_DIR;
556 }
557 
559 {
560  const char* const NAME = "OPENMSX_USER_DATA";
561  char* value = getenv(NAME);
562  return value ? value : getUserOpenMSXDir() + "/share";
563 }
564 
566 {
567  const char* const NAME = "OPENMSX_SYSTEM_DATA";
568  if (char* value = getenv(NAME)) {
569  return value;
570  }
571 
572  string newValue;
573 #ifdef _WIN32
574  wchar_t bufW[MAXPATHLEN + 1];
575  int res = GetModuleFileNameW(nullptr, bufW, std::size(bufW));
576  if (!res) {
577  throw FatalError(
578  "Cannot detect openMSX directory. GetModuleFileNameW failed: ",
579  GetLastError());
580  }
581 
582  string filename = utf16to8(bufW);
583  auto pos = filename.find_last_of('\\');
584  if (pos == string::npos) {
585  throw FatalError("openMSX is not in directory!?");
586  }
587  newValue = getConventionalPath(filename.substr(0, pos)) + "/share";
588 #elif defined(__APPLE__)
589  newValue = findShareDir();
590 #elif PLATFORM_ANDROID
591  newValue = getAbsolutePath("openmsx_system");
592  ad_printf("System data dir: %s", newValue.c_str());
593 #else
594  // defined in build-info.hh (default /opt/openMSX/share)
595  newValue = DATADIR;
596 #endif
597  return newValue;
598 }
599 
600 #ifdef _WIN32
601 static bool driveExists(char driveLetter)
602 {
603  char buf[] = { driveLetter, ':', 0 };
604  return GetFileAttributesA(buf) != INVALID_FILE_ATTRIBUTES;
605 }
606 #endif
607 
608 string expandCurrentDirFromDrive(string_view path)
609 {
610  string result(path);
611 #ifdef _WIN32
612  if (((path.size() == 2) && (path[1] == ':')) ||
613  ((path.size() >= 3) && (path[1] == ':') && (path[2] != '/'))) {
614  // get current directory for this drive
615  unsigned char drive = tolower(path[0]);
616  if (('a' <= drive) && (drive <= 'z')) {
617  wchar_t bufW[MAXPATHLEN + 1];
618  if (driveExists(drive) &&
619  _wgetdcwd(drive - 'a' + 1, bufW, MAXPATHLEN)) {
620  result = getConventionalPath(utf16to8(bufW));
621  if (result.back() != '/') {
622  result += '/';
623  }
624  if (path.size() > 2) {
625  string_view tmp = path.substr(2);
626  result.append(tmp.data(), tmp.size());
627  }
628  }
629  }
630  }
631 #endif
632  return result;
633 }
634 
635 bool getStat(string_view filename_, Stat& st)
636 {
637  string filename = expandTilde(filename_);
638  // workaround for VC++: strip trailing slashes (but keep it if it's the
639  // only character in the path)
640  auto pos = filename.find_last_not_of('/');
641  if (pos == string::npos) {
642  // string was either empty or a (sequence of) '/' character(s)
643  filename = filename.empty() ? string{} : "/";
644  } else {
645  filename.resize(pos + 1);
646  }
647 #ifdef _WIN32
648  return _wstat(utf8to16(filename).c_str(), &st) == 0;
649 #else
650  return stat(filename.c_str(), &st) == 0;
651 #endif
652 }
653 
654 bool isRegularFile(const Stat& st)
655 {
656  return S_ISREG(st.st_mode);
657 }
658 bool isRegularFile(string_view filename)
659 {
660  Stat st;
661  return getStat(filename, st) && isRegularFile(st);
662 }
663 
664 bool isDirectory(const Stat& st)
665 {
666  return S_ISDIR(st.st_mode);
667 }
668 
669 bool isDirectory(string_view directory)
670 {
671  Stat st;
672  return getStat(directory, st) && isDirectory(st);
673 }
674 
675 bool exists(string_view filename)
676 {
677  Stat st; // dummy
678  return getStat(filename, st);
679 }
680 
681 time_t getModificationDate(const Stat& st)
682 {
683  return st.st_mtime;
684 }
685 
686 static unsigned getNextNum(dirent* d, string_view prefix, string_view extension,
687  unsigned nofdigits)
688 {
689  auto extensionLen = extension.size();
690  auto prefixLen = prefix.size();
691  string_view name(d->d_name);
692 
693  if ((name.size() != (prefixLen + nofdigits + extensionLen)) ||
694  (name.substr(0, prefixLen) != prefix) ||
695  (name.substr(prefixLen + nofdigits, extensionLen) != extension)) {
696  return 0;
697  }
698  try {
699  return StringOp::fast_stou(name.substr(prefixLen, nofdigits));
700  } catch (std::invalid_argument&) {
701  return 0;
702  }
703 }
704 
706  string_view directory, string_view prefix, string_view extension)
707 {
708  const unsigned nofdigits = 4;
709 
710  unsigned max_num = 0;
711 
712  string dirName = strCat(getUserOpenMSXDir(), '/', directory);
713  try {
714  mkdirp(dirName);
715  } catch (FileException&) {
716  // ignore
717  }
718 
719  ReadDir dir(dirName);
720  while (auto* d = dir.getEntry()) {
721  max_num = std::max(max_num, getNextNum(d, prefix, extension, nofdigits));
722  }
723 
724  std::ostringstream os;
725  os << dirName << '/' << prefix;
726  os.width(nofdigits);
727  os.fill('0');
728  os << (max_num + 1) << extension;
729  return os.str();
730 }
731 
733  string_view argument, string_view directory,
734  string_view prefix, string_view extension)
735 {
736  if (argument.empty()) {
737  // directory is also created when needed
738  return getNextNumberedFileName(directory, prefix, extension);
739  }
740 
741  string filename(argument);
742  if (getDirName(filename).empty()) {
743  // no dir given, use standard dir (and create it)
744  string dir = strCat(getUserOpenMSXDir(), '/', directory);
745  mkdirp(dir);
746  filename = strCat(dir, '/', filename);
747  } else {
748  filename = expandTilde(filename);
749  }
750 
751  if (!StringOp::endsWith(filename, extension) &&
752  !exists(filename)) {
753  // Expected extension not already given, append it. But only
754  // when the filename without extension doesn't already exist.
755  // Without this exception stuff like 'soundlog start /dev/null'
756  // reports an error " ... error opening file /dev/null.wav."
757  filename.append(extension.data(), extension.size());
758  }
759  return filename;
760 }
761 
762 string getTempDir()
763 {
764 #ifdef _WIN32
765  DWORD len = GetTempPathW(0, nullptr);
766  if (len) {
767  VLA(wchar_t, bufW, (len+1));
768  len = GetTempPathW(len, bufW);
769  if (len) {
770  // Strip last backslash
771  if (bufW[len-1] == L'\\') {
772  bufW[len-1] = L'\0';
773  }
774  return utf16to8(bufW);
775  }
776  }
777  throw FatalError("GetTempPathW failed: ", GetLastError());
778 #elif PLATFORM_ANDROID
779  string result = getSystemDataDir() + "/tmp";
780  return result;
781 #else
782  const char* result = nullptr;
783  if (!result) result = getenv("TMPDIR");
784  if (!result) result = getenv("TMP");
785  if (!result) result = getenv("TEMP");
786  if (!result) {
787  result = "/tmp";
788  }
789  return result;
790 #endif
791 }
792 
793 FILE_t openUniqueFile(const std::string& directory, std::string& filename)
794 {
795 #ifdef _WIN32
796  std::wstring directoryW = utf8to16(directory);
797  wchar_t filenameW[MAX_PATH];
798  if (!GetTempFileNameW(directoryW.c_str(), L"msx", 0, filenameW)) {
799  throw FileException("GetTempFileNameW failed: ", GetLastError());
800  }
801  filename = utf16to8(filenameW);
802  return FILE_t(_wfopen(filenameW, L"wb"));
803 #else
804  filename = directory + "/XXXXXX";
805  auto oldMask = umask(S_IRWXO | S_IRWXG);
806  int fd = mkstemp(const_cast<char*>(filename.c_str()));
807  umask(oldMask);
808  if (fd == -1) {
809  throw FileException("Coundn't get temp file name");
810  }
811  return FILE_t(fdopen(fd, "wb"));
812 #endif
813 }
814 
815 } // namespace openmsx::FileOperations
string_view getFilename(string_view path)
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:343
bool isAbsolutePath(string_view path)
unsigned fast_stou(string_view s)
Definition: StringOp.cc:265
int unlink(const std::string &path)
Call unlink() in a platform-independent manner.
string getNextNumberedFileName(string_view directory, string_view prefix, string_view extension)
string getUserHomeDir(string_view username)
void replace(ForwardRange &&range, const T &old_value, const T &new_value)
Definition: ranges.hh:179
bool isRegularFile(string_view filename)
string getSystemDataDir()
Get system directory.
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:71
string getCurrentWorkingDirectory()
Returns the current working directory.
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
bool isDirectory(string_view directory)
void openofstream(std::ofstream &stream, const std::string &filename, std::ios_base::openmode mode)
Open an ofstream in a platform-independent manner.
size_t size(std::string_view utf8)
octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result)
FILE_t openUniqueFile(const std::string &directory, std::string &filename)
Open a new file with a unique name in the provided directory.
#define MAXPATHLEN
constexpr const char *const filename
string getUserDataDir()
Get the openMSX data dir in the user&#39;s home directory.
string getNativePath(string_view path)
string expandCurrentDirFromDrive(string_view path)
bool exists(string_view filename)
int rmdir(const std::string &path)
Call rmdir() in a platform-independent manner.
string_view getExtension(string_view path)
const string & getUserOpenMSXDir()
Get the openMSX dir in the user&#39;s home directory.
void mkdir(const string &path, mode_t mode)
Create the specified directory.
string_view stripExtension(string_view path)
bool getStat(string_view filename_, Stat &st)
string parseCommandFileArgument(string_view argument, string_view directory, string_view prefix, string_view extension)
string getConventionalPath(string_view path)
u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result)
string_view getDirName(string_view path)
string expandTilde(string_view path)
string getTempDir()
Get the name of the temp directory on the system.
Simple wrapper around openmdir() / readdir() / closedir() functions.
Definition: ReadDir.hh:15
void mkdirp(string_view path_)
FILE_t openFile(const std::string &filename, const std::string &mode)
Call fopen() in a platform-independent manner.
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
struct dirent * getEntry()
Get directory entry for next file.
Definition: ReadDir.cc:17
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
bool endsWith(string_view total, string_view part)
Definition: StringOp.cc:81
detail::Joiner< Collection, Separator > join(Collection &&col, Separator &&sep)
Definition: join.hh:60
std::unique_ptr< FILE, FClose > FILE_t
string getAbsolutePath(string_view path)
int deleteRecursive(const std::string &path)
Recurively delete a file or directory and (in case of a directory) all its sub-components.
time_t getModificationDate(const Stat &st)
Get the date/time of last modification.
#define ad_printf(...)
Definition: openmsx.hh:11