blob: f700ddb424f34f3ee695653221b43c529e7166a7 [file] [log] [blame]
/*
* 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(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(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
std::error_code ec;
std::filesystem::remove(path, ec);
return ec.value();
}
int
removeAll(const std::filesystem::path& path, bool erase)
{
try {
std::error_code ec;
if (not erase) {
std::filesystem::remove_all(path, ec);
return ec.value();
}
if (path.empty())
return -1;
auto status = std::filesystem::status(path, ec);
if (!ec && std::filesystem::is_directory(status) and not std::filesystem::is_symlink(status)) {
for (const auto& entry: std::filesystem::directory_iterator(path, ec)) {
removeAll(entry.path(), erase);
}
}
return remove(path, erase);
} catch (const std::exception& e) {
//JAMI_ERR("Error while removing %s: %s", path.c_str(), e.what());
return -1;
}
}
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