]> git.lizzy.rs Git - minetest.git/blob - src/filesys.cpp
7edb60bcd19a566a7da40c29b296282598d02a07
[minetest.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 <cstdlib>
25 #include <cstring>
26 #include <cerrno>
27 #include <fstream>
28 #include "log.h"
29 #include "config.h"
30 #include "porting.h"
31 #ifndef SERVER
32 #include "irr_ptr.h"
33 #endif
34
35 namespace fs
36 {
37
38 #ifdef _WIN32
39
40 /***********
41  * Windows *
42  ***********/
43
44 #ifndef _WIN32_WINNT
45 #define _WIN32_WINNT 0x0501
46 #endif
47 #include <windows.h>
48 #include <shlwapi.h>
49 #include <io.h>
50 #include <direct.h>
51
52 std::vector<DirListNode> GetDirListing(const std::string &pathstring)
53 {
54         std::vector<DirListNode> listing;
55
56         WIN32_FIND_DATA FindFileData;
57         HANDLE hFind = INVALID_HANDLE_VALUE;
58         DWORD dwError;
59
60         std::string dirSpec = pathstring + "\\*";
61
62         // Find the first file in the directory.
63         hFind = FindFirstFile(dirSpec.c_str(), &FindFileData);
64
65         if (hFind == INVALID_HANDLE_VALUE) {
66                 dwError = GetLastError();
67                 if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) {
68                         errorstream << "GetDirListing: FindFirstFile error."
69                                         << " Error is " << dwError << std::endl;
70                 }
71         } else {
72                 // NOTE:
73                 // Be very sure to not include '..' in the results, it will
74                 // result in an epic failure when deleting stuff.
75
76                 DirListNode node;
77                 node.name = FindFileData.cFileName;
78                 node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
79                 if (node.name != "." && node.name != "..")
80                         listing.push_back(node);
81
82                 // List all the other files in the directory.
83                 while (FindNextFile(hFind, &FindFileData) != 0) {
84                         DirListNode node;
85                         node.name = FindFileData.cFileName;
86                         node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
87                         if(node.name != "." && node.name != "..")
88                                 listing.push_back(node);
89                 }
90
91                 dwError = GetLastError();
92                 FindClose(hFind);
93                 if (dwError != ERROR_NO_MORE_FILES) {
94                         errorstream << "GetDirListing: FindNextFile error."
95                                         << " Error is " << dwError << std::endl;
96                         listing.clear();
97                         return listing;
98                 }
99         }
100         return listing;
101 }
102
103 bool CreateDir(const std::string &path)
104 {
105         bool r = CreateDirectory(path.c_str(), NULL);
106         if(r == true)
107                 return true;
108         if(GetLastError() == ERROR_ALREADY_EXISTS)
109                 return true;
110         return false;
111 }
112
113 bool PathExists(const std::string &path)
114 {
115         return (GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES);
116 }
117
118 bool IsPathAbsolute(const std::string &path)
119 {
120         return !PathIsRelative(path.c_str());
121 }
122
123 bool IsDir(const std::string &path)
124 {
125         DWORD attr = GetFileAttributes(path.c_str());
126         return (attr != INVALID_FILE_ATTRIBUTES &&
127                         (attr & FILE_ATTRIBUTE_DIRECTORY));
128 }
129
130 bool IsDirDelimiter(char c)
131 {
132         return c == '/' || c == '\\';
133 }
134
135 bool RecursiveDelete(const std::string &path)
136 {
137         infostream << "Recursively deleting \"" << path << "\"" << std::endl;
138         if (!IsDir(path)) {
139                 infostream << "RecursiveDelete: Deleting file  " << path << std::endl;
140                 if (!DeleteFile(path.c_str())) {
141                         errorstream << "RecursiveDelete: Failed to delete file "
142                                         << path << std::endl;
143                         return false;
144                 }
145                 return true;
146         }
147         infostream << "RecursiveDelete: Deleting content of directory "
148                         << path << std::endl;
149         std::vector<DirListNode> content = GetDirListing(path);
150         for (const DirListNode &n: content) {
151                 std::string fullpath = path + DIR_DELIM + n.name;
152                 if (!RecursiveDelete(fullpath)) {
153                         errorstream << "RecursiveDelete: Failed to recurse to "
154                                         << fullpath << std::endl;
155                         return false;
156                 }
157         }
158         infostream << "RecursiveDelete: Deleting directory " << path << std::endl;
159         if (!RemoveDirectory(path.c_str())) {
160                 errorstream << "Failed to recursively delete directory "
161                                 << path << std::endl;
162                 return false;
163         }
164         return true;
165 }
166
167 bool DeleteSingleFileOrEmptyDirectory(const std::string &path)
168 {
169         DWORD attr = GetFileAttributes(path.c_str());
170         bool is_directory = (attr != INVALID_FILE_ATTRIBUTES &&
171                         (attr & FILE_ATTRIBUTE_DIRECTORY));
172         if(!is_directory)
173         {
174                 bool did = DeleteFile(path.c_str());
175                 return did;
176         }
177         else
178         {
179                 bool did = RemoveDirectory(path.c_str());
180                 return did;
181         }
182 }
183
184 std::string TempPath()
185 {
186         DWORD bufsize = GetTempPath(0, NULL);
187         if(bufsize == 0){
188                 errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
189                 return "";
190         }
191         std::string buf;
192         buf.resize(bufsize);
193         DWORD len = GetTempPath(bufsize, &buf[0]);
194         if(len == 0 || len > bufsize){
195                 errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
196                 return "";
197         }
198         buf.resize(len);
199         return buf;
200 }
201
202 std::string CreateTempFile()
203 {
204         std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
205         _mktemp_s(&path[0], path.size() + 1); // modifies path
206         HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr,
207                 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
208         if (file == INVALID_HANDLE_VALUE)
209                 return "";
210         CloseHandle(file);
211         return path;
212 }
213
214 #else
215
216 /*********
217  * POSIX *
218  *********/
219
220 #include <sys/types.h>
221 #include <dirent.h>
222 #include <sys/stat.h>
223 #include <sys/wait.h>
224 #include <unistd.h>
225
226 std::vector<DirListNode> GetDirListing(const std::string &pathstring)
227 {
228         std::vector<DirListNode> listing;
229
230         DIR *dp;
231         struct dirent *dirp;
232         if((dp = opendir(pathstring.c_str())) == NULL) {
233                 //infostream<<"Error("<<errno<<") opening "<<pathstring<<std::endl;
234                 return listing;
235         }
236
237         while ((dirp = readdir(dp)) != NULL) {
238                 // NOTE:
239                 // Be very sure to not include '..' in the results, it will
240                 // result in an epic failure when deleting stuff.
241                 if(strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)
242                         continue;
243
244                 DirListNode node;
245                 node.name = dirp->d_name;
246
247                 int isdir = -1; // -1 means unknown
248
249                 /*
250                         POSIX doesn't define d_type member of struct dirent and
251                         certain filesystems on glibc/Linux will only return
252                         DT_UNKNOWN for the d_type member.
253
254                         Also we don't know whether symlinks are directories or not.
255                 */
256 #ifdef _DIRENT_HAVE_D_TYPE
257                 if(dirp->d_type != DT_UNKNOWN && dirp->d_type != DT_LNK)
258                         isdir = (dirp->d_type == DT_DIR);
259 #endif /* _DIRENT_HAVE_D_TYPE */
260
261                 /*
262                         Was d_type DT_UNKNOWN, DT_LNK or nonexistent?
263                         If so, try stat().
264                 */
265                 if(isdir == -1) {
266                         struct stat statbuf{};
267                         if (stat((pathstring + "/" + node.name).c_str(), &statbuf))
268                                 continue;
269                         isdir = ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
270                 }
271                 node.dir = isdir;
272                 listing.push_back(node);
273         }
274         closedir(dp);
275
276         return listing;
277 }
278
279 bool CreateDir(const std::string &path)
280 {
281         int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
282         if (r == 0) {
283                 return true;
284         }
285
286         // If already exists, return true
287         if (errno == EEXIST)
288                 return true;
289         return false;
290
291 }
292
293 bool PathExists(const std::string &path)
294 {
295         struct stat st{};
296         return (stat(path.c_str(),&st) == 0);
297 }
298
299 bool IsPathAbsolute(const std::string &path)
300 {
301         return path[0] == '/';
302 }
303
304 bool IsDir(const std::string &path)
305 {
306         struct stat statbuf{};
307         if(stat(path.c_str(), &statbuf))
308                 return false; // Actually error; but certainly not a directory
309         return ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
310 }
311
312 bool IsDirDelimiter(char c)
313 {
314         return c == '/';
315 }
316
317 bool RecursiveDelete(const std::string &path)
318 {
319         /*
320                 Execute the 'rm' command directly, by fork() and execve()
321         */
322
323         infostream<<"Removing \""<<path<<"\""<<std::endl;
324
325         pid_t child_pid = fork();
326
327         if(child_pid == 0)
328         {
329                 // Child
330                 const char *argv[4] = {
331 #ifdef __ANDROID__
332                         "/system/bin/rm",
333 #else
334                         "/bin/rm",
335 #endif
336                         "-rf",
337                         path.c_str(),
338                         NULL
339                 };
340
341                 verbosestream<<"Executing '"<<argv[0]<<"' '"<<argv[1]<<"' '"
342                                 <<argv[2]<<"'"<<std::endl;
343
344                 execv(argv[0], const_cast<char**>(argv));
345
346                 // Execv shouldn't return. Failed.
347                 _exit(1);
348         }
349         else
350         {
351                 // Parent
352                 int child_status;
353                 pid_t tpid;
354                 do{
355                         tpid = wait(&child_status);
356                 }while(tpid != child_pid);
357                 return (child_status == 0);
358         }
359 }
360
361 bool DeleteSingleFileOrEmptyDirectory(const std::string &path)
362 {
363         if (IsDir(path)) {
364                 bool did = (rmdir(path.c_str()) == 0);
365                 if (!did)
366                         errorstream << "rmdir errno: " << errno << ": " << strerror(errno)
367                                         << std::endl;
368                 return did;
369         }
370
371         bool did = (unlink(path.c_str()) == 0);
372         if (!did)
373                 errorstream << "unlink errno: " << errno << ": " << strerror(errno)
374                                 << std::endl;
375         return did;
376 }
377
378 std::string TempPath()
379 {
380         /*
381                 Should the environment variables TMPDIR, TMP and TEMP
382                 and the macro P_tmpdir (if defined by stdio.h) be checked
383                 before falling back on /tmp?
384
385                 Probably not, because this function is intended to be
386                 compatible with lua's os.tmpname which under the default
387                 configuration hardcodes mkstemp("/tmp/lua_XXXXXX").
388         */
389
390 #ifdef __ANDROID__
391         return porting::path_cache;
392 #else
393         return DIR_DELIM "tmp";
394 #endif
395 }
396
397 std::string CreateTempFile()
398 {
399         std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
400         int fd = mkstemp(&path[0]); // modifies path
401         if (fd == -1)
402                 return "";
403         close(fd);
404         return path;
405 }
406
407 #endif
408
409 /****************************
410  * portable implementations *
411  ****************************/
412
413 void GetRecursiveDirs(std::vector<std::string> &dirs, const std::string &dir)
414 {
415         static const std::set<char> chars_to_ignore = { '_', '.' };
416         if (dir.empty() || !IsDir(dir))
417                 return;
418         dirs.push_back(dir);
419         fs::GetRecursiveSubPaths(dir, dirs, false, chars_to_ignore);
420 }
421
422 std::vector<std::string> GetRecursiveDirs(const std::string &dir)
423 {
424         std::vector<std::string> result;
425         GetRecursiveDirs(result, dir);
426         return result;
427 }
428
429 void GetRecursiveSubPaths(const std::string &path,
430                   std::vector<std::string> &dst,
431                   bool list_files,
432                   const std::set<char> &ignore)
433 {
434         std::vector<DirListNode> content = GetDirListing(path);
435         for (const auto &n : content) {
436                 std::string fullpath = path + DIR_DELIM + n.name;
437                 if (ignore.count(n.name[0]))
438                         continue;
439                 if (list_files || n.dir)
440                         dst.push_back(fullpath);
441                 if (n.dir)
442                         GetRecursiveSubPaths(fullpath, dst, list_files, ignore);
443         }
444 }
445
446 bool RecursiveDeleteContent(const std::string &path)
447 {
448         infostream<<"Removing content of \""<<path<<"\""<<std::endl;
449         std::vector<DirListNode> list = GetDirListing(path);
450         for (const DirListNode &dln : list) {
451                 if(trim(dln.name) == "." || trim(dln.name) == "..")
452                         continue;
453                 std::string childpath = path + DIR_DELIM + dln.name;
454                 bool r = RecursiveDelete(childpath);
455                 if(!r) {
456                         errorstream << "Removing \"" << childpath << "\" failed" << std::endl;
457                         return false;
458                 }
459         }
460         return true;
461 }
462
463 bool CreateAllDirs(const std::string &path)
464 {
465
466         std::vector<std::string> tocreate;
467         std::string basepath = path;
468         while(!PathExists(basepath))
469         {
470                 tocreate.push_back(basepath);
471                 basepath = RemoveLastPathComponent(basepath);
472                 if(basepath.empty())
473                         break;
474         }
475         for(int i=tocreate.size()-1;i>=0;i--)
476                 if(!CreateDir(tocreate[i]))
477                         return false;
478         return true;
479 }
480
481 bool CopyFileContents(const std::string &source, const std::string &target)
482 {
483         FILE *sourcefile = fopen(source.c_str(), "rb");
484         if(sourcefile == NULL){
485                 errorstream<<source<<": can't open for reading: "
486                         <<strerror(errno)<<std::endl;
487                 return false;
488         }
489
490         FILE *targetfile = fopen(target.c_str(), "wb");
491         if(targetfile == NULL){
492                 errorstream<<target<<": can't open for writing: "
493                         <<strerror(errno)<<std::endl;
494                 fclose(sourcefile);
495                 return false;
496         }
497
498         size_t total = 0;
499         bool retval = true;
500         bool done = false;
501         char readbuffer[BUFSIZ];
502         while(!done){
503                 size_t readbytes = fread(readbuffer, 1,
504                                 sizeof(readbuffer), sourcefile);
505                 total += readbytes;
506                 if(ferror(sourcefile)){
507                         errorstream<<source<<": IO error: "
508                                 <<strerror(errno)<<std::endl;
509                         retval = false;
510                         done = true;
511                 }
512                 if(readbytes > 0){
513                         fwrite(readbuffer, 1, readbytes, targetfile);
514                 }
515                 if(feof(sourcefile) || ferror(sourcefile)){
516                         // flush destination file to catch write errors
517                         // (e.g. disk full)
518                         fflush(targetfile);
519                         done = true;
520                 }
521                 if(ferror(targetfile)){
522                         errorstream<<target<<": IO error: "
523                                         <<strerror(errno)<<std::endl;
524                         retval = false;
525                         done = true;
526                 }
527         }
528         infostream<<"copied "<<total<<" bytes from "
529                 <<source<<" to "<<target<<std::endl;
530         fclose(sourcefile);
531         fclose(targetfile);
532         return retval;
533 }
534
535 bool CopyDir(const std::string &source, const std::string &target)
536 {
537         if(PathExists(source)){
538                 if(!PathExists(target)){
539                         fs::CreateAllDirs(target);
540                 }
541                 bool retval = true;
542                 std::vector<DirListNode> content = fs::GetDirListing(source);
543
544                 for (const auto &dln : content) {
545                         std::string sourcechild = source + DIR_DELIM + dln.name;
546                         std::string targetchild = target + DIR_DELIM + dln.name;
547                         if(dln.dir){
548                                 if(!fs::CopyDir(sourcechild, targetchild)){
549                                         retval = false;
550                                 }
551                         }
552                         else {
553                                 if(!fs::CopyFileContents(sourcechild, targetchild)){
554                                         retval = false;
555                                 }
556                         }
557                 }
558                 return retval;
559         }
560
561         return false;
562 }
563
564 bool MoveDir(const std::string &source, const std::string &target)
565 {
566         infostream << "Moving \"" << source << "\" to \"" << target << "\"" << std::endl;
567
568         // If target exists as empty folder delete, otherwise error
569         if (fs::PathExists(target)) {
570                 if (rmdir(target.c_str()) != 0) {
571                         errorstream << "MoveDir: target \"" << target
572                                 << "\" exists as file or non-empty folder" << std::endl;
573                         return false;
574                 }
575         }
576
577         // Try renaming first which is instant
578         if (fs::Rename(source, target))
579                 return true;
580
581         infostream << "MoveDir: rename not possible, will copy instead" << std::endl;
582         bool retval = fs::CopyDir(source, target);
583         if (retval)
584                 retval &= fs::RecursiveDelete(source);
585         return retval;
586 }
587
588 bool PathStartsWith(const std::string &path, const std::string &prefix)
589 {
590         size_t pathsize = path.size();
591         size_t pathpos = 0;
592         size_t prefixsize = prefix.size();
593         size_t prefixpos = 0;
594         for(;;){
595                 bool delim1 = pathpos == pathsize
596                         || IsDirDelimiter(path[pathpos]);
597                 bool delim2 = prefixpos == prefixsize
598                         || IsDirDelimiter(prefix[prefixpos]);
599
600                 if(delim1 != delim2)
601                         return false;
602
603                 if(delim1){
604                         while(pathpos < pathsize &&
605                                         IsDirDelimiter(path[pathpos]))
606                                 ++pathpos;
607                         while(prefixpos < prefixsize &&
608                                         IsDirDelimiter(prefix[prefixpos]))
609                                 ++prefixpos;
610                         if(prefixpos == prefixsize)
611                                 return true;
612                         if(pathpos == pathsize)
613                                 return false;
614                 }
615                 else{
616                         size_t len = 0;
617                         do{
618                                 char pathchar = path[pathpos+len];
619                                 char prefixchar = prefix[prefixpos+len];
620                                 if(FILESYS_CASE_INSENSITIVE){
621                                         pathchar = tolower(pathchar);
622                                         prefixchar = tolower(prefixchar);
623                                 }
624                                 if(pathchar != prefixchar)
625                                         return false;
626                                 ++len;
627                         } while(pathpos+len < pathsize
628                                         && !IsDirDelimiter(path[pathpos+len])
629                                         && prefixpos+len < prefixsize
630                                         && !IsDirDelimiter(
631                                                 prefix[prefixpos+len]));
632                         pathpos += len;
633                         prefixpos += len;
634                 }
635         }
636 }
637
638 std::string RemoveLastPathComponent(const std::string &path,
639                 std::string *removed, int count)
640 {
641         if(removed)
642                 removed->clear();
643
644         size_t remaining = path.size();
645
646         for(int i = 0; i < count; ++i){
647                 // strip a dir delimiter
648                 while(remaining != 0 && IsDirDelimiter(path[remaining-1]))
649                         remaining--;
650                 // strip a path component
651                 size_t component_end = remaining;
652                 while(remaining != 0 && !IsDirDelimiter(path[remaining-1]))
653                         remaining--;
654                 size_t component_start = remaining;
655                 // strip a dir delimiter
656                 while(remaining != 0 && IsDirDelimiter(path[remaining-1]))
657                         remaining--;
658                 if(removed){
659                         std::string component = path.substr(component_start,
660                                         component_end - component_start);
661                         if(i)
662                                 *removed = component + DIR_DELIM + *removed;
663                         else
664                                 *removed = component;
665                 }
666         }
667         return path.substr(0, remaining);
668 }
669
670 std::string RemoveRelativePathComponents(std::string path)
671 {
672         size_t pos = path.size();
673         size_t dotdot_count = 0;
674         while (pos != 0) {
675                 size_t component_with_delim_end = pos;
676                 // skip a dir delimiter
677                 while (pos != 0 && IsDirDelimiter(path[pos-1]))
678                         pos--;
679                 // strip a path component
680                 size_t component_end = pos;
681                 while (pos != 0 && !IsDirDelimiter(path[pos-1]))
682                         pos--;
683                 size_t component_start = pos;
684
685                 std::string component = path.substr(component_start,
686                                 component_end - component_start);
687                 bool remove_this_component = false;
688                 if (component == ".") {
689                         remove_this_component = true;
690                 } else if (component == "..") {
691                         remove_this_component = true;
692                         dotdot_count += 1;
693                 } else if (dotdot_count != 0) {
694                         remove_this_component = true;
695                         dotdot_count -= 1;
696                 }
697
698                 if (remove_this_component) {
699                         while (pos != 0 && IsDirDelimiter(path[pos-1]))
700                                 pos--;
701                         if (component_start == 0) {
702                                 // We need to remove the delemiter too
703                                 path = path.substr(component_with_delim_end, std::string::npos);
704                         } else {
705                                 path = path.substr(0, pos) + DIR_DELIM +
706                                         path.substr(component_with_delim_end, std::string::npos);
707                         }
708                         if (pos > 0)
709                                 pos++;
710                 }
711         }
712
713         if (dotdot_count > 0)
714                 return "";
715
716         // remove trailing dir delimiters
717         pos = path.size();
718         while (pos != 0 && IsDirDelimiter(path[pos-1]))
719                 pos--;
720         return path.substr(0, pos);
721 }
722
723 std::string AbsolutePath(const std::string &path)
724 {
725 #ifdef _WIN32
726         char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH);
727 #else
728         char *abs_path = realpath(path.c_str(), NULL);
729 #endif
730         if (!abs_path) return "";
731         std::string abs_path_str(abs_path);
732         free(abs_path);
733         return abs_path_str;
734 }
735
736 const char *GetFilenameFromPath(const char *path)
737 {
738         const char *filename = strrchr(path, DIR_DELIM_CHAR);
739         // Consistent with IsDirDelimiter this function handles '/' too
740         if (DIR_DELIM_CHAR != '/') {
741                 const char *tmp = strrchr(path, '/');
742                 if (tmp && tmp > filename)
743                         filename = tmp;
744         }
745         return filename ? filename + 1 : path;
746 }
747
748 bool safeWriteToFile(const std::string &path, const std::string &content)
749 {
750         std::string tmp_file = path + ".~mt";
751
752         // Write to a tmp file
753         std::ofstream os(tmp_file.c_str(), std::ios::binary);
754         if (!os.good())
755                 return false;
756         os << content;
757         os.flush();
758         os.close();
759         if (os.fail()) {
760                 // Remove the temporary file because writing it failed and it's useless.
761                 remove(tmp_file.c_str());
762                 return false;
763         }
764
765         bool rename_success = false;
766
767         // Move the finished temporary file over the real file
768 #ifdef _WIN32
769         // When creating the file, it can cause Windows Search indexer, virus scanners and other apps
770         // to query the file. This can make the move file call below fail.
771         // We retry up to 5 times, with a 1ms sleep between, before we consider the whole operation failed
772         int number_attempts = 0;
773         while (number_attempts < 5) {
774                 rename_success = MoveFileEx(tmp_file.c_str(), path.c_str(),
775                                 MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
776                 if (rename_success)
777                         break;
778                 sleep_ms(1);
779                 ++number_attempts;
780         }
781 #else
782         // On POSIX compliant systems rename() is specified to be able to swap the
783         // file in place of the destination file, making this a truly error-proof
784         // transaction.
785         rename_success = rename(tmp_file.c_str(), path.c_str()) == 0;
786 #endif
787         if (!rename_success) {
788                 warningstream << "Failed to write to file: " << path.c_str() << std::endl;
789                 // Remove the temporary file because moving it over the target file
790                 // failed.
791                 remove(tmp_file.c_str());
792                 return false;
793         }
794
795         return true;
796 }
797
798 #ifndef SERVER
799 bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string &destination)
800 {
801         // Be careful here not to touch the global file hierarchy in Irrlicht
802         // since this function needs to be thread-safe!
803
804         io::IArchiveLoader *zip_loader = nullptr;
805         for (u32 i = 0; i < fs->getArchiveLoaderCount(); i++) {
806                 if (fs->getArchiveLoader(i)->isALoadableFileFormat(io::EFAT_ZIP)) {
807                         zip_loader = fs->getArchiveLoader(i);
808                         break;
809                 }
810         }
811         if (!zip_loader) {
812                 warningstream << "fs::extractZipFile(): Irrlicht said it doesn't support ZIPs." << std::endl;
813                 return false;
814         }
815
816         irr_ptr<io::IFileArchive> opened_zip(zip_loader->createArchive(filename, false, false));
817         const io::IFileList* files_in_zip = opened_zip->getFileList();
818
819         for (u32 i = 0; i < files_in_zip->getFileCount(); i++) {
820                 std::string fullpath = destination + DIR_DELIM;
821                 fullpath += files_in_zip->getFullFileName(i).c_str();
822                 std::string fullpath_dir = fs::RemoveLastPathComponent(fullpath);
823
824                 if (files_in_zip->isDirectory(i))
825                         continue; // ignore, we create dirs as necessary
826
827                 if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir))
828                         return false;
829
830                 irr_ptr<io::IReadFile> toread(opened_zip->createAndOpenFile(i));
831
832                 std::ofstream os(fullpath.c_str(), std::ios::binary);
833                 if (!os.good())
834                         return false;
835
836                 char buffer[4096];
837                 long total_read = 0;
838
839                 while (total_read < toread->getSize()) {
840                         long bytes_read = toread->read(buffer, sizeof(buffer));
841                         bool error = true;
842                         if (bytes_read != 0) {
843                                 os.write(buffer, bytes_read);
844                                 error = os.fail();
845                         }
846                         if (error) {
847                                 os.close();
848                                 remove(fullpath.c_str());
849                                 return false;
850                         }
851                         total_read += bytes_read;
852                 }
853         }
854
855         return true;
856 }
857 #endif
858
859 bool ReadFile(const std::string &path, std::string &out)
860 {
861         std::ifstream is(path, std::ios::binary | std::ios::ate);
862         if (!is.good()) {
863                 return false;
864         }
865
866         auto size = is.tellg();
867         out.resize(size);
868         is.seekg(0);
869         is.read(&out[0], size);
870
871         return !is.fail();
872 }
873
874 bool Rename(const std::string &from, const std::string &to)
875 {
876         return rename(from.c_str(), to.c_str()) == 0;
877 }
878
879 } // namespace fs