Skip to content
Snippets Groups Projects
Select Git revision
  • master default protected
1 result

fileutils.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    fileutils.cpp 9.97 KiB
    /*
     *  Copyright (C) 2004-2023 Savoir-faire Linux Inc.
     *
     *  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 3 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.
     *
     *  You should have received a copy of the GNU General Public License
     *  along with this program. If not, see <https://www.gnu.org/licenses/>.
     */
    #include "fileutils.h"
    
    #include <opendht/crypto.h>
    
    #ifdef RING_UWP
    #include <io.h> // for access and close
    #endif
    
    #ifdef __APPLE__
    #include <TargetConditionals.h>
    #endif
    
    #ifdef _WIN32
    #include <windows.h>
    #include "string_utils.h"
    #endif
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #ifndef _MSC_VER
    #include <libgen.h>
    #endif
    #include <fcntl.h>
    #include <signal.h>
    #include <unistd.h>
    
    #include <sstream>
    #include <fstream>
    #include <iostream>
    #include <stdexcept>
    #include <limits>
    #include <array>
    #include <filesystem>
    
    #include <cstdlib>
    #include <cstring>
    #include <cerrno>
    #include <cstddef>
    #include <ciso646>
    
    
    #define ERASE_BLOCK 4096
    
    namespace dhtnet {
    namespace fileutils {
    
    // returns true if directory exists or was created
    bool
    check_dir(const std::filesystem::path& path, mode_t dirmode, mode_t parentmode)
    {
        fmt::print("check_dir: {}\n", path.string());
        if (std::filesystem::exists(path))
            return true;
        if (path.has_parent_path())
            check_dir(path.parent_path(), parentmode, parentmode);
        std::error_code ec;
        if (std::filesystem::create_directory(path, ec)) {
            std::filesystem::permissions(path, (std::filesystem::perms)dirmode);
            return true;
        }
        return false;
    }
    
    std::mutex&
    getFileLock(const std::filesystem::path& path)
    {
        static std::mutex fileLockLock {};
        static std::map<std::string, std::mutex> fileLocks {};
    
        std::lock_guard<std::mutex> l(fileLockLock);
        return fileLocks[path.string()];
    }
    
    bool
    isFile(const std::filesystem::path& path, bool resolveSymlink)
    {
        auto status = resolveSymlink ? std::filesystem::status(path) : std::filesystem::symlink_status(path);
        return std::filesystem::is_regular_file(status);
    }
    
    bool
    isDirectory(const std::filesystem::path& path)
    {
        return std::filesystem::is_directory(path);
    }
    
    bool
    hasHardLink(const std::filesystem::path& path)
    {
        return std::filesystem::hard_link_count(path) > 1;
    }
    
    bool
    isSymLink(const std::filesystem::path& path)
    {
        return std::filesystem::is_symlink(path);
    }
    
    template <typename TP>
    std::chrono::system_clock::time_point to_sysclock(TP tp)
    {
        using namespace std::chrono;
        return time_point_cast<system_clock::duration>(tp - TP::clock::now() + system_clock::now());
    }
    
    bool
    createSymlink(const std::string& linkFile, const std::string& target)
    {
        try {
            std::filesystem::create_symlink(target, linkFile);
        } catch (const std::exception& e) {
            //JAMI_ERR("Couldn't create soft link: %s", e.what());
            return false;
        }
        return true;
    }
    
    bool
    createHardlink(const std::string& linkFile, const std::string& target)
    {
        try {
            std::filesystem::create_hard_link(target, linkFile);
        } catch (const std::exception& e) {
            //JAMI_ERR("Couldn't create hard link: %s", e.what());
            return false;
        }
        return true;
    }
    
    void
    createFileLink(const std::string& linkFile, const std::string& target, bool hard)
    {
        if (not hard or not createHardlink(linkFile, target))
            createSymlink(linkFile, target);
    }
    
    std::vector<uint8_t>
    loadFile(const std::filesystem::path& path)
    {
        std::vector<uint8_t> buffer;
        std::ifstream file = ifstream(path, std::ios::binary);
        if (!file)
            throw std::runtime_error("Can't read file: " + path.string());
        file.seekg(0, std::ios::end);
        auto size = file.tellg();
        if (size > std::numeric_limits<unsigned>::max())
            throw std::runtime_error("File is too big: " + path.string());
        buffer.resize(size);
        file.seekg(0, std::ios::beg);
        if (!file.read((char*) buffer.data(), size))
            throw std::runtime_error("Can't load file: " + path.string());
        return buffer;
    }
    
    void
    saveFile(const std::filesystem::path& path, const uint8_t* data, size_t data_size, mode_t mode)
    {
        std::ofstream file = fileutils::ofstream(path, std::ios::trunc | std::ios::binary);
        if (!file.is_open()) {
            //JAMI_ERR("Could not write data to %s", path.c_str());
            return;
        }
        file.write((char*) data, data_size);
        file.close();
        std::filesystem::permissions(path, (std::filesystem::perms)mode);
    }
    
    std::vector<std::string>
    readDirectory(const std::filesystem::path& dir)
    {
        std::vector<std::string> files;
        std::error_code ec;
        for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) {
            files.emplace_back(entry.path().filename().string());
        }
        return files;
    }
    
    bool
    recursive_mkdir(const std::filesystem::path& path, mode_t mode)
    {
        std::error_code ec;
        std::filesystem::create_directories(path, ec);
        if (!ec)
            std::filesystem::permissions(path, (std::filesystem::perms)mode, ec);
        return !ec;
    }
    
    #ifdef _WIN32
    bool
    eraseFile_win32(const std::string& path, bool dosync)
    {
        HANDLE h
            = CreateFileA(path.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
        if (h == INVALID_HANDLE_VALUE) {
            // JAMI_WARN("Can not open file %s for erasing.", path.c_str());
            return false;
        }
    
        LARGE_INTEGER size;
        if (!GetFileSizeEx(h, &size)) {
            // JAMI_WARN("Can not erase file %s: GetFileSizeEx() failed.", path.c_str());
            CloseHandle(h);
            return false;
        }
        if (size.QuadPart == 0) {
            CloseHandle(h);
            return false;
        }
    
        uint64_t size_blocks = size.QuadPart / ERASE_BLOCK;
        if (size.QuadPart % ERASE_BLOCK)
            size_blocks++;
    
        char* buffer;
        try {
            buffer = new char[ERASE_BLOCK];
        } catch (std::bad_alloc& ba) {
            // JAMI_WARN("Can not allocate buffer for erasing %s.", path.c_str());
            CloseHandle(h);
            return false;
        }
        memset(buffer, 0x00, ERASE_BLOCK);
    
        OVERLAPPED ovlp;
        if (size.QuadPart < (1024 - 42)) { // a small file can be stored in the MFT record
            ovlp.Offset = 0;
            ovlp.OffsetHigh = 0;
            WriteFile(h, buffer, (DWORD) size.QuadPart, 0, &ovlp);
            FlushFileBuffers(h);
        }
        for (uint64_t i = 0; i < size_blocks; i++) {
            uint64_t offset = i * ERASE_BLOCK;
            ovlp.Offset = offset & 0x00000000FFFFFFFF;
            ovlp.OffsetHigh = offset >> 32;
            WriteFile(h, buffer, ERASE_BLOCK, 0, &ovlp);
        }
    
        delete[] buffer;
    
        if (dosync)
            FlushFileBuffers(h);
    
        CloseHandle(h);
        return true;
    }
    
    #else
    
    bool
    eraseFile_posix(const std::string& path, bool dosync)
    {
        struct stat st;
        if (stat(path.c_str(), &st) == -1) {
            //JAMI_WARN("Can not erase file %s: fstat() failed.", path.c_str());
            return false;
        }
        // Remove read-only flag if possible
        chmod(path.c_str(), st.st_mode | (S_IWGRP+S_IWUSR) );
    
        int fd = open(path.c_str(), O_WRONLY);
        if (fd == -1) {
            //JAMI_WARN("Can not open file %s for erasing.", path.c_str());
            return false;
        }
    
        if (st.st_size == 0) {
            close(fd);
            return false;
        }
    
        lseek(fd, 0, SEEK_SET);
    
        std::array<char, ERASE_BLOCK> buffer;
        buffer.fill(0);
        decltype(st.st_size) written(0);
        while (written < st.st_size) {
            auto ret = write(fd, buffer.data(), buffer.size());
            if (ret < 0) {
                //JAMI_WARNING("Error while overriding file with zeros.");
                break;
            } else
                written += ret;
        }
    
        if (dosync)
            fsync(fd);
    
        close(fd);
        return written >= st.st_size;
    }
    #endif
    
    bool
    eraseFile(const std::string& path, bool dosync)
    {
    #ifdef _WIN32
        return eraseFile_win32(path, dosync);
    #else
        return eraseFile_posix(path, dosync);
    #endif
    }
    
    int
    remove(const std::filesystem::path& path, bool erase)
    {
        if (erase and isFile(path, false) and !hasHardLink(path))
            eraseFile(path.string(), true);
    
    #ifdef _WIN32
        // use Win32 api since std::remove will not unlink directory in use
        if (isDirectory(path))
            return !RemoveDirectory(dhtnet::to_wstring(path.string()).c_str());
    #endif
    
        return std::remove(path.string().c_str());
    }
    
    int
    removeAll(const std::filesystem::path& path, bool erase)
    {
        if (path.empty())
            return -1;
    
        auto status = std::filesystem::status(path);
        if (std::filesystem::is_directory(status) and not std::filesystem::is_symlink(status)) {
            std::error_code ec;
            for (const auto& entry: std::filesystem::directory_iterator(path, ec)) {
                removeAll(entry.path(), erase);
            }
        }
        return remove(path, erase);
    }
    
    void
    openStream(std::ifstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
    {
    #ifdef _WIN32
        file.open(dhtnet::to_wstring(path.string()), mode);
    #else
        file.open(path, mode);
    #endif
    }
    
    void
    openStream(std::ofstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
    {
    #ifdef _WIN32
        file.open(dhtnet::to_wstring(path.string()), mode);
    #else
        file.open(path, mode);
    #endif
    }
    
    std::ifstream
    ifstream(const std::filesystem::path& path, std::ios_base::openmode mode)
    {
    #ifdef _WIN32
        return std::ifstream(dhtnet::to_wstring(path.string()), mode);
    #else
        return std::ifstream(path, mode);
    #endif
    }
    
    std::ofstream
    ofstream(const std::filesystem::path& path, std::ios_base::openmode mode)
    {
    #ifdef _WIN32
        return std::ofstream(dhtnet::to_wstring(path.string()), mode);
    #else
        return std::ofstream(path, mode);
    #endif
    }
    
    int
    accessFile(const std::filesystem::path& file, int mode)
    {
    #ifdef _WIN32
        return _waccess(dhtnet::to_wstring(file.string()).c_str(), mode);
    #else
        return access(file.c_str(), mode);
    #endif
    }
    
    } // namespace fileutils
    } // namespace dhtnet