LCOV - code coverage report
Current view: top level - src - fileutils.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 172 297 57.9 %
Date: 2024-12-21 08:56:24 Functions: 45 74 60.8 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #ifdef HAVE_CONFIG_H
      19             : #include "config.h"
      20             : #endif
      21             : 
      22             : #include "fileutils.h"
      23             : #include "logger.h"
      24             : #include "archiver.h"
      25             : #include "compiler_intrinsics.h"
      26             : #include "base64.h"
      27             : 
      28             : #include <opendht/crypto.h>
      29             : 
      30             : #ifdef __APPLE__
      31             : #include <TargetConditionals.h>
      32             : #endif
      33             : 
      34             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
      35             : #include "client/ring_signal.h"
      36             : #endif
      37             : 
      38             : #ifdef _WIN32
      39             : #include <windows.h>
      40             : #include "string_utils.h"
      41             : #endif
      42             : 
      43             : #include <sys/types.h>
      44             : #include <sys/stat.h>
      45             : 
      46             : #ifndef _MSC_VER
      47             : #include <libgen.h>
      48             : #endif
      49             : 
      50             : #ifdef _MSC_VER
      51             : #include "windirent.h"
      52             : #else
      53             : #include <dirent.h>
      54             : #endif
      55             : 
      56             : #include <signal.h>
      57             : #include <unistd.h>
      58             : #include <fcntl.h>
      59             : #ifndef _WIN32
      60             : #include <pwd.h>
      61             : #else
      62             : #include <shlobj.h>
      63             : #define NAME_MAX 255
      64             : #endif
      65             : #if !defined __ANDROID__ && !defined _WIN32
      66             : #include <wordexp.h>
      67             : #endif
      68             : 
      69             : #include <nettle/sha3.h>
      70             : 
      71             : #include <sstream>
      72             : #include <fstream>
      73             : #include <iostream>
      74             : #include <stdexcept>
      75             : #include <limits>
      76             : #include <array>
      77             : 
      78             : #include <cstdlib>
      79             : #include <cstring>
      80             : #include <cerrno>
      81             : #include <cstddef>
      82             : #include <ciso646>
      83             : 
      84             : #include <pj/ctype.h>
      85             : #include <pjlib-util/md5.h>
      86             : 
      87             : #ifndef _MSC_VER
      88             : #define PROTECTED_GETENV(str) \
      89             :     ({ \
      90             :         char* envvar_ = getenv((str)); \
      91             :         envvar_ ? envvar_ : ""; \
      92             :     })
      93             : 
      94             : #define XDG_DATA_HOME   (PROTECTED_GETENV("XDG_DATA_HOME"))
      95             : #define XDG_CONFIG_HOME (PROTECTED_GETENV("XDG_CONFIG_HOME"))
      96             : #define XDG_CACHE_HOME  (PROTECTED_GETENV("XDG_CACHE_HOME"))
      97             : #else
      98             : const wchar_t*
      99             : winGetEnv(const wchar_t* name)
     100             : {
     101             :     const DWORD buffSize = 65535;
     102             :     static wchar_t buffer[buffSize];
     103             :     if (GetEnvironmentVariable(name, buffer, buffSize)) {
     104             :         return buffer;
     105             :     } else {
     106             :         return L"";
     107             :     }
     108             : }
     109             : 
     110             : #define PROTECTED_GETENV(str) winGetEnv(str)
     111             : 
     112             : #define JAMI_DATA_HOME   PROTECTED_GETENV(L"JAMI_DATA_HOME")
     113             : #define JAMI_CONFIG_HOME PROTECTED_GETENV(L"JAMI_CONFIG_HOME")
     114             : #define JAMI_CACHE_HOME  PROTECTED_GETENV(L"JAMI_CACHE_HOME")
     115             : #endif
     116             : 
     117             : #define PIDFILE     ".ring.pid"
     118             : #define ERASE_BLOCK 4096
     119             : 
     120             : namespace jami {
     121             : namespace fileutils {
     122             : 
     123             : static std::filesystem::path resource_dir_path;
     124             : 
     125             : void
     126           0 : set_resource_dir_path(const std::filesystem::path&  resourceDirPath)
     127             : {
     128           0 :     resource_dir_path = resourceDirPath;
     129           0 : }
     130             : 
     131             : const std::filesystem::path&
     132         823 : get_resource_dir_path()
     133             : {
     134         823 :     static const std::filesystem::path jami_default_data_dir(JAMI_DATADIR);
     135         823 :     return resource_dir_path.empty() ? jami_default_data_dir : resource_dir_path;
     136             : }
     137             : 
     138             : std::string
     139           5 : expand_path(const std::string& path)
     140             : {
     141             : #if defined __ANDROID__ || defined _MSC_VER || defined WIN32 || defined __APPLE__
     142             :     JAMI_ERR("Path expansion not implemented, returning original");
     143             :     return path;
     144             : #else
     145             : 
     146           5 :     std::string result;
     147             : 
     148             :     wordexp_t p;
     149           5 :     int ret = wordexp(path.c_str(), &p, 0);
     150             : 
     151           5 :     switch (ret) {
     152           0 :     case WRDE_BADCHAR:
     153           0 :         JAMI_ERR("Illegal occurrence of newline or one of |, &, ;, <, >, "
     154             :                  "(, ), {, }.");
     155           0 :         return result;
     156           0 :     case WRDE_BADVAL:
     157           0 :         JAMI_ERR("An undefined shell variable was referenced");
     158           0 :         return result;
     159           0 :     case WRDE_CMDSUB:
     160           0 :         JAMI_ERR("Command substitution occurred");
     161           0 :         return result;
     162           0 :     case WRDE_SYNTAX:
     163           0 :         JAMI_ERR("Shell syntax error");
     164           0 :         return result;
     165           0 :     case WRDE_NOSPACE:
     166           0 :         JAMI_ERR("Out of memory.");
     167             :         // This is the only error where we must call wordfree
     168           0 :         break;
     169           5 :     default:
     170           5 :         if (p.we_wordc > 0)
     171           5 :             result = std::string(p.we_wordv[0]);
     172           5 :         break;
     173             :     }
     174             : 
     175           5 :     wordfree(&p);
     176             : 
     177           5 :     return result;
     178             : #endif
     179           0 : }
     180             : 
     181             : bool
     182           7 : isDirectoryWritable(const std::string& directory)
     183             : {
     184           7 :     return accessFile(directory, W_OK) == 0;
     185             : }
     186             : 
     187             : bool
     188          12 : createSymlink(const std::filesystem::path& linkFile, const std::filesystem::path& target)
     189             : {
     190          12 :     std::error_code ec;
     191          12 :     std::filesystem::create_symlink(target, linkFile, ec);
     192          12 :     if (ec) {
     193           0 :         JAMI_WARNING("Unable to create soft link from {} to {}: {}", linkFile, target, ec.message());
     194           0 :         return false;
     195             :     } else {
     196          36 :         JAMI_LOG("Created soft link from {} to {}", linkFile, target);
     197             :     }
     198          12 :     return true;
     199             : }
     200             : 
     201             : bool
     202          11 : createHardlink(const std::filesystem::path& linkFile, const std::filesystem::path& target)
     203             : {
     204          11 :     std::error_code ec;
     205          11 :     std::filesystem::create_hard_link(target, linkFile, ec);
     206          11 :     if (ec) {
     207           0 :         JAMI_WARNING("Unable to create hard link from {} to {}: {}", linkFile, target, ec.message());
     208           0 :         return false;
     209             :     } else {
     210          33 :         JAMI_LOG("Created hard link from {} to {}", linkFile, target);
     211             :     }
     212          11 :     return true;
     213             : }
     214             : 
     215             : bool
     216          23 : createFileLink(const std::filesystem::path& linkFile, const std::filesystem::path& target, bool hard)
     217             : {
     218          23 :     if (linkFile == target)
     219           0 :         return true;
     220          23 :     std::error_code ec;
     221          23 :     if (std::filesystem::exists(linkFile, ec)) {
     222           0 :         if (std::filesystem::is_symlink(linkFile, ec) && std::filesystem::read_symlink(linkFile, ec) == target)
     223           0 :             return true;
     224           0 :         std::filesystem::remove(linkFile, ec);
     225             :     }
     226          23 :     if (not hard or not createHardlink(linkFile, target))
     227          12 :         return createSymlink(linkFile, target);
     228          11 :     return true;
     229             : }
     230             : 
     231             : std::string_view
     232          61 : getFileExtension(std::string_view filename)
     233             : {
     234          61 :     std::string_view result;
     235          61 :     auto sep = filename.find_last_of('.');
     236          61 :     if (sep != std::string_view::npos && sep != filename.size() - 1)
     237           0 :         result = filename.substr(sep + 1);
     238          61 :     if (result.size() >= 8)
     239           0 :         return {};
     240          61 :     return result;
     241             : }
     242             : 
     243             : bool
     244        9404 : isPathRelative(const std::filesystem::path& path)
     245             : {
     246        9404 :     return not path.empty() and path.is_relative();
     247             : }
     248             : 
     249             : std::string
     250       10319 : getCleanPath(const std::string& base, const std::string& path)
     251             : {
     252       10319 :     if (base.empty() or path.size() < base.size())
     253       10312 :         return path;
     254           7 :     auto base_sep = base + DIR_SEPARATOR_STR;
     255           7 :     if (path.compare(0, base_sep.size(), base_sep) == 0)
     256           7 :         return path.substr(base_sep.size());
     257             :     else
     258           0 :         return path;
     259           7 : }
     260             : 
     261             : std::filesystem::path
     262       15016 : getFullPath(const std::filesystem::path& base, const std::filesystem::path& path)
     263             : {
     264       15016 :     bool isRelative {not base.empty() and isPathRelative(path)};
     265       15016 :     return isRelative ? base / path : path;
     266             : }
     267             : 
     268             : std::vector<uint8_t>
     269        7255 : loadFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
     270             : {
     271       11458 :     return dhtnet::fileutils::loadFile(getFullPath(default_dir, path));
     272             : }
     273             : 
     274             : std::string
     275           8 : loadTextFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
     276             : {
     277           8 :     std::string buffer;
     278           8 :     std::ifstream file(getFullPath(default_dir, path));
     279           8 :     if (!file)
     280           3 :         throw std::runtime_error("Unable to read file: " + path.string());
     281           5 :     file.seekg(0, std::ios::end);
     282           5 :     auto size = file.tellg();
     283           5 :     if (size > std::numeric_limits<unsigned>::max())
     284           0 :         throw std::runtime_error("File is too big: " + path.string());
     285           5 :     buffer.resize(size);
     286           5 :     file.seekg(0, std::ios::beg);
     287           5 :     if (!file.read((char*) buffer.data(), size))
     288           0 :         throw std::runtime_error("Unable to load file: " + path.string());
     289          10 :     return buffer;
     290          11 : }
     291             : 
     292             : void
     293        1589 : saveFile(const std::filesystem::path& path,
     294             :          const uint8_t* data,
     295             :          size_t data_size,
     296             :          mode_t UNUSED mode)
     297             : {
     298        1589 :     std::ofstream file(path, std::ios::trunc | std::ios::binary);
     299        1589 :     if (!file.is_open()) {
     300           0 :         JAMI_ERROR("Unable to write data to {}", path);
     301           0 :         return;
     302             :     }
     303        1589 :     file.write((char*) data, data_size);
     304             : #ifndef _WIN32
     305        1589 :     file.close();
     306        1589 :     if (chmod(path.c_str(), mode) < 0)
     307           0 :         JAMI_WARNING("fileutils::saveFile(): chmod() failed on {}, {}", path, strerror(errno));
     308             : #endif
     309        1589 : }
     310             : 
     311             : std::vector<uint8_t>
     312           0 : loadCacheFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
     313             : {
     314             :     // last_write_time throws exception if file doesn't exist
     315           0 :     auto writeTime = std::filesystem::last_write_time(path);
     316           0 :     if (decltype(writeTime)::clock::now() - writeTime > maxAge)
     317           0 :         throw std::runtime_error("file too old");
     318             : 
     319           0 :     JAMI_LOG("Loading cache file '{}'", path);
     320           0 :     return dhtnet::fileutils::loadFile(path);
     321             : }
     322             : 
     323             : std::string
     324           0 : loadCacheTextFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
     325             : {
     326             :     // last_write_time throws exception if file doesn't exist
     327           0 :     auto writeTime = std::filesystem::last_write_time(path);
     328           0 :     if (decltype(writeTime)::clock::now() - writeTime > maxAge)
     329           0 :         throw std::runtime_error("file too old");
     330             : 
     331           0 :     JAMI_LOG("Loading cache file '{}'", path);
     332           0 :     return loadTextFile(path);
     333             : }
     334             : 
     335             : ArchiveStorageData
     336          96 : readArchive(const std::filesystem::path& path, std::string_view scheme, const std::string& pwd)
     337             : {
     338         288 :     JAMI_LOG("Reading archive from {} with scheme '{}'", path, scheme);
     339             : 
     340         184 :     auto isUnencryptedGzip = [](const std::vector<uint8_t>& data) {
     341             :         // NOTE: some webserver modify gzip files and this can end with a gunzip in a gunzip
     342             :         // file. So, to make the readArchive more robust, we can support this case by detecting
     343             :         // gzip header via 1f8b 08
     344             :         // We don't need to support more than 2 level, else somebody may be able to send
     345             :         // gunzip in loops and abuse.
     346         184 :         return data.size() > 3 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08;
     347             :     };
     348             : 
     349          96 :     auto decompress = [](std::vector<uint8_t>& data) {
     350             :         try {
     351          96 :             data = archiver::decompress(data);
     352           0 :         } catch (const std::exception& e) {
     353           0 :             JAMI_ERROR("Error decrypting archive: {}", e.what());
     354           0 :             throw e;
     355           0 :         }
     356          96 :     };
     357             : 
     358          96 :     ArchiveStorageData ret;
     359             : 
     360             :     // Read file
     361             :     try {
     362          96 :         ret.data = dhtnet::fileutils::loadFile(path);
     363           0 :     } catch (const std::exception& e) {
     364           0 :         JAMI_ERR("Error loading archive: %s", e.what());
     365           0 :         throw e;
     366           0 :     }
     367             : 
     368          96 :     if (isUnencryptedGzip(ret.data)) {
     369          89 :         if (!pwd.empty())
     370           6 :             JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
     371          89 :         decompress(ret.data);
     372             :     }
     373             : 
     374          96 :     if (!pwd.empty()) {
     375             :         // Decrypt
     376           8 :         if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
     377             :             try {
     378           0 :                 ret.salt = dht::crypto::aesGetSalt(ret.data);
     379           0 :                 ret.data = dht::crypto::aesDecrypt(dht::crypto::aesGetEncrypted(ret.data), base64::decode(pwd));
     380           0 :             } catch (const std::exception& e) {
     381           0 :                 JAMI_ERROR("Error decrypting archive: {}", e.what());
     382           0 :                 throw e;
     383           0 :             }
     384           8 :         } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD) {
     385             :             try {
     386           8 :                 ret.salt = dht::crypto::aesGetSalt(ret.data);
     387           8 :                 ret.data = dht::crypto::aesDecrypt(ret.data, pwd);
     388           2 :             } catch (const std::exception& e) {
     389           6 :                 JAMI_ERROR("Error decrypting archive: {}", e.what());
     390           2 :                 throw e;
     391           2 :             }
     392             :         }
     393           6 :         decompress(ret.data);
     394          88 :     } else if (isUnencryptedGzip(ret.data)) {
     395           3 :         JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
     396           1 :         decompress(ret.data);
     397             :     }
     398         188 :     return ret;
     399           2 : }
     400             : 
     401             : void
     402         827 : writeArchive(const std::string& archive_str,
     403             :              const std::filesystem::path& path,
     404             :              std::string_view scheme,
     405             :              const std::string& password,
     406             :              const std::vector<uint8_t>& password_salt)
     407             : {
     408        2481 :     JAMI_LOG("Writing archive to {}", path);
     409             : 
     410         827 :     if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
     411             :         // Encrypt using provided key
     412             :         try {
     413           0 :             auto key = base64::decode(password);
     414           0 :             auto newArchive = dht::crypto::aesEncrypt(archiver::compress(archive_str), key);
     415           0 :             saveFile(path, dht::crypto::aesBuildEncrypted(newArchive, password_salt));
     416           0 :         } catch (const std::runtime_error& ex) {
     417           0 :             JAMI_ERROR("Export failed: {}", ex.what());
     418           0 :             return;
     419           0 :         }
     420         827 :     } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD and not password.empty()) {
     421             :         // Encrypt using provided password
     422             :         try {
     423          16 :             saveFile(path, dht::crypto::aesEncrypt(archiver::compress(archive_str), password, password_salt));
     424           0 :         } catch (const std::runtime_error& ex) {
     425           0 :             JAMI_ERROR("Export failed: {}", ex.what());
     426           0 :             return;
     427           0 :         }
     428             :     } else {
     429        2433 :         JAMI_WARNING("Unsecured archiving (no password)");
     430         811 :         archiver::compressGzip(archive_str, path.string());
     431             :     }
     432             : }
     433             : 
     434             : std::filesystem::path
     435          66 : get_cache_dir(const char* pkg)
     436             : {
     437             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
     438             :     std::vector<std::string> paths;
     439             :     paths.reserve(1);
     440             :     emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("cache", &paths);
     441             :     if (not paths.empty())
     442             :         return paths[0];
     443             :     return {};
     444             : #elif defined(__APPLE__)
     445             :     return get_home_dir() / "Library" / "Caches" / pkg;
     446             : #else
     447             : #ifdef _WIN32
     448             :     const std::wstring cache_home(JAMI_CACHE_HOME);
     449             :     if (not cache_home.empty())
     450             :         return jami::to_string(cache_home);
     451             : #else
     452          66 :     const std::string cache_home(XDG_CACHE_HOME);
     453          66 :     if (not cache_home.empty())
     454           0 :         return cache_home;
     455             : #endif
     456         132 :     return get_home_dir() / ".cache" / pkg;
     457             : #endif
     458          66 : }
     459             : 
     460             : const std::filesystem::path&
     461        3311 : get_cache_dir()
     462             : {
     463        3311 :     static const std::filesystem::path cache_dir = get_cache_dir(PACKAGE);
     464        3311 :     return cache_dir;
     465             : }
     466             : 
     467             : std::filesystem::path
     468          34 : get_home_dir_impl()
     469             : {
     470             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
     471             :     std::vector<std::string> paths;
     472             :     paths.reserve(1);
     473             :     emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("files", &paths);
     474             :     if (not paths.empty())
     475             :         return paths[0];
     476             :     return {};
     477             : #elif defined _WIN32
     478             :     TCHAR path[MAX_PATH];
     479             :     if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_PROFILE, nullptr, 0, path))) {
     480             :         return jami::to_string(path);
     481             :     }
     482             :     return {};
     483             : #else
     484             : 
     485             :     // 1) try getting user's home directory from the environment
     486          34 :     std::string home(PROTECTED_GETENV("HOME"));
     487          34 :     if (not home.empty())
     488          34 :         return home;
     489             : 
     490             :     // 2) try getting it from getpwuid_r (i.e. /etc/passwd)
     491           0 :     const long max = sysconf(_SC_GETPW_R_SIZE_MAX);
     492           0 :     if (max != -1) {
     493           0 :         char buf[max];
     494             :         struct passwd pwbuf, *pw;
     495           0 :         if (getpwuid_r(getuid(), &pwbuf, buf, sizeof(buf), &pw) == 0 and pw != NULL)
     496           0 :             return pw->pw_dir;
     497           0 :     }
     498             : 
     499           0 :     return {};
     500             : #endif
     501          34 : }
     502             : 
     503             : const std::filesystem::path&
     504         201 : get_home_dir()
     505             : {
     506         201 :     static const std::filesystem::path home_dir = get_home_dir_impl();
     507         201 :     return home_dir;
     508             : }
     509             : 
     510             : std::filesystem::path
     511          66 : get_data_dir(const char* pkg)
     512             : {
     513             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
     514             :     std::vector<std::string> paths;
     515             :     paths.reserve(1);
     516             :     emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("files", &paths);
     517             :     if (not paths.empty())
     518             :         return paths[0];
     519             :     return {};
     520             : #elif defined(__APPLE__)
     521             :     return get_home_dir() / "Library" / "Application Support" / pkg;
     522             : #elif defined(_WIN32)
     523             :     std::wstring data_home(JAMI_DATA_HOME);
     524             :     if (not data_home.empty())
     525             :         return std::filesystem::path(data_home) / pkg;
     526             : 
     527             :     if (!strcmp(pkg, "ring")) {
     528             :         return get_home_dir() / ".local" / "share" / pkg;
     529             :     } else {
     530             :         return get_home_dir() / "AppData" / "Local" / pkg;
     531             :     }
     532             : #else
     533          66 :     std::string_view data_home(XDG_DATA_HOME);
     534          66 :     if (not data_home.empty())
     535           0 :         return std::filesystem::path(data_home) / pkg;
     536             :     // "If $XDG_DATA_HOME is either not set or empty, a default equal to
     537             :     // $HOME/.local/share should be used."
     538         132 :     return get_home_dir() / ".local" / "share" / pkg;
     539             : #endif
     540             : }
     541             : 
     542             : const std::filesystem::path&
     543       78167 : get_data_dir()
     544             : {
     545       78167 :     static const std::filesystem::path data_dir = get_data_dir(PACKAGE);
     546       78167 :     return data_dir;
     547             : }
     548             : 
     549             : std::filesystem::path
     550          67 : get_config_dir(const char* pkg)
     551             : {
     552          67 :     std::filesystem::path configdir;
     553             : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
     554             :     std::vector<std::string> paths;
     555             :     emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("config", &paths);
     556             :     if (not paths.empty())
     557             :         configdir = std::filesystem::path(paths[0]);
     558             : #elif defined(__APPLE__)
     559             :     configdir = fileutils::get_home_dir() / "Library" / "Application Support" / pkg;
     560             : #elif defined(_WIN32)
     561             :     std::wstring xdg_env(JAMI_CONFIG_HOME);
     562             :     if (not xdg_env.empty()) {
     563             :         configdir = std::filesystem::path(xdg_env) / pkg;
     564             :     } else if (!strcmp(pkg, "ring")) {
     565             :         configdir = fileutils::get_home_dir() / ".config" / pkg;
     566             :     } else {
     567             :         configdir = fileutils::get_home_dir() / "AppData" / "Local" / pkg;
     568             :     }
     569             : #else
     570          67 :     std::string xdg_env(XDG_CONFIG_HOME);
     571          67 :     if (not xdg_env.empty())
     572           0 :         configdir = std::filesystem::path(xdg_env) / pkg;
     573             :     else
     574          67 :         configdir = fileutils::get_home_dir() / ".config" / pkg;
     575             : #endif
     576          67 :     if (!dhtnet::fileutils::recursive_mkdir(configdir, 0700)) {
     577             :         // If directory creation failed
     578           0 :         if (errno != EEXIST)
     579           0 :             JAMI_DBG("Unable to create directory: %s!", configdir.c_str());
     580             :     }
     581         134 :     return configdir;
     582          67 : }
     583             : 
     584             : const std::filesystem::path&
     585         284 : get_config_dir()
     586             : {
     587         284 :     static const std::filesystem::path config_dir = get_config_dir(PACKAGE);
     588         284 :     return config_dir;
     589             : }
     590             : 
     591             : #ifdef _WIN32
     592             : bool
     593             : eraseFile_win32(const std::string& path, bool dosync)
     594             : {
     595             :     // Note: from
     596             :     // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-deletefilea#remarks To
     597             :     // delete a read-only file, first you must remove the read-only attribute.
     598             :     SetFileAttributesA(path.c_str(), GetFileAttributesA(path.c_str()) & ~FILE_ATTRIBUTE_READONLY);
     599             :     HANDLE h
     600             :         = CreateFileA(path.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
     601             :     if (h == INVALID_HANDLE_VALUE) {
     602             :         JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
     603             :         return false;
     604             :     }
     605             : 
     606             :     LARGE_INTEGER size;
     607             :     if (!GetFileSizeEx(h, &size)) {
     608             :         JAMI_WARN("Unable to erase file %s: GetFileSizeEx() failed.", path.c_str());
     609             :         CloseHandle(h);
     610             :         return false;
     611             :     }
     612             :     if (size.QuadPart == 0) {
     613             :         CloseHandle(h);
     614             :         return false;
     615             :     }
     616             : 
     617             :     uint64_t size_blocks = size.QuadPart / ERASE_BLOCK;
     618             :     if (size.QuadPart % ERASE_BLOCK)
     619             :         size_blocks++;
     620             : 
     621             :     char* buffer;
     622             :     try {
     623             :         buffer = new char[ERASE_BLOCK];
     624             :     } catch (std::bad_alloc& ba) {
     625             :         JAMI_WARN("Unable to allocate buffer for erasing %s.", path.c_str());
     626             :         CloseHandle(h);
     627             :         return false;
     628             :     }
     629             :     memset(buffer, 0x00, ERASE_BLOCK);
     630             : 
     631             :     OVERLAPPED ovlp;
     632             :     if (size.QuadPart < (1024 - 42)) { // a small file can be stored in the MFT record
     633             :         ovlp.Offset = 0;
     634             :         ovlp.OffsetHigh = 0;
     635             :         WriteFile(h, buffer, (DWORD) size.QuadPart, 0, &ovlp);
     636             :         FlushFileBuffers(h);
     637             :     }
     638             :     for (uint64_t i = 0; i < size_blocks; i++) {
     639             :         uint64_t offset = i * ERASE_BLOCK;
     640             :         ovlp.Offset = offset & 0x00000000FFFFFFFF;
     641             :         ovlp.OffsetHigh = offset >> 32;
     642             :         WriteFile(h, buffer, ERASE_BLOCK, 0, &ovlp);
     643             :     }
     644             : 
     645             :     delete[] buffer;
     646             : 
     647             :     if (dosync)
     648             :         FlushFileBuffers(h);
     649             : 
     650             :     CloseHandle(h);
     651             :     return true;
     652             : }
     653             : 
     654             : #else
     655             : 
     656             : bool
     657           0 : eraseFile_posix(const std::string& path, bool dosync)
     658             : {
     659             :     struct stat st;
     660           0 :     if (stat(path.c_str(), &st) == -1) {
     661           0 :         JAMI_WARN("Unable to erase file %s: fstat() failed.", path.c_str());
     662           0 :         return false;
     663             :     }
     664             :     // Remove read-only flag if possible
     665           0 :     chmod(path.c_str(), st.st_mode | (S_IWGRP + S_IWUSR));
     666             : 
     667           0 :     int fd = open(path.c_str(), O_WRONLY);
     668           0 :     if (fd == -1) {
     669           0 :         JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
     670           0 :         return false;
     671             :     }
     672             : 
     673           0 :     if (st.st_size == 0) {
     674           0 :         close(fd);
     675           0 :         return false;
     676             :     }
     677             : 
     678           0 :     lseek(fd, 0, SEEK_SET);
     679             : 
     680             :     std::array<char, ERASE_BLOCK> buffer;
     681           0 :     buffer.fill(0);
     682           0 :     decltype(st.st_size) written(0);
     683           0 :     while (written < st.st_size) {
     684           0 :         auto ret = write(fd, buffer.data(), buffer.size());
     685           0 :         if (ret < 0) {
     686           0 :             JAMI_WARNING("Error while overriding file with zeros.");
     687           0 :             break;
     688             :         } else
     689           0 :             written += ret;
     690             :     }
     691             : 
     692           0 :     if (dosync)
     693           0 :         fsync(fd);
     694             : 
     695           0 :     close(fd);
     696           0 :     return written >= st.st_size;
     697             : }
     698             : #endif
     699             : 
     700             : bool
     701           0 : eraseFile(const std::string& path, bool dosync)
     702             : {
     703             : #ifdef _WIN32
     704             :     return eraseFile_win32(path, dosync);
     705             : #else
     706           0 :     return eraseFile_posix(path, dosync);
     707             : #endif
     708             : }
     709             : 
     710             : int
     711           0 : remove(const std::filesystem::path& path, bool erase)
     712             : {
     713           0 :     if (erase and dhtnet::fileutils::isFile(path, false) and !dhtnet::fileutils::hasHardLink(path))
     714           0 :         eraseFile(path.string(), true);
     715             : 
     716             : #ifdef _WIN32
     717             :     // use Win32 api since std::remove will not unlink directory in use
     718             :     if (std::filesystem::is_directory(path))
     719             :         return !RemoveDirectory(path.c_str());
     720             : #endif
     721             : 
     722           0 :     return std::remove(path.string().c_str());
     723             : }
     724             : 
     725             : int64_t
     726          22 : size(const std::filesystem::path& path)
     727             : {
     728          22 :     int64_t size = 0;
     729             :     try {
     730          22 :         std::ifstream file(path, std::ios::binary | std::ios::in);
     731          22 :         file.seekg(0, std::ios_base::end);
     732          22 :         size = file.tellg();
     733          22 :         file.close();
     734          22 :     } catch (...) {
     735           0 :     }
     736          22 :     return size;
     737             : }
     738             : 
     739             : std::string
     740          44 : sha3File(const std::filesystem::path& path)
     741             : {
     742             :     sha3_512_ctx ctx;
     743          44 :     sha3_512_init(&ctx);
     744             : 
     745             :     try {
     746          44 :         if (not std::filesystem::is_regular_file(path))
     747           0 :             return {};
     748          44 :         std::ifstream file(path, std::ios::binary | std::ios::in);
     749          44 :         if (!file)
     750           0 :             return {};
     751          44 :         std::vector<char> buffer(8192, 0);
     752         675 :         while (!file.eof()) {
     753         631 :             file.read(buffer.data(), buffer.size());
     754         631 :             std::streamsize readSize = file.gcount();
     755         631 :             sha3_512_update(&ctx, readSize, (const uint8_t*) buffer.data());
     756             :         }
     757          44 :     } catch (...) {
     758           0 :         return {};
     759           0 :     }
     760             : 
     761             :     unsigned char digest[SHA3_512_DIGEST_SIZE];
     762          44 :     sha3_512_digest(&ctx, SHA3_512_DIGEST_SIZE, digest);
     763             : 
     764             :     char hash[SHA3_512_DIGEST_SIZE * 2];
     765             : 
     766        2860 :     for (int i = 0; i < SHA3_512_DIGEST_SIZE; ++i)
     767        2816 :         pj_val_to_hex_digit(digest[i], &hash[2 * i]);
     768             : 
     769          44 :     return {hash, SHA3_512_DIGEST_SIZE * 2};
     770             : }
     771             : 
     772             : std::string
     773           0 : sha3sum(const uint8_t* data, size_t size)
     774             : {
     775             :     sha3_512_ctx ctx;
     776           0 :     sha3_512_init(&ctx);
     777           0 :     sha3_512_update(&ctx, size, data);
     778             :     unsigned char digest[SHA3_512_DIGEST_SIZE];
     779           0 :     sha3_512_digest(&ctx, SHA3_512_DIGEST_SIZE, digest);
     780           0 :     return dht::toHex(digest, SHA3_512_DIGEST_SIZE);
     781             : }
     782             : 
     783             : int
     784           7 : accessFile(const std::string& file, int mode)
     785             : {
     786             : #ifdef _WIN32
     787             :     return _waccess(jami::to_wstring(file).c_str(), mode);
     788             : #else
     789           7 :     return access(file.c_str(), mode);
     790             : #endif
     791             : }
     792             : 
     793             : uint64_t
     794          18 : lastWriteTimeInSeconds(const std::filesystem::path& filePath)
     795             : {
     796          16 :     return std::chrono::duration_cast<std::chrono::seconds>(
     797          18 :                std::filesystem::last_write_time(filePath).time_since_epoch())
     798          16 :         .count();
     799             : }
     800             : 
     801             : } // namespace fileutils
     802             : } // namespace jami

Generated by: LCOV version 1.14