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