/* * 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) { 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