fileutils: move API to std::filesystem, cleanup
Change-Id: I5408d193bda6830395bd705371c86c949643ee74
diff --git a/src/fileutils.cpp b/src/fileutils.cpp
index c3aa059..6e60fd4 100644
--- a/src/fileutils.cpp
+++ b/src/fileutils.cpp
@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "fileutils.h"
+
#include <opendht/crypto.h>
#ifdef RING_UWP
@@ -32,29 +33,12 @@
#include <sys/types.h>
#include <sys/stat.h>
-
#ifndef _MSC_VER
#include <libgen.h>
#endif
-
-#ifdef _MSC_VER
-#include "windirent.h"
-#else
-#include <dirent.h>
-#endif
-
+#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
-#include <fcntl.h>
-#ifndef _WIN32
-#include <pwd.h>
-#else
-#include <shlobj.h>
-#define NAME_MAX 255
-#endif
-#if !defined __ANDROID__ && !defined _WIN32
-#include <wordexp.h>
-#endif
#include <sstream>
#include <fstream>
@@ -62,6 +46,7 @@
#include <stdexcept>
#include <limits>
#include <array>
+#include <filesystem>
#include <cstdlib>
#include <cstring>
@@ -69,218 +54,67 @@
#include <cstddef>
#include <ciso646>
-extern "C" {
-#include <pj/ctype.h>
-#include <pjlib-util/md5.h>
-}
-#include <filesystem>
-
-#define PIDFILE ".ring.pid"
#define ERASE_BLOCK 4096
namespace dhtnet {
namespace fileutils {
-// returns true if directory exists
+// returns true if directory exists or was created
bool
-check_dir(const char* path, [[maybe_unused]] mode_t dirmode, mode_t parentmode)
+check_dir(const std::filesystem::path& path, mode_t dirmode, mode_t parentmode)
{
- DIR* dir = opendir(path);
-
- if (!dir) { // doesn't exist
- if (not recursive_mkdir(path, parentmode)) {
- perror(path);
- return false;
- }
-#ifndef _WIN32
- if (chmod(path, dirmode) < 0) {
- //JAMI_ERR("fileutils::check_dir(): chmod() failed on '%s', %s", path, strerror(errno));
- return false;
- }
-#endif
- } else
- closedir(dir);
- return true;
-}
-
-std::string
-expand_path(const std::string& path)
-{
-#if defined __ANDROID__ || defined _MSC_VER || defined WIN32 || defined __APPLE__
- //JAMI_ERR("Path expansion not implemented, returning original");
- return path;
-#else
-
- std::string result;
-
- wordexp_t p;
- int ret = wordexp(path.c_str(), &p, 0);
-
- switch (ret) {
- case WRDE_BADCHAR:
- /*JAMI_ERR("Illegal occurrence of newline or one of |, &, ;, <, >, "
- "(, ), {, }.");*/
- return result;
- case WRDE_BADVAL:
- //JAMI_ERR("An undefined shell variable was referenced");
- return result;
- case WRDE_CMDSUB:
- //JAMI_ERR("Command substitution occurred");
- return result;
- case WRDE_SYNTAX:
- //JAMI_ERR("Shell syntax error");
- return result;
- case WRDE_NOSPACE:
- //JAMI_ERR("Out of memory.");
- // This is the only error where we must call wordfree
- break;
- default:
- if (p.we_wordc > 0)
- result = std::string(p.we_wordv[0]);
- break;
+ if (std::filesystem::exists(path))
+ return true;
+ if (path.has_parent_path())
+ check_dir(path.parent_path(), parentmode, parentmode);
+ if (std::filesystem::create_directory(path)) {
+ std::filesystem::permissions(path, (std::filesystem::perms)dirmode);
+ return true;
}
-
- wordfree(&p);
-
- return result;
-#endif
+ return false;
}
std::mutex&
-getFileLock(const std::string& path)
+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];
+ return fileLocks[path.string()];
}
bool
-isFile(const std::string& path, bool resolveSymlink)
+isFile(const std::filesystem::path& path, bool resolveSymlink)
{
- if (path.empty())
- return false;
-#ifdef _WIN32
- if (resolveSymlink) {
- struct _stat64i32 s;
- if (_wstat(dhtnet::to_wstring(path).c_str(), &s) == 0)
- return S_ISREG(s.st_mode);
- } else {
- DWORD attr = GetFileAttributes(dhtnet::to_wstring(path).c_str());
- if ((attr != INVALID_FILE_ATTRIBUTES) && !(attr & FILE_ATTRIBUTE_DIRECTORY)
- && !(attr & FILE_ATTRIBUTE_REPARSE_POINT))
- return true;
- }
-#else
- if (resolveSymlink) {
- struct stat s;
- if (stat(path.c_str(), &s) == 0)
- return S_ISREG(s.st_mode);
- } else {
- struct stat s;
- if (lstat(path.c_str(), &s) == 0)
- return S_ISREG(s.st_mode);
- }
-#endif
-
- return false;
+ auto status = resolveSymlink ? std::filesystem::status(path) : std::filesystem::symlink_status(path);
+ return std::filesystem::is_regular_file(status);
}
bool
-isDirectory(const std::string& path)
+isDirectory(const std::filesystem::path& path)
{
- struct stat s;
- if (stat(path.c_str(), &s) == 0)
- return s.st_mode & S_IFDIR;
- return false;
+ return std::filesystem::is_directory(path);
}
bool
-isDirectoryWritable(const std::string& directory)
+hasHardLink(const std::filesystem::path& path)
{
- return accessFile(directory, W_OK) == 0;
+ return std::filesystem::hard_link_count(path) > 1;
}
bool
-hasHardLink(const std::string& path)
+isSymLink(const std::filesystem::path& path)
{
-#ifndef _WIN32
- struct stat s;
- if (lstat(path.c_str(), &s) == 0)
- return s.st_nlink > 1;
-#endif
- return false;
+ return std::filesystem::is_symlink(path);
}
-bool
-isSymLink(const std::string& path)
+template <typename TP>
+std::chrono::system_clock::time_point to_sysclock(TP tp)
{
-#ifndef _WIN32
- struct stat s;
- if (lstat(path.c_str(), &s) == 0)
- return S_ISLNK(s.st_mode);
-#elif !defined(_MSC_VER)
- DWORD attr = GetFileAttributes(dhtnet::to_wstring(path).c_str());
- if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
- return true;
-#endif
- return false;
-}
-
-std::chrono::system_clock::time_point
-writeTime(const std::string& path)
-{
-#ifndef _WIN32
- struct stat s;
- auto ret = stat(path.c_str(), &s);
- if (ret)
- throw std::runtime_error("Can't check write time for: " + path);
- return std::chrono::system_clock::from_time_t(s.st_mtime);
-#else
-#if RING_UWP
- _CREATEFILE2_EXTENDED_PARAMETERS ext_params = {0};
- ext_params.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
- ext_params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
- ext_params.dwFileFlags = FILE_FLAG_NO_BUFFERING;
- ext_params.dwSecurityQosFlags = SECURITY_ANONYMOUS;
- ext_params.lpSecurityAttributes = nullptr;
- ext_params.hTemplateFile = nullptr;
- HANDLE h = CreateFile2(dhtnet::to_wstring(path).c_str(),
- GENERIC_READ,
- FILE_SHARE_READ,
- OPEN_EXISTING,
- &ext_params);
-#elif _WIN32
- HANDLE h = CreateFileW(dhtnet::to_wstring(path).c_str(),
- GENERIC_READ,
- FILE_SHARE_READ,
- nullptr,
- OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL,
- nullptr);
-#endif
- if (h == INVALID_HANDLE_VALUE)
- throw std::runtime_error("Can't open: " + path);
- FILETIME lastWriteTime;
- if (!GetFileTime(h, nullptr, nullptr, &lastWriteTime))
- throw std::runtime_error("Can't check write time for: " + path);
- CloseHandle(h);
- SYSTEMTIME sTime;
- if (!FileTimeToSystemTime(&lastWriteTime, &sTime))
- throw std::runtime_error("Can't check write time for: " + path);
- struct tm tm
- {};
- tm.tm_year = sTime.wYear - 1900;
- tm.tm_mon = sTime.wMonth - 1;
- tm.tm_mday = sTime.wDay;
- tm.tm_hour = sTime.wHour;
- tm.tm_min = sTime.wMinute;
- tm.tm_sec = sTime.wSecond;
- tm.tm_isdst = -1;
- return std::chrono::system_clock::from_time_t(mktime(&tm));
-#endif
+ using namespace std::chrono;
+ return time_point_cast<system_clock::duration>(tp - TP::clock::now() + system_clock::now());
}
bool
@@ -314,85 +148,26 @@
createSymlink(linkFile, target);
}
-std::string_view
-getFileExtension(std::string_view filename)
-{
- std::string_view result;
- auto sep = filename.find_last_of('.');
- if (sep != std::string_view::npos && sep != filename.size() - 1)
- result = filename.substr(sep + 1);
- if (result.size() >= 8)
- return {};
- return result;
-}
-
-bool
-isPathRelative(const std::string& path)
-{
-#ifndef _WIN32
- return not path.empty() and not(path[0] == '/');
-#else
- return not path.empty() and path.find(":") == std::string::npos;
-#endif
-}
-
-std::string
-getCleanPath(const std::string& base, const std::string& path)
-{
- if (base.empty() or path.size() < base.size())
- return path;
- auto base_sep = base + DIR_SEPARATOR_STR;
- if (path.compare(0, base_sep.size(), base_sep) == 0)
- return path.substr(base_sep.size());
- else
- return path;
-}
-
-std::string
-getFullPath(const std::string& base, const std::string& path)
-{
- bool isRelative {not base.empty() and isPathRelative(path)};
- return isRelative ? base + DIR_SEPARATOR_STR + path : path;
-}
-
std::vector<uint8_t>
-loadFile(const std::string& path, const std::string& default_dir)
+loadFile(const std::filesystem::path& path)
{
std::vector<uint8_t> buffer;
- std::ifstream file = ifstream(getFullPath(default_dir, path), std::ios::binary);
+ std::ifstream file = ifstream(path, std::ios::binary);
if (!file)
- throw std::runtime_error("Can't read file: " + path);
+ 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);
+ 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);
- return buffer;
-}
-
-std::string
-loadTextFile(const std::string& path, const std::string& default_dir)
-{
- std::string buffer;
- std::ifstream file = ifstream(getFullPath(default_dir, path));
- if (!file)
- throw std::runtime_error("Can't read file: " + path);
- 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);
- 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);
+ throw std::runtime_error("Can't load file: " + path.string());
return buffer;
}
void
-saveFile(const std::string& path, const uint8_t* data, size_t data_size, [[maybe_unused]] mode_t mode)
+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()) {
@@ -400,188 +175,32 @@
return;
}
file.write((char*) data, data_size);
-#ifndef _WIN32
- if (chmod(path.c_str(), mode) < 0)
- /*JAMI_WARN("fileutils::saveFile(): chmod() failed on '%s', %s",
- path.c_str(),
- strerror(errno))*/;
-#endif
-}
-
-std::vector<uint8_t>
-loadCacheFile(const std::string& path, std::chrono::system_clock::duration maxAge)
-{
- // writeTime throws exception if file doesn't exist
- auto duration = std::chrono::system_clock::now() - writeTime(path);
- if (duration > maxAge)
- throw std::runtime_error("file too old");
-
- //JAMI_DBG("Loading cache file '%.*s'", (int) path.size(), path.c_str());
- return loadFile(path);
-}
-
-std::string
-loadCacheTextFile(const std::string& path, std::chrono::system_clock::duration maxAge)
-{
- // writeTime throws exception if file doesn't exist
- auto duration = std::chrono::system_clock::now() - writeTime(path);
- if (duration > maxAge)
- throw std::runtime_error("file too old");
-
- //JAMI_DBG("Loading cache file '%.*s'", (int) path.size(), path.c_str());
- return loadTextFile(path);
-}
-
-static size_t
-dirent_buf_size([[maybe_unused]] DIR* dirp)
-{
- long name_max;
-#if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD) && defined(_PC_NAME_MAX)
- name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
- if (name_max == -1)
-#if defined(NAME_MAX)
- name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
-#else
- return (size_t) (-1);
-#endif
-#else
-#if defined(NAME_MAX)
- name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
-#else
-#error "buffer size for readdir_r cannot be determined"
-#endif
-#endif
- size_t name_end = (size_t) offsetof(struct dirent, d_name) + name_max + 1;
- return name_end > sizeof(struct dirent) ? name_end : sizeof(struct dirent);
+ file.close();
+ std::filesystem::permissions(path, (std::filesystem::perms)mode);
}
std::vector<std::string>
-readDirectory(const std::string& dir)
+readDirectory(const std::filesystem::path& dir)
{
- DIR* dp = opendir(dir.c_str());
- if (!dp)
- return {};
-
- size_t size = dirent_buf_size(dp);
- if (size == (size_t) (-1))
- return {};
- std::vector<uint8_t> buf(size);
- dirent* entry;
-
std::vector<std::string> files;
-#ifndef _WIN32
- while (!readdir_r(dp, reinterpret_cast<dirent*>(buf.data()), &entry) && entry) {
-#else
- while ((entry = readdir(dp)) != nullptr) {
-#endif
- std::string fname {entry->d_name};
+ std::error_code ec;
+ for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) {
+ std::string fname {entry.path().filename().string()};
if (fname == "." || fname == "..")
continue;
files.emplace_back(std::move(fname));
}
- closedir(dp);
return files;
-} // namespace fileutils
-
-/*
-std::vector<uint8_t>
-readArchive(const std::string& path, const std::string& pwd)
-{
- JAMI_DBG("Reading archive from %s", path.c_str());
-
- auto isUnencryptedGzip = [](const std::vector<uint8_t>& data) {
- // NOTE: some webserver modify gzip files and this can end with a gunzip in a gunzip
- // file. So, to make the readArchive more robust, we can support this case by detecting
- // gzip header via 1f8b 08
- // We don't need to support more than 2 level, else somebody may be able to send
- // gunzip in loops and abuse.
- return data.size() > 3 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08;
- };
-
- auto decompress = [](std::vector<uint8_t>& data) {
- try {
- data = archiver::decompress(data);
- } catch (const std::exception& e) {
- JAMI_ERR("Error decrypting archive: %s", e.what());
- throw e;
- }
- };
-
- std::vector<uint8_t> data;
- // Read file
- try {
- data = loadFile(path);
- } catch (const std::exception& e) {
- JAMI_ERR("Error loading archive: %s", e.what());
- throw e;
- }
-
- if (isUnencryptedGzip(data)) {
- if (!pwd.empty())
- JAMI_WARN() << "A gunzip in a gunzip is detected. A webserver may have a bad config";
-
- decompress(data);
- }
-
- if (!pwd.empty()) {
- // Decrypt
- try {
- data = dht::crypto::aesDecrypt(data, pwd);
- } catch (const std::exception& e) {
- JAMI_ERR("Error decrypting archive: %s", e.what());
- throw e;
- }
- decompress(data);
- } else if (isUnencryptedGzip(data)) {
- JAMI_WARN() << "A gunzip in a gunzip is detected. A webserver may have a bad config";
- decompress(data);
- }
- return data;
}
-void
-writeArchive(const std::string& archive_str, const std::string& path, const std::string& password)
-{
- JAMI_DBG("Writing archive to %s", path.c_str());
-
- if (not password.empty()) {
- // Encrypt using provided password
- std::vector<uint8_t> data = dht::crypto::aesEncrypt(archiver::compress(archive_str),
- password);
- // Write
- try {
- saveFile(path, data);
- } catch (const std::runtime_error& ex) {
- JAMI_ERR("Export failed: %s", ex.what());
- return;
- }
- } else {
- JAMI_WARN("Unsecured archiving (no password)");
- archiver::compressGzip(archive_str, path);
- }
-}*/
-
bool
-recursive_mkdir(const std::string& path, mode_t mode)
+recursive_mkdir(const std::filesystem::path& path, mode_t mode)
{
-#ifndef _WIN32
- if (mkdir(path.data(), mode) != 0) {
-#else
- if (_wmkdir(dhtnet::to_wstring(path.data()).c_str()) != 0) {
-#endif
- if (errno == ENOENT) {
- recursive_mkdir(path.substr(0, path.find_last_of(DIR_SEPARATOR_CH)), mode);
-#ifndef _WIN32
- if (mkdir(path.data(), mode) != 0) {
-#else
- if (_wmkdir(dhtnet::to_wstring(path.data()).c_str()) != 0) {
-#endif
- //JAMI_ERR("Could not create directory.");
- return false;
- }
- }
- } // namespace dhtnet
- return true;
+ 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
@@ -700,7 +319,7 @@
}
int
-remove(const std::string& path, bool erase)
+remove(const std::filesystem::path& path, bool erase)
{
if (erase and isFile(path, false) and !hasHardLink(path))
eraseFile(path, true);
@@ -715,22 +334,25 @@
}
int
-removeAll(const std::string& path, bool erase)
+removeAll(const std::filesystem::path& path, bool erase)
{
if (path.empty())
return -1;
- if (isDirectory(path) and !isSymLink(path)) {
- auto dir = path;
- if (dir.back() != DIR_SEPARATOR_CH)
- dir += DIR_SEPARATOR_CH;
- for (auto& entry : fileutils::readDirectory(dir))
- removeAll(dir + entry, erase);
+
+ auto status = std::filesystem::status(path);
+ if (std::filesystem::is_directory(status) and not std::filesystem::is_symlink(status)) {
+ for (const auto& entry: std::filesystem::directory_iterator(path)) {
+ auto fname = entry.path().filename().string();
+ if (fname == "." || fname == "..")
+ continue;
+ removeAll(entry.path(), erase);
+ }
}
return remove(path, erase);
}
void
-openStream(std::ifstream& file, const std::string& path, std::ios_base::openmode mode)
+openStream(std::ifstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
{
#ifdef _WIN32
file.open(dhtnet::to_wstring(path), mode);
@@ -740,7 +362,7 @@
}
void
-openStream(std::ofstream& file, const std::string& path, std::ios_base::openmode mode)
+openStream(std::ofstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
{
#ifdef _WIN32
file.open(dhtnet::to_wstring(path), mode);
@@ -750,7 +372,7 @@
}
std::ifstream
-ifstream(const std::string& path, std::ios_base::openmode mode)
+ifstream(const std::filesystem::path& path, std::ios_base::openmode mode)
{
#ifdef _WIN32
return std::ifstream(dhtnet::to_wstring(path), mode);
@@ -760,7 +382,7 @@
}
std::ofstream
-ofstream(const std::string& path, std::ios_base::openmode mode)
+ofstream(const std::filesystem::path& path, std::ios_base::openmode mode)
{
#ifdef _WIN32
return std::ofstream(dhtnet::to_wstring(path), mode);
@@ -769,23 +391,8 @@
#endif
}
-int64_t
-size(const std::string& path)
-{
- int64_t size = 0;
- try {
- std::ifstream file;
- openStream(file, path, std::ios::binary | std::ios::in);
- file.seekg(0, std::ios_base::end);
- size = file.tellg();
- file.close();
- } catch (...) {
- }
- return size;
-}
-
int
-accessFile(const std::string& file, int mode)
+accessFile(const std::filesystem::path& file, int mode)
{
#ifdef _WIN32
return _waccess(dhtnet::to_wstring(file).c_str(), mode);
@@ -794,20 +401,5 @@
#endif
}
-uint64_t
-lastWriteTime(const std::string& p)
-{
-#if USE_STD_FILESYSTEM
- return std::chrono::duration_cast<std::chrono::milliseconds>(
- std::filesystem::last_write_time(std::filesystem::path(p)).time_since_epoch())
- .count();
-#else
- struct stat result;
- if (stat(p.c_str(), &result) == 0)
- return result.st_mtime;
- return 0;
-#endif
-}
-
} // namespace fileutils
} // namespace dhtnet