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