LCOV - code coverage report
Current view: top level - src - fileutils.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 147 292 50.3 %
Date: 2024-04-26 09:41:19 Functions: 38 65 58.5 %

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

Generated by: LCOV version 1.14