]> git.lizzy.rs Git - dragonfireclient.git/blob - src/filesys.cpp
Merge branch 'master' into master
[dragonfireclient.git] / src / filesys.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "filesys.h"
21 #include "util/string.h"
22 #include <iostream>
23 #include <cstdio>
24 #include <cstring>
25 #include <cerrno>
26 #include <fstream>
27 #include "log.h"
28 #include "config.h"
29 #include "porting.h"
30 #ifdef __ANDROID__
31 #include "settings.h" // For g_settings
32 #endif
33
34 namespace fs
35 {
36
37 #ifdef _WIN32 // WINDOWS
38
39 #define _WIN32_WINNT 0x0501
40 #include <windows.h>
41 #include <shlwapi.h>
42
43 std::vector<DirListNode> GetDirListing(const std::string &pathstring)
44 {
45         std::vector<DirListNode> listing;
46
47         WIN32_FIND_DATA FindFileData;
48         HANDLE hFind = INVALID_HANDLE_VALUE;
49         DWORD dwError;
50
51         std::string dirSpec = pathstring + "\\*";
52
53         // Find the first file in the directory.
54         hFind = FindFirstFile(dirSpec.c_str(), &FindFileData);
55
56         if (hFind == INVALID_HANDLE_VALUE) {
57                 dwError = GetLastError();
58                 if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) {
59                         errorstream << "GetDirListing: FindFirstFile error."
60                                     << " Error is " << dwError << std::endl;
61                 }
62         } else {
63                 // NOTE:
64                 // Be very sure to not include '..' in the results, it will
65                 // result in an epic failure when deleting stuff.
66
67                 DirListNode node;
68                 node.name = FindFileData.cFileName;
69                 node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
70                 if (node.name != "." && node.name != "..")
71                         listing.push_back(node);
72
73                 // List all the other files in the directory.
74                 while (FindNextFile(hFind, &FindFileData) != 0) {
75                         DirListNode node;
76                         node.name = FindFileData.cFileName;
77                         node.dir = FindFileData.dwFileAttributes &
78                                    FILE_ATTRIBUTE_DIRECTORY;
79                         if (node.name != "." && node.name != "..")
80                                 listing.push_back(node);
81                 }
82
83                 dwError = GetLastError();
84                 FindClose(hFind);
85                 if (dwError != ERROR_NO_MORE_FILES) {
86                         errorstream << "GetDirListing: FindNextFile error."
87                                     << " Error is " << dwError << std::endl;
88                         listing.clear();
89                         return listing;
90                 }
91         }
92         return listing;
93 }
94
95 bool CreateDir(const std::string &path)
96 {
97         bool r = CreateDirectory(path.c_str(), NULL);
98         if (r == true)
99                 return true;
100         if (GetLastError() == ERROR_ALREADY_EXISTS)
101                 return true;
102         return false;
103 }
104
105 bool PathExists(const std::string &path)
106 {
107         return (GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES);
108 }
109
110 bool IsPathAbsolute(const std::string &path)
111 {
112         return !PathIsRelative(path.c_str());
113 }
114
115 bool IsDir(const std::string &path)
116 {
117         DWORD attr = GetFileAttributes(path.c_str());
118         return (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY));
119 }
120
121 bool IsDirDelimiter(char c)
122 {
123         return c == '/' || c == '\\';
124 }
125
126 bool RecursiveDelete(const std::string &path)
127 {
128         infostream << "Recursively deleting \"" << path << "\"" << std::endl;
129         if (!IsDir(path)) {
130                 infostream << "RecursiveDelete: Deleting file  " << path << std::endl;
131                 if (!DeleteFile(path.c_str())) {
132                         errorstream << "RecursiveDelete: Failed to delete file " << path
133                                     << std::endl;
134                         return false;
135                 }
136                 return true;
137         }
138         infostream << "RecursiveDelete: Deleting content of directory " << path
139                    << std::endl;
140         std::vector<DirListNode> content = GetDirListing(path);
141         for (const DirListNode &n : content) {
142                 std::string fullpath = path + DIR_DELIM + n.name;
143                 if (!RecursiveDelete(fullpath)) {
144                         errorstream << "RecursiveDelete: Failed to recurse to "
145                                     << fullpath << std::endl;
146                         return false;
147                 }
148         }
149         infostream << "RecursiveDelete: Deleting directory " << path << std::endl;
150         if (!RemoveDirectory(path.c_str())) {
151                 errorstream << "Failed to recursively delete directory " << path
152                             << std::endl;
153                 return false;
154         }
155         return true;
156 }
157
158 bool DeleteSingleFileOrEmptyDirectory(const std::string &path)
159 {
160         DWORD attr = GetFileAttributes(path.c_str());
161         bool is_directory = (attr != INVALID_FILE_ATTRIBUTES &&
162                              (attr & FILE_ATTRIBUTE_DIRECTORY));
163         if (!is_directory) {
164                 bool did = DeleteFile(path.c_str());
165                 return did;
166         } else {
167                 bool did = RemoveDirectory(path.c_str());
168                 return did;
169         }
170 }
171
172 std::string TempPath()
173 {
174         DWORD bufsize = GetTempPath(0, NULL);
175         if (bufsize == 0) {
176                 errorstream << "GetTempPath failed, error = " << GetLastError()
177                             << std::endl;
178                 return "";
179         }
180         std::vector<char> buf(bufsize);
181         DWORD len = GetTempPath(bufsize, &buf[0]);
182         if (len == 0 || len > bufsize) {
183                 errorstream << "GetTempPath failed, error = " << GetLastError()
184                             << std::endl;
185                 return "";
186         }
187         return std::string(buf.begin(), buf.begin() + len);
188 }
189
190 #else // POSIX
191
192 #include <sys/types.h>
193 #include <dirent.h>
194 #include <sys/stat.h>
195 #include <sys/wait.h>
196 #include <unistd.h>
197
198 std::vector<DirListNode> GetDirListing(const std::string &pathstring)
199 {
200         std::vector<DirListNode> listing;
201
202         DIR *dp;
203         struct dirent *dirp;
204         if ((dp = opendir(pathstring.c_str())) == NULL) {
205                 // infostream<<"Error("<<errno<<") opening "<<pathstring<<std::endl;
206                 return listing;
207         }
208
209         while ((dirp = readdir(dp)) != NULL) {
210                 // NOTE:
211                 // Be very sure to not include '..' in the results, it will
212                 // result in an epic failure when deleting stuff.
213                 if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)
214                         continue;
215
216                 DirListNode node;
217                 node.name = dirp->d_name;
218
219                 int isdir = -1; // -1 means unknown
220
221                 /*
222                         POSIX doesn't define d_type member of struct dirent and
223                         certain filesystems on glibc/Linux will only return
224                         DT_UNKNOWN for the d_type member.
225
226                         Also we don't know whether symlinks are directories or not.
227                 */
228 #ifdef _DIRENT_HAVE_D_TYPE
229                 if (dirp->d_type != DT_UNKNOWN && dirp->d_type != DT_LNK)
230                         isdir = (dirp->d_type == DT_DIR);
231 #endif /* _DIRENT_HAVE_D_TYPE */
232
233                 /*
234                         Was d_type DT_UNKNOWN, DT_LNK or nonexistent?
235                         If so, try stat().
236                 */
237                 if (isdir == -1) {
238                         struct stat statbuf
239                         {
240                         };
241                         if (stat((pathstring + "/" + node.name).c_str(), &statbuf))
242                                 continue;
243                         isdir = ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
244                 }
245                 node.dir = isdir;
246                 listing.push_back(node);
247         }
248         closedir(dp);
249
250         return listing;
251 }
252
253 bool CreateDir(const std::string &path)
254 {
255         int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
256         if (r == 0) {
257                 return true;
258         }
259
260         // If already exists, return true
261         if (errno == EEXIST)
262                 return true;
263         return false;
264 }
265
266 bool PathExists(const std::string &path)
267 {
268         struct stat st
269         {
270         };
271         return (stat(path.c_str(), &st) == 0);
272 }
273
274 bool IsPathAbsolute(const std::string &path)
275 {
276         return path[0] == '/';
277 }
278
279 bool IsDir(const std::string &path)
280 {
281         struct stat statbuf
282         {
283         };
284         if (stat(path.c_str(), &statbuf))
285                 return false; // Actually error; but certainly not a directory
286         return ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
287 }
288
289 bool IsDirDelimiter(char c)
290 {
291         return c == '/';
292 }
293
294 bool RecursiveDelete(const std::string &path)
295 {
296         /*
297                 Execute the 'rm' command directly, by fork() and execve()
298         */
299
300         infostream << "Removing \"" << path << "\"" << std::endl;
301
302         // return false;
303
304         pid_t child_pid = fork();
305
306         if (child_pid == 0) {
307                 // Child
308                 char argv_data[3][10000];
309 #ifdef __ANDROID__
310                 strcpy(argv_data[0], "/system/bin/rm");
311 #else
312                 strcpy(argv_data[0], "/bin/rm");
313 #endif
314                 strcpy(argv_data[1], "-rf");
315                 strncpy(argv_data[2], path.c_str(), sizeof(argv_data[2]) - 1);
316                 char *argv[4];
317                 argv[0] = argv_data[0];
318                 argv[1] = argv_data[1];
319                 argv[2] = argv_data[2];
320                 argv[3] = NULL;
321
322                 verbosestream << "Executing '" << argv[0] << "' '" << argv[1] << "' '"
323                               << argv[2] << "'" << std::endl;
324
325                 execv(argv[0], argv);
326
327                 // Execv shouldn't return. Failed.
328                 _exit(1);
329         } else {
330                 // Parent
331                 int child_status;
332                 pid_t tpid;
333                 do {
334                         tpid = wait(&child_status);
335                         // if(tpid != child_pid) process_terminated(tpid);
336                 } while (tpid != child_pid);
337                 return (child_status == 0);
338         }
339 }
340
341 bool DeleteSingleFileOrEmptyDirectory(const std::string &path)
342 {
343         if (IsDir(path)) {
344                 bool did = (rmdir(path.c_str()) == 0);
345                 if (!did)
346                         errorstream << "rmdir errno: " << errno << ": " << strerror(errno)
347                                     << std::endl;
348                 return did;
349         }
350
351         bool did = (unlink(path.c_str()) == 0);
352         if (!did)
353                 errorstream << "unlink errno: " << errno << ": " << strerror(errno)
354                             << std::endl;
355         return did;
356 }
357
358 std::string TempPath()
359 {
360         /*
361                 Should the environment variables TMPDIR, TMP and TEMP
362                 and the macro P_tmpdir (if defined by stdio.h) be checked
363                 before falling back on /tmp?
364
365                 Probably not, because this function is intended to be
366                 compatible with lua's os.tmpname which under the default
367                 configuration hardcodes mkstemp("/tmp/lua_XXXXXX").
368         */
369 #ifdef __ANDROID__
370         return g_settings->get("TMPFolder");
371 #else
372         return DIR_DELIM "tmp";
373 #endif
374 }
375
376 #endif
377
378 void GetRecursiveDirs(std::vector<std::string> &dirs, const std::string &dir)
379 {
380         static const std::set<char> chars_to_ignore = {'_', '.'};
381         if (dir.empty() || !IsDir(dir))
382                 return;
383         dirs.push_back(dir);
384         fs::GetRecursiveSubPaths(dir, dirs, false, chars_to_ignore);
385 }
386
387 std::vector<std::string> GetRecursiveDirs(const std::string &dir)
388 {
389         std::vector<std::string> result;
390         GetRecursiveDirs(result, dir);
391         return result;
392 }
393
394 void GetRecursiveSubPaths(const std::string &path, std::vector<std::string> &dst,
395                 bool list_files, const std::set<char> &ignore)
396 {
397         std::vector<DirListNode> content = GetDirListing(path);
398         for (const auto &n : content) {
399                 std::string fullpath = path + DIR_DELIM + n.name;
400                 if (ignore.count(n.name[0]))
401                         continue;
402                 if (list_files || n.dir)
403                         dst.push_back(fullpath);
404                 if (n.dir)
405                         GetRecursiveSubPaths(fullpath, dst, list_files, ignore);
406         }
407 }
408
409 bool DeletePaths(const std::vector<std::string> &paths)
410 {
411         bool success = true;
412         // Go backwards to succesfully delete the output of GetRecursiveSubPaths
413         for (int i = paths.size() - 1; i >= 0; i--) {
414                 const std::string &path = paths[i];
415                 bool did = DeleteSingleFileOrEmptyDirectory(path);
416                 if (!did) {
417                         errorstream << "Failed to delete " << path << std::endl;
418                         success = false;
419                 }
420         }
421         return success;
422 }
423
424 bool RecursiveDeleteContent(const std::string &path)
425 {
426         infostream << "Removing content of \"" << path << "\"" << std::endl;
427         std::vector<DirListNode> list = GetDirListing(path);
428         for (const DirListNode &dln : list) {
429                 if (trim(dln.name) == "." || trim(dln.name) == "..")
430                         continue;
431                 std::string childpath = path + DIR_DELIM + dln.name;
432                 bool r = RecursiveDelete(childpath);
433                 if (!r) {
434                         errorstream << "Removing \"" << childpath << "\" failed"
435                                     << std::endl;
436                         return false;
437                 }
438         }
439         return true;
440 }
441
442 bool CreateAllDirs(const std::string &path)
443 {
444
445         std::vector<std::string> tocreate;
446         std::string basepath = path;
447         while (!PathExists(basepath)) {
448                 tocreate.push_back(basepath);
449                 basepath = RemoveLastPathComponent(basepath);
450                 if (basepath.empty())
451                         break;
452         }
453         for (int i = tocreate.size() - 1; i >= 0; i--)
454                 if (!CreateDir(tocreate[i]))
455                         return false;
456         return true;
457 }
458
459 bool CopyFileContents(const std::string &source, const std::string &target)
460 {
461         FILE *sourcefile = fopen(source.c_str(), "rb");
462         if (sourcefile == NULL) {
463                 errorstream << source << ": can't open for reading: " << strerror(errno)
464                             << std::endl;
465                 return false;
466         }
467
468         FILE *targetfile = fopen(target.c_str(), "wb");
469         if (targetfile == NULL) {
470                 errorstream << target << ": can't open for writing: " << strerror(errno)
471                             << std::endl;
472                 fclose(sourcefile);
473                 return false;
474         }
475
476         size_t total = 0;
477         bool retval = true;
478         bool done = false;
479         char readbuffer[BUFSIZ];
480         while (!done) {
481                 size_t readbytes = fread(readbuffer, 1, sizeof(readbuffer), sourcefile);
482                 total += readbytes;
483                 if (ferror(sourcefile)) {
484                         errorstream << source << ": IO error: " << strerror(errno)
485                                     << std::endl;
486                         retval = false;
487                         done = true;
488                 }
489                 if (readbytes > 0) {
490                         fwrite(readbuffer, 1, readbytes, targetfile);
491                 }
492                 if (feof(sourcefile) || ferror(sourcefile)) {
493                         // flush destination file to catch write errors
494                         // (e.g. disk full)
495                         fflush(targetfile);
496                         done = true;
497                 }
498                 if (ferror(targetfile)) {
499                         errorstream << target << ": IO error: " << strerror(errno)
500                                     << std::endl;
501                         retval = false;
502                         done = true;
503                 }
504         }
505         infostream << "copied " << total << " bytes from " << source << " to " << target
506                    << std::endl;
507         fclose(sourcefile);
508         fclose(targetfile);
509         return retval;
510 }
511
512 bool CopyDir(const std::string &source, const std::string &target)
513 {
514         if (PathExists(source)) {
515                 if (!PathExists(target)) {
516                         fs::CreateAllDirs(target);
517                 }
518                 bool retval = true;
519                 std::vector<DirListNode> content = fs::GetDirListing(source);
520
521                 for (const auto &dln : content) {
522                         std::string sourcechild = source + DIR_DELIM + dln.name;
523                         std::string targetchild = target + DIR_DELIM + dln.name;
524                         if (dln.dir) {
525                                 if (!fs::CopyDir(sourcechild, targetchild)) {
526                                         retval = false;
527                                 }
528                         } else {
529                                 if (!fs::CopyFileContents(sourcechild, targetchild)) {
530                                         retval = false;
531                                 }
532                         }
533                 }
534                 return retval;
535         }
536
537         return false;
538 }
539
540 bool PathStartsWith(const std::string &path, const std::string &prefix)
541 {
542         size_t pathsize = path.size();
543         size_t pathpos = 0;
544         size_t prefixsize = prefix.size();
545         size_t prefixpos = 0;
546         for (;;) {
547                 bool delim1 = pathpos == pathsize || IsDirDelimiter(path[pathpos]);
548                 bool delim2 = prefixpos == prefixsize ||
549                               IsDirDelimiter(prefix[prefixpos]);
550
551                 if (delim1 != delim2)
552                         return false;
553
554                 if (delim1) {
555                         while (pathpos < pathsize && IsDirDelimiter(path[pathpos]))
556                                 ++pathpos;
557                         while (prefixpos < prefixsize &&
558                                         IsDirDelimiter(prefix[prefixpos]))
559                                 ++prefixpos;
560                         if (prefixpos == prefixsize)
561                                 return true;
562                         if (pathpos == pathsize)
563                                 return false;
564                 } else {
565                         size_t len = 0;
566                         do {
567                                 char pathchar = path[pathpos + len];
568                                 char prefixchar = prefix[prefixpos + len];
569                                 if (FILESYS_CASE_INSENSITIVE) {
570                                         pathchar = tolower(pathchar);
571                                         prefixchar = tolower(prefixchar);
572                                 }
573                                 if (pathchar != prefixchar)
574                                         return false;
575                                 ++len;
576                         } while (pathpos + len < pathsize &&
577                                         !IsDirDelimiter(path[pathpos + len]) &&
578                                         prefixpos + len < prefixsize &&
579                                         !IsDirDelimiter(prefix[prefixpos + len]));
580                         pathpos += len;
581                         prefixpos += len;
582                 }
583         }
584 }
585
586 std::string RemoveLastPathComponent(
587                 const std::string &path, std::string *removed, int count)
588 {
589         if (removed)
590                 *removed = "";
591
592         size_t remaining = path.size();
593
594         for (int i = 0; i < count; ++i) {
595                 // strip a dir delimiter
596                 while (remaining != 0 && IsDirDelimiter(path[remaining - 1]))
597                         remaining--;
598                 // strip a path component
599                 size_t component_end = remaining;
600                 while (remaining != 0 && !IsDirDelimiter(path[remaining - 1]))
601                         remaining--;
602                 size_t component_start = remaining;
603                 // strip a dir delimiter
604                 while (remaining != 0 && IsDirDelimiter(path[remaining - 1]))
605                         remaining--;
606                 if (removed) {
607                         std::string component = path.substr(
608                                         component_start, component_end - component_start);
609                         if (i)
610                                 *removed = component + DIR_DELIM + *removed;
611                         else
612                                 *removed = component;
613                 }
614         }
615         return path.substr(0, remaining);
616 }
617
618 std::string RemoveRelativePathComponents(std::string path)
619 {
620         size_t pos = path.size();
621         size_t dotdot_count = 0;
622         while (pos != 0) {
623                 size_t component_with_delim_end = pos;
624                 // skip a dir delimiter
625                 while (pos != 0 && IsDirDelimiter(path[pos - 1]))
626                         pos--;
627                 // strip a path component
628                 size_t component_end = pos;
629                 while (pos != 0 && !IsDirDelimiter(path[pos - 1]))
630                         pos--;
631                 size_t component_start = pos;
632
633                 std::string component = path.substr(
634                                 component_start, component_end - component_start);
635                 bool remove_this_component = false;
636                 if (component == ".") {
637                         remove_this_component = true;
638                 } else if (component == "..") {
639                         remove_this_component = true;
640                         dotdot_count += 1;
641                 } else if (dotdot_count != 0) {
642                         remove_this_component = true;
643                         dotdot_count -= 1;
644                 }
645
646                 if (remove_this_component) {
647                         while (pos != 0 && IsDirDelimiter(path[pos - 1]))
648                                 pos--;
649                         if (component_start == 0) {
650                                 // We need to remove the delemiter too
651                                 path = path.substr(component_with_delim_end,
652                                                 std::string::npos);
653                         } else {
654                                 path = path.substr(0, pos) + DIR_DELIM +
655                                        path.substr(component_with_delim_end,
656                                                        std::string::npos);
657                         }
658                         if (pos > 0)
659                                 pos++;
660                 }
661         }
662
663         if (dotdot_count > 0)
664                 return "";
665
666         // remove trailing dir delimiters
667         pos = path.size();
668         while (pos != 0 && IsDirDelimiter(path[pos - 1]))
669                 pos--;
670         return path.substr(0, pos);
671 }
672
673 std::string AbsolutePath(const std::string &path)
674 {
675 #ifdef _WIN32
676         char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH);
677 #else
678         char *abs_path = realpath(path.c_str(), NULL);
679 #endif
680         if (!abs_path)
681                 return "";
682         std::string abs_path_str(abs_path);
683         free(abs_path);
684         return abs_path_str;
685 }
686
687 const char *GetFilenameFromPath(const char *path)
688 {
689         const char *filename = strrchr(path, DIR_DELIM_CHAR);
690         // Consistent with IsDirDelimiter this function handles '/' too
691         if (DIR_DELIM_CHAR != '/') {
692                 const char *tmp = strrchr(path, '/');
693                 if (tmp && tmp > filename)
694                         filename = tmp;
695         }
696         return filename ? filename + 1 : path;
697 }
698
699 bool safeWriteToFile(const std::string &path, const std::string &content)
700 {
701         std::string tmp_file = path + ".~mt";
702
703         // Write to a tmp file
704         std::ofstream os(tmp_file.c_str(), std::ios::binary);
705         if (!os.good())
706                 return false;
707         os << content;
708         os.flush();
709         os.close();
710         if (os.fail()) {
711                 // Remove the temporary file because writing it failed and it's useless.
712                 remove(tmp_file.c_str());
713                 return false;
714         }
715
716         bool rename_success = false;
717
718         // Move the finished temporary file over the real file
719 #ifdef _WIN32
720         // When creating the file, it can cause Windows Search indexer, virus scanners and
721         // other apps to query the file. This can make the move file call below fail. We
722         // retry up to 5 times, with a 1ms sleep between, before we consider the whole
723         // operation failed
724         int number_attempts = 0;
725         while (number_attempts < 5) {
726                 rename_success = MoveFileEx(tmp_file.c_str(), path.c_str(),
727                                 MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
728                 if (rename_success)
729                         break;
730                 sleep_ms(1);
731                 ++number_attempts;
732         }
733 #else
734         // On POSIX compliant systems rename() is specified to be able to swap the
735         // file in place of the destination file, making this a truly error-proof
736         // transaction.
737         rename_success = rename(tmp_file.c_str(), path.c_str()) == 0;
738 #endif
739         if (!rename_success) {
740                 warningstream << "Failed to write to file: " << path.c_str() << std::endl;
741                 // Remove the temporary file because moving it over the target file
742                 // failed.
743                 remove(tmp_file.c_str());
744                 return false;
745         }
746
747         return true;
748 }
749
750 bool Rename(const std::string &from, const std::string &to)
751 {
752         return rename(from.c_str(), to.c_str()) == 0;
753 }
754
755 } // namespace fs