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