]> git.lizzy.rs Git - dragonfireclient.git/blobdiff - src/filesys.cpp
Improve LBMManager::applyLBMs() code
[dragonfireclient.git] / src / filesys.cpp
index b4c52ab793b8bb1bb5331285767b159e7bd917c9..ea00def6aa733cda2792c28ea95d36d4eff13334 100644 (file)
@@ -20,22 +20,34 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "filesys.h"
 #include "util/string.h"
 #include <iostream>
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
 #include <fstream>
 #include "log.h"
 #include "config.h"
 #include "porting.h"
+#ifndef SERVER
+#include "irr_ptr.h"
+#endif
 
 namespace fs
 {
 
-#ifdef _WIN32 // WINDOWS
+#ifdef _WIN32
+
+/***********
+ * Windows *
+ ***********/
 
+#ifndef _WIN32_WINNT
 #define _WIN32_WINNT 0x0501
+#endif
 #include <windows.h>
 #include <shlwapi.h>
+#include <io.h>
+#include <direct.h>
 
 std::vector<DirListNode> GetDirListing(const std::string &pathstring)
 {
@@ -122,46 +134,33 @@ bool IsDirDelimiter(char c)
 
 bool RecursiveDelete(const std::string &path)
 {
-       infostream<<"Recursively deleting \""<<path<<"\""<<std::endl;
-
-       DWORD attr = GetFileAttributes(path.c_str());
-       bool is_directory = (attr != INVALID_FILE_ATTRIBUTES &&
-                       (attr & FILE_ATTRIBUTE_DIRECTORY));
-       if(!is_directory)
-       {
-               infostream<<"RecursiveDelete: Deleting file "<<path<<std::endl;
-               //bool did = DeleteFile(path.c_str());
-               bool did = true;
-               if(!did){
-                       errorstream<<"RecursiveDelete: Failed to delete file "
-                                       <<path<<std::endl;
+       infostream << "Recursively deleting \"" << path << "\"" << std::endl;
+       if (!IsDir(path)) {
+               infostream << "RecursiveDelete: Deleting file  " << path << std::endl;
+               if (!DeleteFile(path.c_str())) {
+                       errorstream << "RecursiveDelete: Failed to delete file "
+                                       << path << std::endl;
                        return false;
                }
+               return true;
        }
-       else
-       {
-               infostream<<"RecursiveDelete: Deleting content of directory "
-                               <<path<<std::endl;
-               std::vector<DirListNode> content = GetDirListing(path);
-               for(size_t i=0; i<content.size(); i++){
-                       const DirListNode &n = content[i];
-                       std::string fullpath = path + DIR_DELIM + n.name;
-                       bool did = RecursiveDelete(fullpath);
-                       if(!did){
-                               errorstream<<"RecursiveDelete: Failed to recurse to "
-                                               <<fullpath<<std::endl;
-                               return false;
-                       }
-               }
-               infostream<<"RecursiveDelete: Deleting directory "<<path<<std::endl;
-               //bool did = RemoveDirectory(path.c_str();
-               bool did = true;
-               if(!did){
-                       errorstream<<"Failed to recursively delete directory "
-                                       <<path<<std::endl;
+       infostream << "RecursiveDelete: Deleting content of directory "
+                       << path << std::endl;
+       std::vector<DirListNode> 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;
 }
 
@@ -189,16 +188,34 @@ std::string TempPath()
                errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
                return "";
        }
-       std::vector<char> buf(bufsize);
+       std::string buf;
+       buf.resize(bufsize);
        DWORD len = GetTempPath(bufsize, &buf[0]);
        if(len == 0 || len > bufsize){
                errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl;
                return "";
        }
-       return std::string(buf.begin(), buf.begin() + len);
+       buf.resize(len);
+       return buf;
+}
+
+std::string CreateTempFile()
+{
+       std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
+       _mktemp_s(&path[0], path.size() + 1); // modifies path
+       HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr,
+               CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+       if (file == INVALID_HANDLE_VALUE)
+               return "";
+       CloseHandle(file);
+       return path;
 }
 
-#else // POSIX
+#else
+
+/*********
+ * POSIX *
+ *********/
 
 #include <sys/types.h>
 #include <dirent.h>
@@ -246,7 +263,7 @@ std::vector<DirListNode> GetDirListing(const std::string &pathstring)
                        If so, try stat().
                */
                if(isdir == -1) {
-                       struct stat statbuf;
+                       struct stat statbuf{};
                        if (stat((pathstring + "/" + node.name).c_str(), &statbuf))
                                continue;
                        isdir = ((statbuf.st_mode & S_IFDIR) == S_IFDIR);
@@ -262,22 +279,20 @@ std::vector<DirListNode> GetDirListing(const std::string &pathstring)
 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(const std::string &path)
 {
-       struct stat st;
+       struct stat st{};
        return (stat(path.c_str(),&st) == 0);
 }
 
@@ -288,7 +303,7 @@ bool IsPathAbsolute(const std::string &path)
 
 bool IsDir(const std::string &path)
 {
-       struct stat statbuf;
+       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);
@@ -307,27 +322,26 @@ bool RecursiveDelete(const std::string &path)
 
        infostream<<"Removing \""<<path<<"\""<<std::endl;
 
-       //return false;
-
        pid_t child_pid = fork();
 
        if(child_pid == 0)
        {
                // Child
-               char argv_data[3][10000];
-               strcpy(argv_data[0], "/bin/rm");
-               strcpy(argv_data[1], "-rf");
-               strncpy(argv_data[2], path.c_str(), 10000);
-               char *argv[4];
-               argv[0] = argv_data[0];
-               argv[1] = argv_data[1];
-               argv[2] = argv_data[2];
-               argv[3] = NULL;
+               const char *argv[4] = {
+#ifdef __ANDROID__
+                       "/system/bin/rm",
+#else
+                       "/bin/rm",
+#endif
+                       "-rf",
+                       path.c_str(),
+                       NULL
+               };
 
                verbosestream<<"Executing '"<<argv[0]<<"' '"<<argv[1]<<"' '"
                                <<argv[2]<<"'"<<std::endl;
 
-               execv(argv[0], argv);
+               execv(argv[0], const_cast<char**>(argv));
 
                // Execv shouldn't return. Failed.
                _exit(1);
@@ -339,7 +353,6 @@ bool RecursiveDelete(const std::string &path)
                pid_t tpid;
                do{
                        tpid = wait(&child_status);
-                       //if(tpid != child_pid) process_terminated(tpid);
                }while(tpid != child_pid);
                return (child_status == 0);
        }
@@ -347,19 +360,19 @@ bool RecursiveDelete(const std::string &path)
 
 bool DeleteSingleFileOrEmptyDirectory(const std::string &path)
 {
-       if(IsDir(path)){
+       if (IsDir(path)) {
                bool did = (rmdir(path.c_str()) == 0);
-               if(!did)
-                       errorstream<<"rmdir errno: "<<errno<<": "<<strerror(errno)
-                                       <<std::endl;
-               return did;
-       } else {
-               bool did = (unlink(path.c_str()) == 0);
-               if(!did)
-                       errorstream<<"unlink errno: "<<errno<<": "<<strerror(errno)
-                                       <<std::endl;
+               if (!did)
+                       errorstream << "rmdir errno: " << errno << ": " << strerror(errno)
+                                       << std::endl;
                return did;
        }
+
+       bool did = (unlink(path.c_str()) == 0);
+       if (!did)
+               errorstream << "unlink errno: " << errno << ": " << strerror(errno)
+                               << std::endl;
+       return did;
 }
 
 std::string TempPath()
@@ -373,56 +386,74 @@ std::string TempPath()
                compatible with lua's os.tmpname which under the default
                configuration hardcodes mkstemp("/tmp/lua_XXXXXX").
        */
+
 #ifdef __ANDROID__
-       return DIR_DELIM "sdcard" DIR_DELIM PROJECT_NAME DIR_DELIM "tmp";
+       return porting::path_cache;
 #else
        return DIR_DELIM "tmp";
 #endif
 }
 
+std::string CreateTempFile()
+{
+       std::string path = TempPath() + DIR_DELIM "MT_XXXXXX";
+       int fd = mkstemp(&path[0]); // modifies path
+       if (fd == -1)
+               return "";
+       close(fd);
+       return path;
+}
+
 #endif
 
-void GetRecursiveSubPaths(const std::string &path, std::vector<std::string> &dst)
+/****************************
+ * portable implementations *
+ ****************************/
+
+void GetRecursiveDirs(std::vector<std::string> &dirs, const std::string &dir)
 {
-       std::vector<DirListNode> content = GetDirListing(path);
-       for(unsigned int  i=0; i<content.size(); i++){
-               const DirListNode &n = content[i];
-               std::string fullpath = path + DIR_DELIM + n.name;
-               dst.push_back(fullpath);
-               if (n.dir) {
-                       GetRecursiveSubPaths(fullpath, dst);
-               }
-       }
+       static const std::set<char> chars_to_ignore = { '_', '.' };
+       if (dir.empty() || !IsDir(dir))
+               return;
+       dirs.push_back(dir);
+       fs::GetRecursiveSubPaths(dir, dirs, false, chars_to_ignore);
 }
 
-bool DeletePaths(const std::vector<std::string> &paths)
+std::vector<std::string> GetRecursiveDirs(const std::string &dir)
 {
-       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 "<<path<<std::endl;
-                       success = false;
-               }
+       std::vector<std::string> result;
+       GetRecursiveDirs(result, dir);
+       return result;
+}
+
+void GetRecursiveSubPaths(const std::string &path,
+                 std::vector<std::string> &dst,
+                 bool list_files,
+                 const std::set<char> &ignore)
+{
+       std::vector<DirListNode> 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);
        }
-       return success;
 }
 
 bool RecursiveDeleteContent(const std::string &path)
 {
        infostream<<"Removing content of \""<<path<<"\""<<std::endl;
        std::vector<DirListNode> list = GetDirListing(path);
-       for(unsigned int i=0; i<list.size(); i++)
-       {
-               if(trim(list[i].name) == "." || trim(list[i].name) == "..")
+       for (const DirListNode &dln : list) {
+               if(trim(dln.name) == "." || trim(dln.name) == "..")
                        continue;
-               std::string childpath = path + DIR_DELIM + list[i].name;
+               std::string childpath = path + DIR_DELIM + dln.name;
                bool r = RecursiveDelete(childpath);
-               if(r == false)
-               {
-                       errorstream<<"Removing \""<<childpath<<"\" failed"<<std::endl;
+               if(!r) {
+                       errorstream << "Removing \"" << childpath << "\" failed" << std::endl;
                        return false;
                }
        }
@@ -510,10 +541,10 @@ bool CopyDir(const std::string &source, const std::string &target)
                bool retval = true;
                std::vector<DirListNode> content = fs::GetDirListing(source);
 
-               for(unsigned int i=0; i < content.size(); i++){
-                       std::string sourcechild = source + DIR_DELIM + content[i].name;
-                       std::string targetchild = target + DIR_DELIM + content[i].name;
-                       if(content[i].dir){
+               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;
                                }
@@ -526,9 +557,32 @@ bool CopyDir(const std::string &source, const std::string &target)
                }
                return retval;
        }
-       else {
-               return false;
+
+       return false;
+}
+
+bool MoveDir(const std::string &source, const std::string &target)
+{
+       infostream << "Moving \"" << source << "\" to \"" << target << "\"" << std::endl;
+
+       // If target exists as empty folder delete, otherwise error
+       if (fs::PathExists(target)) {
+               if (rmdir(target.c_str()) != 0) {
+                       errorstream << "MoveDir: target \"" << target
+                               << "\" exists as file or non-empty folder" << std::endl;
+                       return false;
+               }
        }
+
+       // Try renaming first which is instant
+       if (fs::Rename(source, target))
+               return true;
+
+       infostream << "MoveDir: rename not possible, will copy instead" << std::endl;
+       bool retval = fs::CopyDir(source, target);
+       if (retval)
+               retval &= fs::RecursiveDelete(source);
+       return retval;
 }
 
 bool PathStartsWith(const std::string &path, const std::string &prefix)
@@ -617,48 +671,51 @@ std::string RemoveRelativePathComponents(std::string path)
 {
        size_t pos = path.size();
        size_t dotdot_count = 0;
-       while(pos != 0){
+       while (pos != 0) {
                size_t component_with_delim_end = pos;
                // skip a dir delimiter
-               while(pos != 0 && IsDirDelimiter(path[pos-1]))
+               while (pos != 0 && IsDirDelimiter(path[pos-1]))
                        pos--;
                // strip a path component
                size_t component_end = pos;
-               while(pos != 0 && !IsDirDelimiter(path[pos-1]))
+               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 == "."){
+               if (component == ".") {
                        remove_this_component = true;
-               }
-               else if(component == ".."){
+               } else if (component == "..") {
                        remove_this_component = true;
                        dotdot_count += 1;
-               }
-               else if(dotdot_count != 0){
+               } else if (dotdot_count != 0) {
                        remove_this_component = true;
                        dotdot_count -= 1;
                }
 
-               if(remove_this_component){
-                       while(pos != 0 && IsDirDelimiter(path[pos-1]))
+               if (remove_this_component) {
+                       while (pos != 0 && IsDirDelimiter(path[pos-1]))
                                pos--;
-                       path = path.substr(0, pos) + DIR_DELIM +
-                               path.substr(component_with_delim_end,
-                                               std::string::npos);
-                       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)
+       if (dotdot_count > 0)
                return "";
 
        // remove trailing dir delimiters
        pos = path.size();
-       while(pos != 0 && IsDirDelimiter(path[pos-1]))
+       while (pos != 0 && IsDirDelimiter(path[pos-1]))
                pos--;
        return path.substr(0, pos);
 }
@@ -679,6 +736,12 @@ std::string AbsolutePath(const std::string &path)
 const char *GetFilenameFromPath(const char *path)
 {
        const char *filename = strrchr(path, DIR_DELIM_CHAR);
+       // Consistent with IsDirDelimiter this function handles '/' too
+       if (DIR_DELIM_CHAR != '/') {
+               const char *tmp = strrchr(path, '/');
+               if (tmp && tmp > filename)
+                       filename = tmp;
+       }
        return filename ? filename + 1 : path;
 }
 
@@ -732,6 +795,82 @@ bool safeWriteToFile(const std::string &path, const std::string &content)
        return true;
 }
 
+#ifndef SERVER
+bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string &destination)
+{
+       // Be careful here not to touch the global file hierarchy in Irrlicht
+       // since this function needs to be thread-safe!
+
+       io::IArchiveLoader *zip_loader = nullptr;
+       for (u32 i = 0; i < fs->getArchiveLoaderCount(); i++) {
+               if (fs->getArchiveLoader(i)->isALoadableFileFormat(io::EFAT_ZIP)) {
+                       zip_loader = fs->getArchiveLoader(i);
+                       break;
+               }
+       }
+       if (!zip_loader) {
+               warningstream << "fs::extractZipFile(): Irrlicht said it doesn't support ZIPs." << std::endl;
+               return false;
+       }
+
+       irr_ptr<io::IFileArchive> opened_zip(zip_loader->createArchive(filename, false, false));
+       const io::IFileList* files_in_zip = opened_zip->getFileList();
+
+       for (u32 i = 0; i < files_in_zip->getFileCount(); i++) {
+               std::string fullpath = destination + DIR_DELIM;
+               fullpath += files_in_zip->getFullFileName(i).c_str();
+               std::string fullpath_dir = fs::RemoveLastPathComponent(fullpath);
+
+               if (files_in_zip->isDirectory(i))
+                       continue; // ignore, we create dirs as necessary
+
+               if (!fs::PathExists(fullpath_dir) && !fs::CreateAllDirs(fullpath_dir))
+                       return false;
+
+               irr_ptr<io::IReadFile> toread(opened_zip->createAndOpenFile(i));
+
+               std::ofstream os(fullpath.c_str(), std::ios::binary);
+               if (!os.good())
+                       return false;
+
+               char buffer[4096];
+               long total_read = 0;
+
+               while (total_read < toread->getSize()) {
+                       long bytes_read = toread->read(buffer, sizeof(buffer));
+                       bool error = true;
+                       if (bytes_read != 0) {
+                               os.write(buffer, bytes_read);
+                               error = os.fail();
+                       }
+                       if (error) {
+                               os.close();
+                               remove(fullpath.c_str());
+                               return false;
+                       }
+                       total_read += bytes_read;
+               }
+       }
+
+       return true;
+}
+#endif
+
+bool ReadFile(const std::string &path, std::string &out)
+{
+       std::ifstream is(path, std::ios::binary | std::ios::ate);
+       if (!is.good()) {
+               return false;
+       }
+
+       auto size = is.tellg();
+       out.resize(size);
+       is.seekg(0);
+       is.read(&out[0], size);
+
+       return !is.fail();
+}
+
 bool Rename(const std::string &from, const std::string &to)
 {
        return rename(from.c_str(), to.c_str()) == 0;