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