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