X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Ffilesys.cpp;h=f61b39b9465b191badf70befdb27da41e27b13f5;hb=09f9e465e760cb8fd791222405a9e5e68a676ba0;hp=8248a13d46f3703c48061faa68f3b2e150289f8e;hpb=7cfb71385d00d1cbbbfd9c76f6c01adafa9e648a;p=oweals%2Fminetest.git diff --git a/src/filesys.cpp b/src/filesys.cpp index 8248a13d4..f61b39b94 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -1,26 +1,35 @@ /* -Minetest-c55 -Copyright (C) 2010 celeron55, Perttu Ahola +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License along +You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "filesys.h" -#include "strfnd.h" +#include "util/string.h" #include -#include +#include +#include +#include +#include +#include "log.h" +#include "config.h" +#include "porting.h" +#ifdef __ANDROID__ +#include "settings.h" // For g_settings +#endif namespace fs { @@ -28,57 +37,29 @@ namespace fs #ifdef _WIN32 // WINDOWS #define _WIN32_WINNT 0x0501 -#include -#include -#include -#include -#include -#include - -#define BUFSIZE MAX_PATH +#include +#include -std::vector GetDirListing(std::string pathstring) +std::vector GetDirListing(const std::string &pathstring) { std::vector listing; WIN32_FIND_DATA FindFileData; HANDLE hFind = INVALID_HANDLE_VALUE; DWORD dwError; - LPTSTR DirSpec; - INT retval; - - DirSpec = (LPTSTR) malloc (BUFSIZE); - - if( DirSpec == NULL ) - { - printf( "Insufficient memory available\n" ); - retval = 1; - goto Cleanup; - } - // Check that the input is not larger than allowed. - if (pathstring.size() > (BUFSIZE - 2)) - { - _tprintf(TEXT("Input directory is too large.\n")); - retval = 3; - goto Cleanup; - } - - //_tprintf (TEXT("Target directory is %s.\n"), pathstring.c_str()); - - sprintf(DirSpec, "%s", (pathstring + "\\*").c_str()); + std::string dirSpec = pathstring + "\\*"; // Find the first file in the directory. - hFind = FindFirstFile(DirSpec, &FindFileData); + hFind = FindFirstFile(dirSpec.c_str(), &FindFileData); - if (hFind == INVALID_HANDLE_VALUE) - { - _tprintf (TEXT("Invalid file handle. Error is %u.\n"), - GetLastError()); - retval = (-1); - } - else - { + if (hFind == INVALID_HANDLE_VALUE) { + dwError = GetLastError(); + if (dwError != ERROR_FILE_NOT_FOUND && dwError != ERROR_PATH_NOT_FOUND) { + errorstream << "GetDirListing: FindFirstFile error." + << " Error is " << dwError << std::endl; + } + } else { // NOTE: // Be very sure to not include '..' in the results, it will // result in an epic failure when deleting stuff. @@ -86,12 +67,11 @@ std::vector GetDirListing(std::string pathstring) DirListNode node; node.name = FindFileData.cFileName; node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; - if(node.name != "." && node.name != "..") + if (node.name != "." && node.name != "..") listing.push_back(node); // List all the other files in the directory. - while (FindNextFile(hFind, &FindFileData) != 0) - { + while (FindNextFile(hFind, &FindFileData) != 0) { DirListNode node; node.name = FindFileData.cFileName; node.dir = FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; @@ -101,29 +81,17 @@ std::vector GetDirListing(std::string pathstring) dwError = GetLastError(); FindClose(hFind); - if (dwError != ERROR_NO_MORE_FILES) - { - _tprintf (TEXT("FindNextFile error. Error is %u.\n"), - dwError); - retval = (-1); - goto Cleanup; - } + if (dwError != ERROR_NO_MORE_FILES) { + errorstream << "GetDirListing: FindNextFile error." + << " Error is " << dwError << std::endl; + listing.clear(); + return listing; + } } - retval = 0; - -Cleanup: - free(DirSpec); - - if(retval != 0) listing.clear(); - - //for(unsigned int i=0; i content = GetDirListing(path); + for (const DirListNode &n: content) { + std::string fullpath = path + DIR_DELIM + n.name; + if (!RecursiveDelete(fullpath)) { + errorstream << "RecursiveDelete: Failed to recurse to " + << fullpath << std::endl; + return false; + } + } + infostream << "RecursiveDelete: Deleting directory " << path << std::endl; + if (!RemoveDirectory(path.c_str())) { + errorstream << "Failed to recursively delete directory " + << path << std::endl; + return false; + } return true; } +bool DeleteSingleFileOrEmptyDirectory(const std::string &path) +{ + DWORD attr = GetFileAttributes(path.c_str()); + bool is_directory = (attr != INVALID_FILE_ATTRIBUTES && + (attr & FILE_ATTRIBUTE_DIRECTORY)); + if(!is_directory) + { + bool did = DeleteFile(path.c_str()); + return did; + } + else + { + bool did = RemoveDirectory(path.c_str()); + return did; + } +} + +std::string TempPath() +{ + DWORD bufsize = GetTempPath(0, NULL); + if(bufsize == 0){ + errorstream<<"GetTempPath failed, error = "< buf(bufsize); + DWORD len = GetTempPath(bufsize, &buf[0]); + if(len == 0 || len > bufsize){ + errorstream<<"GetTempPath failed, error = "< #include -#include #include #include +#include -std::vector GetDirListing(std::string pathstring) +std::vector GetDirListing(const std::string &pathstring) { std::vector listing; - DIR *dp; - struct dirent *dirp; - if((dp = opendir(pathstring.c_str())) == NULL) { - //std::cout<<"Error("<d_name[0]!='.'){ - DirListNode node; - node.name = dirp->d_name; - if(dirp->d_type == DT_DIR) node.dir = true; - else node.dir = false; - if(node.name != "." && node.name != "..") - listing.push_back(node); + if(strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) + continue; + + DirListNode node; + node.name = dirp->d_name; + + int isdir = -1; // -1 means unknown + + /* + POSIX doesn't define d_type member of struct dirent and + certain filesystems on glibc/Linux will only return + DT_UNKNOWN for the d_type member. + + Also we don't know whether symlinks are directories or not. + */ +#ifdef _DIRENT_HAVE_D_TYPE + if(dirp->d_type != DT_UNKNOWN && dirp->d_type != DT_LNK) + isdir = (dirp->d_type == DT_DIR); +#endif /* _DIRENT_HAVE_D_TYPE */ + + /* + Was d_type DT_UNKNOWN, DT_LNK or nonexistent? + If so, try stat(). + */ + if(isdir == -1) { + struct stat statbuf{}; + if (stat((pathstring + "/" + node.name).c_str(), &statbuf)) + continue; + isdir = ((statbuf.st_mode & S_IFDIR) == S_IFDIR); } - } - closedir(dp); + node.dir = isdir; + listing.push_back(node); + } + closedir(dp); return listing; } -bool CreateDir(std::string path) +bool CreateDir(const std::string &path) { int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - if(r == 0) - { + if (r == 0) { return true; } - else - { - // If already exists, return true - if(errno == EEXIST) - return true; - return false; - } + + // If already exists, return true + if (errno == EEXIST) + return true; + return false; + } -bool PathExists(std::string path) +bool PathExists(const std::string &path) { - struct stat st; + struct stat st{}; return (stat(path.c_str(),&st) == 0); } -bool RecursiveDelete(std::string path) +bool IsPathAbsolute(const std::string &path) +{ + return path[0] == '/'; +} + +bool IsDir(const std::string &path) +{ + struct stat statbuf{}; + if(stat(path.c_str(), &statbuf)) + return false; // Actually error; but certainly not a directory + return ((statbuf.st_mode & S_IFDIR) == S_IFDIR); +} + +bool IsDirDelimiter(char c) +{ + return c == '/'; +} + +bool RecursiveDelete(const std::string &path) { /* Execute the 'rm' command directly, by fork() and execve() */ - - std::cerr<<"Removing \""<get("TMPFolder"); +#else + return DIR_DELIM "tmp"; +#endif +} + #endif -bool RecursiveDeleteContent(std::string path) +void GetRecursiveDirs(std::vector &dirs, const std::string &dir) +{ + static const std::set chars_to_ignore = { '_', '.' }; + if (dir.empty() || !IsDir(dir)) + return; + dirs.push_back(dir); + fs::GetRecursiveSubPaths(dir, dirs, false, chars_to_ignore); +} + +std::vector GetRecursiveDirs(const std::string &dir) { - std::cerr<<"Removing content of \""< result; + GetRecursiveDirs(result, dir); + return result; +} + +void GetRecursiveSubPaths(const std::string &path, + std::vector &dst, + bool list_files, + const std::set &ignore) +{ + std::vector content = GetDirListing(path); + for (const auto &n : content) { + std::string fullpath = path + DIR_DELIM + n.name; + if (ignore.count(n.name[0])) + continue; + if (list_files || n.dir) + dst.push_back(fullpath); + if (n.dir) + GetRecursiveSubPaths(fullpath, dst, list_files, ignore); + } +} + +bool DeletePaths(const std::vector &paths) +{ + bool success = true; + // Go backwards to succesfully delete the output of GetRecursiveSubPaths + for(int i=paths.size()-1; i>=0; i--){ + const std::string &path = paths[i]; + bool did = DeleteSingleFileOrEmptyDirectory(path); + if(!did){ + errorstream<<"Failed to delete "< list = GetDirListing(path); - for(unsigned int i=0; i tocreate; std::string basepath = path; while(!PathExists(basepath)) { tocreate.push_back(basepath); - pos = basepath.rfind('/'); - if(pos == std::string::npos) - return false; - basepath = basepath.substr(0,pos); + basepath = RemoveLastPathComponent(basepath); + if(basepath.empty()) + break; } for(int i=tocreate.size()-1;i>=0;i--) - CreateDir(tocreate[i]); + if(!CreateDir(tocreate[i])) + return false; + return true; +} + +bool CopyFileContents(const std::string &source, const std::string &target) +{ + FILE *sourcefile = fopen(source.c_str(), "rb"); + if(sourcefile == NULL){ + errorstream< 0){ + fwrite(readbuffer, 1, readbytes, targetfile); + } + if(feof(sourcefile) || ferror(sourcefile)){ + // flush destination file to catch write errors + // (e.g. disk full) + fflush(targetfile); + done = true; + } + if(ferror(targetfile)){ + errorstream< content = fs::GetDirListing(source); + + for (const auto &dln : content) { + std::string sourcechild = source + DIR_DELIM + dln.name; + std::string targetchild = target + DIR_DELIM + dln.name; + if(dln.dir){ + if(!fs::CopyDir(sourcechild, targetchild)){ + retval = false; + } + } + else { + if(!fs::CopyFileContents(sourcechild, targetchild)){ + retval = false; + } + } + } + return retval; + } + + return false; +} + +bool PathStartsWith(const std::string &path, const std::string &prefix) +{ + size_t pathsize = path.size(); + size_t pathpos = 0; + size_t prefixsize = prefix.size(); + size_t prefixpos = 0; + for(;;){ + bool delim1 = pathpos == pathsize + || IsDirDelimiter(path[pathpos]); + bool delim2 = prefixpos == prefixsize + || IsDirDelimiter(prefix[prefixpos]); + + if(delim1 != delim2) + return false; + + if(delim1){ + while(pathpos < pathsize && + IsDirDelimiter(path[pathpos])) + ++pathpos; + while(prefixpos < prefixsize && + IsDirDelimiter(prefix[prefixpos])) + ++prefixpos; + if(prefixpos == prefixsize) + return true; + if(pathpos == pathsize) + return false; + } + else{ + size_t len = 0; + do{ + char pathchar = path[pathpos+len]; + char prefixchar = prefix[prefixpos+len]; + if(FILESYS_CASE_INSENSITIVE){ + pathchar = tolower(pathchar); + prefixchar = tolower(prefixchar); + } + if(pathchar != prefixchar) + return false; + ++len; + } while(pathpos+len < pathsize + && !IsDirDelimiter(path[pathpos+len]) + && prefixpos+len < prefixsize + && !IsDirDelimiter( + prefix[prefixpos+len])); + pathpos += len; + prefixpos += len; + } + } +} + +std::string RemoveLastPathComponent(const std::string &path, + std::string *removed, int count) +{ + if(removed) + *removed = ""; + + size_t remaining = path.size(); + + for(int i = 0; i < count; ++i){ + // strip a dir delimiter + while(remaining != 0 && IsDirDelimiter(path[remaining-1])) + remaining--; + // strip a path component + size_t component_end = remaining; + while(remaining != 0 && !IsDirDelimiter(path[remaining-1])) + remaining--; + size_t component_start = remaining; + // strip a dir delimiter + while(remaining != 0 && IsDirDelimiter(path[remaining-1])) + remaining--; + if(removed){ + std::string component = path.substr(component_start, + component_end - component_start); + if(i) + *removed = component + DIR_DELIM + *removed; + else + *removed = component; + } + } + return path.substr(0, remaining); +} + +std::string RemoveRelativePathComponents(std::string path) +{ + size_t pos = path.size(); + size_t dotdot_count = 0; + while (pos != 0) { + size_t component_with_delim_end = pos; + // skip a dir delimiter + while (pos != 0 && IsDirDelimiter(path[pos-1])) + pos--; + // strip a path component + size_t component_end = pos; + while (pos != 0 && !IsDirDelimiter(path[pos-1])) + pos--; + size_t component_start = pos; + + std::string component = path.substr(component_start, + component_end - component_start); + bool remove_this_component = false; + if (component == ".") { + remove_this_component = true; + } else if (component == "..") { + remove_this_component = true; + dotdot_count += 1; + } else if (dotdot_count != 0) { + remove_this_component = true; + dotdot_count -= 1; + } + + if (remove_this_component) { + while (pos != 0 && IsDirDelimiter(path[pos-1])) + pos--; + if (component_start == 0) { + // We need to remove the delemiter too + path = path.substr(component_with_delim_end, std::string::npos); + } else { + path = path.substr(0, pos) + DIR_DELIM + + path.substr(component_with_delim_end, std::string::npos); + } + if (pos > 0) + pos++; + } + } + + if (dotdot_count > 0) + return ""; + + // remove trailing dir delimiters + pos = path.size(); + while (pos != 0 && IsDirDelimiter(path[pos-1])) + pos--; + return path.substr(0, pos); +} + +std::string AbsolutePath(const std::string &path) +{ +#ifdef _WIN32 + char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH); +#else + char *abs_path = realpath(path.c_str(), NULL); +#endif + if (!abs_path) return ""; + std::string abs_path_str(abs_path); + free(abs_path); + return abs_path_str; +} + +const char *GetFilenameFromPath(const char *path) +{ + const char *filename = strrchr(path, DIR_DELIM_CHAR); + return filename ? filename + 1 : path; +} + +bool safeWriteToFile(const std::string &path, const std::string &content) +{ + std::string tmp_file = path + ".~mt"; + + // Write to a tmp file + std::ofstream os(tmp_file.c_str(), std::ios::binary); + if (!os.good()) + return false; + os << content; + os.flush(); + os.close(); + if (os.fail()) { + // Remove the temporary file because writing it failed and it's useless. + remove(tmp_file.c_str()); + return false; + } + + bool rename_success = false; + + // Move the finished temporary file over the real file +#ifdef _WIN32 + // When creating the file, it can cause Windows Search indexer, virus scanners and other apps + // to query the file. This can make the move file call below fail. + // We retry up to 5 times, with a 1ms sleep between, before we consider the whole operation failed + int number_attempts = 0; + while (number_attempts < 5) { + rename_success = MoveFileEx(tmp_file.c_str(), path.c_str(), + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); + if (rename_success) + break; + sleep_ms(1); + ++number_attempts; + } +#else + // On POSIX compliant systems rename() is specified to be able to swap the + // file in place of the destination file, making this a truly error-proof + // transaction. + rename_success = rename(tmp_file.c_str(), path.c_str()) == 0; +#endif + if (!rename_success) { + warningstream << "Failed to write to file: " << path.c_str() << std::endl; + // Remove the temporary file because moving it over the target file + // failed. + remove(tmp_file.c_str()); + return false; + } + return true; } +bool Rename(const std::string &from, const std::string &to) +{ + return rename(from.c_str(), to.c_str()) == 0; +} + } // namespace fs