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 "one_of.hh"
51 #include "ranges.hh"
52 #include "strCat.hh"
53 #include "build-info.hh"
54 #include <algorithm>
55 #include <sstream>
56 #include <cerrno>
57 #include <cstdlib>
58 #include <stdexcept>
59 #include <cassert>
60 #include <iterator>
61 
62 #ifndef _MSC_VER
63 #include <dirent.h>
64 #endif
65 
66 #if PLATFORM_ANDROID
67 #include "SDL_system.h" // for GetExternalStorage stuff
68 #endif
69 
70 using std::string;
71 using std::string_view;
72 
73 #ifdef _WIN32
74 using namespace utf8;
75 #endif
76 
78 
79 #ifdef __APPLE__
80 
81 static std::string findShareDir()
82 {
83  // Find bundle location:
84  // for an app folder, this is the outer directory,
85  // for an unbundled executable, it is the executable itself.
86  ProcessSerialNumber psn;
87  if (GetCurrentProcess(&psn) != noErr) {
88  throw FatalError("Failed to get process serial number");
89  }
90  FSRef location;
91  if (GetProcessBundleLocation(&psn, &location) != noErr) {
92  throw FatalError("Failed to get process bundle location");
93  }
94  // Get info about the location.
95  FSCatalogInfo catalogInfo;
96  FSRef parentRef;
97  if (FSGetCatalogInfo(
98  &location, kFSCatInfoVolume | kFSCatInfoNodeFlags,
99  &catalogInfo, nullptr, nullptr, &parentRef
100  ) != noErr) {
101  throw FatalError("Failed to get info about bundle path");
102  }
103  // Get reference to root directory of the volume we are searching.
104  // We will need this later to know when to give up.
105  FSRef root;
106  if (FSGetVolumeInfo(
107  catalogInfo.volume, 0, nullptr, kFSVolInfoNone, nullptr, nullptr, &root
108  ) != noErr) {
109  throw FatalError("Failed to get reference to root directory");
110  }
111  // Make sure we are looking at a directory.
112  if (~catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) {
113  // Location is not a directory, so it is the path to the executable.
114  location = parentRef;
115  }
116  while (true) {
117  // Iterate through the files in the directory.
118  FSIterator iterator;
119  if (FSOpenIterator(&location, kFSIterateFlat, &iterator) != noErr) {
120  throw FatalError("Failed to open iterator");
121  }
122  bool filesLeft = true; // iterator has files left for next call
123  while (filesLeft) {
124  // Get info about several files at a time.
125  const int MAX_SCANNED_FILES = 100;
126  ItemCount actualObjects;
127  FSRef refs[MAX_SCANNED_FILES];
128  FSCatalogInfo catalogInfos[MAX_SCANNED_FILES];
129  HFSUniStr255 names[MAX_SCANNED_FILES];
130  OSErr err = FSGetCatalogInfoBulk(
131  iterator,
132  MAX_SCANNED_FILES,
133  &actualObjects,
134  nullptr /*containerChanged*/,
135  kFSCatInfoNodeFlags,
136  catalogInfos,
137  refs,
138  nullptr /*specs*/,
139  names
140  );
141  if (err == errFSNoMoreItems) {
142  filesLeft = false;
143  } else if (err != noErr) {
144  throw FatalError("Catalog get failed");
145  }
146  for (ItemCount i = 0; i < actualObjects; i++) {
147  // We're only interested in subdirectories.
148  if (catalogInfos[i].nodeFlags & kFSNodeIsDirectoryMask) {
149  // Convert the name to a CFString.
150  CFStringRef name = CFStringCreateWithCharactersNoCopy(
151  kCFAllocatorDefault,
152  names[i].unicode,
153  names[i].length,
154  kCFAllocatorNull // do not deallocate character buffer
155  );
156  // Is this the directory we are looking for?
157  static const CFStringRef SHARE = CFSTR("share");
158  CFComparisonResult cmp = CFStringCompare(SHARE, name, 0);
159  CFRelease(name);
160  if (cmp == kCFCompareEqualTo) {
161  // Clean up.
162  OSErr closeErr = FSCloseIterator(iterator);
163  assert(closeErr == noErr); (void)closeErr;
164  // Get full path of directory.
165  UInt8 path[256];
166  if (FSRefMakePath(
167  &refs[i], path, sizeof(path)) != noErr
168  ) {
169  throw FatalError("Path too long");
170  }
171  return std::string(reinterpret_cast<char*>(path));
172  }
173  }
174  }
175  }
176  OSErr closeErr = FSCloseIterator(iterator);
177  assert(closeErr == noErr); (void)closeErr;
178  // Are we in the root yet?
179  if (FSCompareFSRefs(&location, &root) == noErr) {
180  throw FatalError("Could not find \"share\" directory anywhere");
181  }
182  // Go up one level.
183  if (FSGetCatalogInfo(
184  &location, kFSCatInfoNone, nullptr, nullptr, nullptr, &parentRef
185  ) != noErr
186  ) {
187  throw FatalError("Failed to get parent directory");
188  }
189  location = parentRef;
190  }
191 }
192 
193 #endif // __APPLE__
194 
195 string expandTilde(string_view path)
196 {
197  if (path.empty() || path[0] != '~') {
198  return string(path);
199  }
200  auto pos = path.find_first_of('/');
201  string_view user = ((path.size() == 1) || (pos == 1))
202  ? string_view{}
203  : path.substr(1, (pos == string_view::npos) ? pos : pos - 1);
204  string result = getUserHomeDir(user);
205  if (result.empty()) {
206  // failed to find homedir, return the path unchanged
207  return string(path);
208  }
209  if (pos == string_view::npos) {
210  return result;
211  }
212  if (result.back() != '/') {
213  result += '/';
214  }
215  string_view last = path.substr(pos + 1);
216  result.append(last.data(), last.size());
217  return result;
218 }
219 
220 void mkdir(const string& path, mode_t mode)
221 {
222 #ifdef _WIN32
223  (void)&mode; // Suppress C4100 VC++ warning
224  if ((path == "/") ||
225  StringOp::endsWith(path, ':') ||
226  StringOp::endsWith(path, ":/")) {
227  return;
228  }
229  int result = _wmkdir(utf8to16(getNativePath(path)).c_str());
230 #else
231  int result = ::mkdir(path.c_str(), mode);
232 #endif
233  if (result && (errno != EEXIST)) {
234  throw FileException("Error creating dir ", path);
235  }
236 }
237 
238 static bool isUNCPath(string_view path)
239 {
240 #ifdef _WIN32
241  return StringOp::startsWith(path, "//") || StringOp::startsWith(path, "\\\\");
242 #else
243  (void)path;
244  return false;
245 #endif
246 }
247 
248 void mkdirp(string_view path_)
249 {
250  if (path_.empty()) {
251  return;
252  }
253 
254  // We may receive platform-specific paths here, so conventionalize early
255  string path = getConventionalPath(expandTilde(path_));
256 
257  // If the directory already exists, don't try to recreate it
258  if (isDirectory(path))
259  return;
260 
261  // If the path is a UNC path (e.g. \\server\share) then the first two paths in the loop below will be \ and \\server
262  // If the path is an absolute path (e.g. c:\foo\bar) then the first path in the loop will be C:
263  // None of those are valid directory paths, so we skip over them and don't call mkdir.
264  // Relative paths are fine, since each segment in the path is significant.
265  int skip = isUNCPath(path) ? 2 :
266  isAbsolutePath(path) ? 1 : 0;
267  string::size_type pos = 0;
268  do {
269  pos = path.find_first_of('/', pos + 1);
270  if (skip) {
271  skip--;
272  continue;
273  }
274  mkdir(path.substr(0, pos), 0755);
275  } while (pos != string::npos);
276 
277  if (!isDirectory(path)) {
278  throw FileException("Error creating dir ", path);
279  }
280 }
281 
282 int unlink(const std::string& path)
283 {
284 #ifdef _WIN32
285  return _wunlink(utf8to16(path).c_str());
286 #else
287  return ::unlink(path.c_str());
288 #endif
289 }
290 
291 int rmdir(const std::string& path)
292 {
293 #ifdef _WIN32
294  return _wrmdir(utf8to16(path).c_str());
295 #else
296  return ::rmdir(path.c_str());
297 #endif
298 }
299 
300 #ifdef _WIN32
301 int deleteRecursive(const std::string& path)
302 {
303  std::wstring pathW = utf8to16(path);
304 
305  SHFILEOPSTRUCTW rmdirFileop;
306  rmdirFileop.hwnd = nullptr;
307  rmdirFileop.wFunc = FO_DELETE;
308  rmdirFileop.pFrom = pathW.c_str();
309  rmdirFileop.pTo = nullptr;
310  rmdirFileop.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
311  rmdirFileop.fAnyOperationsAborted = FALSE;
312  rmdirFileop.hNameMappings = nullptr;
313  rmdirFileop.lpszProgressTitle = nullptr;
314 
315  return SHFileOperationW(&rmdirFileop);
316 }
317 #elif HAVE_NFTW
318 static int deleteRecursive_cb(const char* fpath, const struct stat* /*sb*/,
319  int /*typeflag*/, struct FTW* /*ftwbuf*/)
320 {
321  return remove(fpath);
322 }
323 int deleteRecursive(const std::string& path)
324 {
325  return nftw(path.c_str(), deleteRecursive_cb, 64, FTW_DEPTH | FTW_PHYS);
326 }
327 #else
328 // This is a platform independent version of deleteRecursive() (it builds on
329 // top of helper routines that _are_ platform specific). Though I still prefer
330 // the two platform specific deleteRecursive() routines above because they are
331 // likely more optimized and likely contain less bugs than this version (e.g.
332 // we're walking over the entries in a directory while simultaneously deleting
333 // entries in that same directory. Although this seems to work fine, I'm not
334 // 100% sure our ReadDir 'emulation code' for windows covers all corner cases.
335 // While the windows version above very likely does handle everything).
336 int deleteRecursive(const std::string& path)
337 {
338  if (isDirectory(path)) {
339  {
340  ReadDir dir(path);
341  while (dirent* d = dir.getEntry()) {
342  int err = deleteRecursive(d->d_name);
343  if (err) return err;
344  }
345  }
346  return rmdir(path);
347  } else {
348  return unlink(path);
349  }
350 }
351 #endif
352 
353 FILE_t openFile(const std::string& filename, const std::string& mode)
354 {
355  // Mode must contain a 'b' character. On unix this doesn't make any
356  // difference. But on windows this is required to open the file
357  // in binary mode.
358  assert(mode.find('b') != std::string::npos);
359 #ifdef _WIN32
360  return FILE_t(_wfopen(utf8to16(filename).c_str(),
361  utf8to16(mode).c_str()));
362 #else
363  return FILE_t(fopen(filename.c_str(), mode.c_str()));
364 #endif
365 }
366 
367 void openofstream(std::ofstream& stream, const std::string& filename)
368 {
369 #if defined _WIN32 && defined _MSC_VER
370  // MinGW 3.x doesn't support ofstream.open(wchar_t*)
371  // TODO - this means that unicode text may not work right here
372  stream.open(utf8to16(filename).c_str());
373 #else
374  stream.open(filename.c_str());
375 #endif
376 }
377 
378 void openofstream(std::ofstream& stream, const std::string& filename,
379  std::ios_base::openmode mode)
380 {
381 #if defined _WIN32 && defined _MSC_VER
382  // MinGW 3.x doesn't support ofstream.open(wchar_t*)
383  // TODO - this means that unicode text may not work right here
384  stream.open(utf8to16(filename).c_str(), mode);
385 #else
386  stream.open(filename.c_str(), mode);
387 #endif
388 }
389 
390 string_view getFilename(string_view path)
391 {
392  auto pos = path.rfind('/');
393  if (pos == string_view::npos) {
394  return path;
395  } else {
396  return path.substr(pos + 1);
397  }
398 }
399 
400 string_view getDirName(string_view path)
401 {
402  auto pos = path.rfind('/');
403  if (pos == string_view::npos) {
404  return {};
405  } else {
406  return path.substr(0, pos + 1);
407  }
408 }
409 
410 string_view getExtension(string_view path)
411 {
412  string_view filename = getFilename(path);
413  auto pos = filename.rfind('.');
414  if (pos == string_view::npos) {
415  return string_view();
416  } else {
417  return filename.substr(pos);
418  }
419 }
420 
421 string_view stripExtension(string_view path)
422 {
423  auto pos = path.rfind('.');
424  if (pos == string_view::npos) {
425  return path;
426  } else {
427  return path.substr(0, pos);
428  }
429 }
430 
431 string join(string_view part1, string_view part2)
432 {
433  if (part1.empty() || isAbsolutePath(part2)) {
434  return string(part2);
435  }
436  if (part1.back() == '/') {
437  return strCat(part1, part2);
438  }
439  return strCat(part1, '/', part2);
440 }
441 string join(string_view part1, string_view part2, string_view part3)
442 {
443  return join(part1, join(part2, part3));
444 }
445 
446 string join(string_view part1, string_view part2,
447  string_view part3, string_view part4)
448 {
449  return join(part1, join(part2, join(part3, part4)));
450 }
451 
452 string getNativePath(string_view path)
453 {
454  string result(path);
455 #ifdef _WIN32
456  ranges::replace(result, '/', '\\');
457 #endif
458  return result;
459 }
460 
461 string getConventionalPath(string_view path)
462 {
463  string result(path);
464 #ifdef _WIN32
465  ranges::replace(result, '\\', '/');
466 #endif
467  return result;
468 }
469 
471 {
472 #ifdef _WIN32
473  wchar_t bufW[MAXPATHLEN];
474  wchar_t* result = _wgetcwd(bufW, MAXPATHLEN);
475  string buf;
476  if (result) {
477  buf = utf16to8(result);
478  }
479 #else
480  char buf[MAXPATHLEN];
481  char* result = getcwd(buf, MAXPATHLEN);
482 #endif
483  if (!result) {
484  throw FileException("Couldn't get current working directory.");
485  }
486  return buf;
487 }
488 
489 string getAbsolutePath(string_view path)
490 {
491  // In rare cases getCurrentWorkingDirectory() can throw,
492  // so only call it when really necessary.
493  if (isAbsolutePath(path)) {
494  return string(path);
495  }
496  string currentDir = getCurrentWorkingDirectory();
497  return join(currentDir, path);
498 }
499 
500 bool isAbsolutePath(string_view path)
501 {
502  if (isUNCPath(path)) return true;
503 #ifdef _WIN32
504  if ((path.size() >= 3) && (path[1] == ':') && (path[2] == one_of('/', '\\'))) {
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 {
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
one_of.hh
FileException.hh
openmsx.hh
StringOp::startsWith
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:71
utf8
Definition: utf8_checked.hh:36
openmsx::FileOperations::getConventionalPath
string getConventionalPath(string_view path)
Returns the path in conventional path-delimiter.
Definition: FileOperations.cc:461
openmsx::FileOperations::FILE_t
std::unique_ptr< FILE, FClose > FILE_t
Definition: FileOperations.hh:16
openmsx::FileOperations::deleteRecursive
int deleteRecursive(const std::string &path)
Recurively delete a file or directory and (in case of a directory) all its sub-components.
Definition: FileOperations.cc:323
statp.hh
MAXPATHLEN
#define MAXPATHLEN
Definition: FileOperations.cc:36
openmsx::ReadDir
Simple wrapper around openmdir() / readdir() / closedir() functions.
Definition: ReadDir.hh:15
utf8::unchecked::size
size_t size(std::string_view utf8)
Definition: utf8_unchecked.hh:227
openmsx::FileOperations::isAbsolutePath
bool isAbsolutePath(string_view path)
Checks whether it's a absolute path or not.
Definition: FileOperations.cc:500
openmsx::FileOperations::getStat
bool getStat(string_view filename_, Stat &st)
Call stat() and return the stat structure.
Definition: FileOperations.cc:635
openmsx::FileOperations::Stat
struct stat Stat
Definition: FileOperations.hh:212
openmsx::FileOperations::getAbsolutePath
string getAbsolutePath(string_view path)
Transform given path into an absolute path.
Definition: FileOperations.cc:489
utf8::utf8to16
u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result)
Definition: utf8_checked.hh:227
gl::length
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:348
openmsx::FileOperations::unlink
int unlink(const std::string &path)
Call unlink() in a platform-independent manner.
Definition: FileOperations.cc:282
openmsx::FileOperations::getNativePath
string getNativePath(string_view path)
Returns the path in native path-delimiter.
Definition: FileOperations.cc:452
StringOp::fast_stou
unsigned fast_stou(string_view s)
Definition: StringOp.cc:265
ranges.hh
openmsx::L
@ L
Definition: CPUCore.cc:204
utf8::iterator
Definition: utf8_checked.hh:264
openmsx::FileOperations::exists
bool exists(string_view filename)
Does this file (directory) exists?
Definition: FileOperations.cc:675
openmsx::ReadDir::getEntry
struct dirent * getEntry()
Get directory entry for next file.
Definition: ReadDir.cc:17
openmsx::FileOperations::mkdirp
void mkdirp(string_view path_)
Acts like the unix command "mkdir -p".
Definition: FileOperations.cc:248
openmsx::FileOperations::openofstream
void openofstream(std::ofstream &stream, const std::string &filename, std::ios_base::openmode mode)
Open an ofstream in a platform-independent manner.
Definition: FileOperations.cc:378
systemfuncs.hh
openmsx::FileOperations::getSystemDataDir
string getSystemDataDir()
Get system directory.
Definition: FileOperations.cc:565
openmsx::FileOperations::getCurrentWorkingDirectory
string getCurrentWorkingDirectory()
Returns the current working directory.
Definition: FileOperations.cc:470
ReadDir.hh
StringOp::endsWith
bool endsWith(string_view total, string_view part)
Definition: StringOp.cc:81
vla.hh
openmsx::FileOperations::getDirName
string_view getDirName(string_view path)
Returns the directory portion of a path.
Definition: FileOperations.cc:400
openmsx::FileOperations::getUserDataDir
string getUserDataDir()
Get the openMSX data dir in the user's home directory.
Definition: FileOperations.cc:558
unistdp.hh
openmsx::FileOperations
Definition: FileOperations.cc:77
openmsx::FileOperations::expandTilde
string expandTilde(string_view path)
Expand the '~' character to the users home directory.
Definition: FileOperations.cc:195
one_of
Definition: one_of.hh:7
openmsx::filename
constexpr const char *const filename
Definition: FirmwareSwitch.cc:10
ranges::remove
auto remove(ForwardRange &&range, const T &value)
Definition: ranges.hh:167
openmsx::FileOperations::getUserOpenMSXDir
const string & getUserOpenMSXDir()
Get the openMSX dir in the user's home directory.
Definition: FileOperations.cc:544
openmsx::FileException
Definition: FileException.hh:8
openmsx::FileOperations::expandCurrentDirFromDrive
string expandCurrentDirFromDrive(string_view path)
Get the current directory of the specified drive Linux: just return an empty string.
Definition: FileOperations.cc:608
build-info.hh
openmsx::FileOperations::parseCommandFileArgument
string parseCommandFileArgument(string_view argument, string_view directory, string_view prefix, string_view extension)
Helper function for parsing filename arguments in Tcl commands.
Definition: FileOperations.cc:732
openmsx::FileOperations::openUniqueFile
FILE_t openUniqueFile(const std::string &directory, std::string &filename)
Open a new file with a unique name in the provided directory.
Definition: FileOperations.cc:793
openmsx::FatalError
Definition: MSXException.hh:30
openmsx::FileOperations::getTempDir
string getTempDir()
Get the name of the temp directory on the system.
Definition: FileOperations.cc:762
utf8_checked.hh
openmsx::FileOperations::openFile
FILE_t openFile(const std::string &filename, const std::string &mode)
Call fopen() in a platform-independent manner.
Definition: FileOperations.cc:353
FileOperations.hh
StringOp.hh
openmsx::FileOperations::rmdir
int rmdir(const std::string &path)
Call rmdir() in a platform-independent manner.
Definition: FileOperations.cc:291
ranges::replace
void replace(ForwardRange &&range, const T &old_value, const T &new_value)
Definition: ranges.hh:179
strCat.hh
openmsx::FileOperations::getUserHomeDir
string getUserHomeDir(string_view username)
Get user's home directory.
Definition: FileOperations.cc:514
openmsx::FileOperations::isRegularFile
bool isRegularFile(string_view filename)
Is this a regular file (no directory, device, ..)?
Definition: FileOperations.cc:658
ad_printf
#define ad_printf(...)
Definition: openmsx.hh:11
gl::max
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:292
join
detail::Joiner< Collection, Separator > join(Collection &&col, Separator &&sep)
Definition: join.hh:60
utf8::utf16to8
octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result)
Definition: utf8_checked.hh:204
openmsx::FileOperations::getExtension
string_view getExtension(string_view path)
Returns the extension portion of a path.
Definition: FileOperations.cc:410
openmsx::FileOperations::mkdir
void mkdir(const string &path, mode_t mode)
Create the specified directory.
Definition: FileOperations.cc:220
VLA
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
strCat
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
openmsx::FileOperations::stripExtension
string_view stripExtension(string_view path)
Returns the path without extension.
Definition: FileOperations.cc:421
openmsx::FileOperations::getModificationDate
time_t getModificationDate(const Stat &st)
Get the date/time of last modification.
Definition: FileOperations.cc:681
openmsx::FileOperations::getNextNumberedFileName
string getNextNumberedFileName(string_view directory, string_view prefix, string_view extension)
Gets the next numbered file name with the specified prefix in the specified directory,...
Definition: FileOperations.cc:705
openmsx::FileOperations::isDirectory
bool isDirectory(string_view directory)
Is this a directory?
Definition: FileOperations.cc:669
openmsx::FileOperations::getFilename
string_view getFilename(string_view path)
Returns the file portion of a path name.
Definition: FileOperations.cc:390