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