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