Ring Daemon
Loading...
Searching...
No Matches
fileutils.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 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#include "string_utils.h"
28
29#include <opendht/crypto.h>
30
31#ifdef __APPLE__
32#include <TargetConditionals.h>
33#endif
34
35#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
36#include "client/jami_signal.h"
37#endif
38
39#ifdef _WIN32
40#include <windows.h>
41#include "string_utils.h"
42#endif
43
44#include <sys/types.h>
45#include <sys/stat.h>
46
47#ifndef _MSC_VER
48#include <libgen.h>
49#endif
50
51#ifdef _MSC_VER
52#include "windirent.h"
53#else
54#include <dirent.h>
55#endif
56
57#include <signal.h>
58#include <unistd.h>
59#include <fcntl.h>
60#ifndef _WIN32
61#include <pwd.h>
62#else
63#include <shlobj.h>
64#define NAME_MAX 255
65#endif
66#if !defined __ANDROID__ && !defined _WIN32
67#include <wordexp.h>
68#endif
69
70#include <nettle/sha3.h>
71
72#include <sstream>
73#include <fstream>
74#include <iostream>
75#include <stdexcept>
76#include <limits>
77#include <array>
78
79#include <cstdlib>
80#include <cstring>
81#include <cerrno>
82#include <cstddef>
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
98const wchar_t*
99winGetEnv(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
120namespace jami {
121namespace fileutils {
122
123static std::filesystem::path resource_dir_path;
124
125void
126set_resource_dir_path(const std::filesystem::path& resourceDirPath)
127{
129}
130
131const std::filesystem::path&
133{
134 static const std::filesystem::path jami_default_data_dir(JAMI_DATADIR);
136}
137
138std::string
139expand_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 std::string result;
147
148 wordexp_t p;
149 int ret = wordexp(path.c_str(), &p, 0);
150
151 switch (ret) {
152 case WRDE_BADCHAR:
153 JAMI_ERR("Illegal occurrence of newline or one of |, &, ;, <, >, "
154 "(, ), {, }.");
155 return result;
156 case WRDE_BADVAL:
157 JAMI_ERR("An undefined shell variable was referenced");
158 return result;
159 case WRDE_CMDSUB:
160 JAMI_ERR("Command substitution occurred");
161 return result;
162 case WRDE_SYNTAX:
163 JAMI_ERR("Shell syntax error");
164 return result;
165 case WRDE_NOSPACE:
166 JAMI_ERR("Out of memory.");
167 // This is the only error where we must call wordfree
168 break;
169 default:
170 if (p.we_wordc > 0)
171 result = std::string(p.we_wordv[0]);
172 break;
173 }
174
175 wordfree(&p);
176
177 return result;
178#endif
179}
180
181bool
183{
184 return accessFile(directory, W_OK) == 0;
185}
186
187bool
188createSymlink(const std::filesystem::path& linkFile, const std::filesystem::path& target)
189{
190 std::error_code ec;
191 std::filesystem::create_symlink(target, linkFile, ec);
192 if (ec) {
193 JAMI_WARNING("Unable to create soft link from {} to {}: {}", linkFile, target, ec.message());
194 return false;
195 } else {
196 JAMI_LOG("Created soft link from {} to {}", linkFile, target);
197 }
198 return true;
199}
200
201bool
202createHardlink(const std::filesystem::path& linkFile, const std::filesystem::path& target)
203{
204 std::error_code ec;
205 std::filesystem::create_hard_link(target, linkFile, ec);
206 if (ec) {
207 JAMI_WARNING("Unable to create hard link from {} to {}: {}", linkFile, target, ec.message());
208 return false;
209 } else {
210 JAMI_LOG("Created hard link from {} to {}", linkFile, target);
211 }
212 return true;
213}
214
215bool
216createFileLink(const std::filesystem::path& linkFile, const std::filesystem::path& target, bool hard)
217{
218 if (linkFile == target)
219 return true;
220 std::error_code ec;
221 // Use symlink_status() because exists() could return false for broken symlinks
222 auto status = std::filesystem::symlink_status(linkFile, ec);
223 if (status.type() != std::filesystem::file_type::not_found) {
224 if (status.type() == std::filesystem::file_type::symlink
225 && std::filesystem::read_symlink(linkFile, ec) == target) {
226 JAMI_DEBUG("createFileLink: {} symlink already points to target {}", linkFile, target);
227 return true;
228 }
229 // Remove any existing file or symlink before creating a new one, as create_symlink()
230 // will fail with "File exists" error if the linkFile path already exists.
231 if (status.type() == std::filesystem::file_type::regular
232 || status.type() == std::filesystem::file_type::symlink) {
233 std::filesystem::remove(linkFile, ec);
234 }
235 }
236
237 // Try to create a hard link if requested; fall back to symlink on failure
240 return true;
241}
242
243std::string_view
244getFileExtension(std::string_view filename)
245{
246 std::string_view result;
247 auto sep = filename.find_last_of('.');
248 if (sep != std::string_view::npos && sep != filename.size() - 1)
249 result = filename.substr(sep + 1);
250 if (result.size() >= 8)
251 return {};
252 return result;
253}
254
255bool
256isPathRelative(const std::filesystem::path& path)
257{
258 return not path.empty() and path.is_relative();
259}
260
261std::string
262getCleanPath(const std::string& base, const std::string& path)
263{
264 if (base.empty() or path.size() < base.size())
265 return path;
266 auto base_sep = base + DIR_SEPARATOR_STR;
267 if (path.compare(0, base_sep.size(), base_sep) == 0)
268 return path.substr(base_sep.size());
269 else
270 return path;
271}
272
273std::filesystem::path
274getFullPath(const std::filesystem::path& base, const std::filesystem::path& path)
275{
276 bool isRelative {not base.empty() and isPathRelative(path)};
277 return isRelative ? base / path : path;
278}
279
280std::vector<uint8_t>
281loadFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
282{
283 return dhtnet::fileutils::loadFile(getFullPath(default_dir, path));
284}
285
286std::string
287loadTextFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
288{
289 std::string buffer;
290 auto fullPath = getFullPath(default_dir, path);
291
292 // Open with explicit share mode to allow reading even if file is opened elsewhere
293#ifdef _WIN32
294 std::ifstream file(fullPath, std::ios::in | std::ios::binary, _SH_DENYNO);
295#else
296 std::ifstream file(fullPath);
297#endif
298
299 if (!file)
300 throw std::runtime_error("Unable to read file: " + path.string());
301
302 file.seekg(0, std::ios::end);
303 auto size = file.tellg();
304 if (size > std::numeric_limits<unsigned>::max())
305 throw std::runtime_error("File is too big: " + path.string());
306 buffer.resize(size);
307 file.seekg(0, std::ios::beg);
308 if (!file.read((char*) buffer.data(), size))
309 throw std::runtime_error("Unable to load file: " + path.string());
310 return buffer;
311}
312
313void
314saveFile(const std::filesystem::path& path, const uint8_t* data, size_t data_size, mode_t UNUSED mode)
315{
316 std::ofstream file(path, std::ios::trunc | std::ios::binary);
317 if (!file.is_open()) {
318 JAMI_ERROR("Unable to write data to {}", path);
319 return;
320 }
321 file.write((char*) data, data_size);
322#ifndef _WIN32
323 file.close();
324 if (chmod(path.c_str(), mode) < 0)
325 JAMI_WARNING("fileutils::saveFile(): chmod() failed on {}, {}", path, strerror(errno));
326#endif
327}
328
329std::vector<uint8_t>
330loadCacheFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
331{
332 // last_write_time throws exception if file doesn't exist
333 std::error_code ec;
334 auto writeTime = std::filesystem::last_write_time(path, ec);
335 if (ec)
336 throw std::runtime_error("unable to get last write time of file");
337 auto now = decltype(writeTime)::clock::now();
338 if (now - writeTime > maxAge)
339 throw std::runtime_error("file too old " + dht::print_time_relative(now, writeTime));
340
341 JAMI_LOG("Loading cache file '{}'", path);
342 return dhtnet::fileutils::loadFile(path);
343}
344
345std::string
346loadCacheTextFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
347{
348 // last_write_time throws exception if file doesn't exist
349 std::error_code ec;
350 auto writeTime = std::filesystem::last_write_time(path, ec);
351 if (ec)
352 throw std::runtime_error("unable to get last write time of file");
353 auto now = decltype(writeTime)::clock::now();
354 if (now - writeTime > maxAge)
355 throw std::runtime_error("file too old " + dht::print_time_relative(now, writeTime));
356
357 JAMI_LOG("Loading cache file '{}'", path);
358 return loadTextFile(path);
359}
360
361ArchiveStorageData
362readArchive(const std::filesystem::path& path, std::string_view scheme, const std::string& pwd)
363{
364 JAMI_LOG("Reading archive from {} with scheme '{}'", path, scheme);
365
366 auto isUnencryptedGzip = [](const std::vector<uint8_t>& data) {
367 // NOTE: some webserver modify gzip files and this can end with a gunzip in a gunzip
368 // file. So, to make the readArchive more robust, we can support this case by detecting
369 // gzip header via 1f8b 08
370 // We don't need to support more than 2 level, else somebody may be able to send
371 // gunzip in loops and abuse.
372 return data.size() > 3 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08;
373 };
374
375 auto decompress = [](std::vector<uint8_t>& data) {
376 try {
377 data = archiver::decompress(data);
378 } catch (const std::exception& e) {
379 JAMI_ERROR("Error decrypting archive: {}", e.what());
380 throw e;
381 }
382 };
383
384 std::vector<uint8_t> fileContent;
385
386 // Read file
387 try {
388 fileContent = dhtnet::fileutils::loadFile(path);
389 } catch (const std::exception& e) {
390 JAMI_ERROR("Error loading archive: {}", e.what());
391 throw;
392 }
393
395 if (!pwd.empty())
396 JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
397 decompress(fileContent);
398 }
399
401 // ret.data = {fileContent.data(), fileContent.data()+fileContent.size()};
402
403 if (!pwd.empty()) {
404 // Decrypt
405 if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
406 try {
407 ret.salt = dht::crypto::aesGetSalt(fileContent);
408 fileContent = dht::crypto::aesDecrypt(dht::crypto::aesGetEncrypted(fileContent), base64::decode(pwd));
409 } catch (const std::exception& e) {
410 JAMI_ERROR("Error decrypting archive: {}", e.what());
411 throw;
412 }
413 } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD) {
414 try {
415 ret.salt = dht::crypto::aesGetSalt(fileContent);
416 fileContent = dht::crypto::aesDecrypt(fileContent, pwd);
417 } catch (const std::exception& e) {
418 JAMI_ERROR("Error decrypting archive: {}", e.what());
419 throw;
420 }
421 }
422 decompress(fileContent);
423 } else if (isUnencryptedGzip(fileContent)) {
424 JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
425 decompress(fileContent);
426 }
427 ret.data = {fileContent.data(), fileContent.data() + fileContent.size()};
428 return ret;
429}
430
431bool
432writeArchive(const std::string& archive_str,
433 const std::filesystem::path& path,
434 std::string_view scheme,
435 const std::string& password,
436 const std::vector<uint8_t>& password_salt)
437{
438 JAMI_LOG("Writing archive to {} using scheme '{}'", path, scheme);
439
440 if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
441 // Encrypt using provided key
442 try {
443 auto key = base64::decode(password);
444 auto newArchive = dht::crypto::aesEncrypt(archiver::compress(archive_str), key);
445 saveFile(path, dht::crypto::aesBuildEncrypted(newArchive, password_salt));
446 } catch (const std::runtime_error& ex) {
447 JAMI_ERROR("Export failed: {}", ex.what());
448 return false;
449 }
450 } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD and not password.empty()) {
451 // Encrypt using provided password
452 try {
453 saveFile(path, dht::crypto::aesEncrypt(archiver::compress(archive_str), password, password_salt));
454 } catch (const std::runtime_error& ex) {
455 JAMI_ERROR("Export failed: {}", ex.what());
456 return false;
457 }
458 } else if (scheme == ARCHIVE_AUTH_SCHEME_NONE || (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD && password.empty())) {
459 JAMI_WARNING("Unsecured archiving (no password)");
460 archiver::compressGzip(archive_str, path.string());
461 } else {
462 JAMI_ERROR("Unsupported scheme: {}", scheme);
463 return false;
464 }
465 return true;
466}
467
468std::filesystem::path
470{
471#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
472 std::vector<std::string> paths;
473 paths.reserve(1);
475 if (not paths.empty())
476 return paths[0];
477 return {};
478#elif defined(__APPLE__)
479 return get_home_dir() / "Library" / "Caches" / pkg;
480#else
481#ifdef _WIN32
482 const std::wstring cache_home(JAMI_CACHE_HOME);
483 if (not cache_home.empty())
485#else
486 const std::string cache_home(XDG_CACHE_HOME);
487 if (not cache_home.empty())
488 return cache_home;
489#endif
490 return get_home_dir() / ".cache" / pkg;
491#endif
492}
493
494const std::filesystem::path&
496{
497 static const std::filesystem::path cache_dir = get_cache_dir(PACKAGE);
498 return cache_dir;
499}
500
501std::filesystem::path
503{
504#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
505 std::vector<std::string> paths;
506 paths.reserve(1);
508 if (not paths.empty())
509 return paths[0];
510 return {};
511#elif defined _WIN32
512 TCHAR path[MAX_PATH];
513 if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_PROFILE, nullptr, 0, path))) {
514 return jami::to_string(path);
515 }
516 return {};
517#else
518
519 // 1) try getting user's home directory from the environment
520 std::string home(PROTECTED_GETENV("HOME"));
521 if (not home.empty())
522 return home;
523
524 // 2) try getting it from getpwuid_r (i.e. /etc/passwd)
525 const long max = sysconf(_SC_GETPW_R_SIZE_MAX);
526 if (max != -1) {
527 char buf[max];
528 struct passwd pwbuf, *pw;
529 if (getpwuid_r(getuid(), &pwbuf, buf, sizeof(buf), &pw) == 0 and pw != NULL)
530 return pw->pw_dir;
531 }
532
533 return {};
534#endif
535}
536
537const std::filesystem::path&
539{
540 static const std::filesystem::path home_dir = get_home_dir_impl();
541 return home_dir;
542}
543
544std::filesystem::path
546{
547#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
548 std::vector<std::string> paths;
549 paths.reserve(1);
551 if (not paths.empty())
552 return paths[0];
553 return {};
554#elif defined(__APPLE__)
555 return get_home_dir() / "Library" / "Application Support" / pkg;
556#elif defined(_WIN32)
557 std::wstring data_home(JAMI_DATA_HOME);
558 if (not data_home.empty())
559 return std::filesystem::path(data_home) / pkg;
560
561 if (!strcmp(pkg, "ring")) {
562 return get_home_dir() / ".local" / "share" / pkg;
563 } else {
564 return get_home_dir() / "AppData" / "Local" / pkg;
565 }
566#else
567 std::string_view data_home(XDG_DATA_HOME);
568 if (not data_home.empty())
569 return std::filesystem::path(data_home) / pkg;
570 // "If $XDG_DATA_HOME is either not set or empty, a default equal to
571 // $HOME/.local/share should be used."
572 return get_home_dir() / ".local" / "share" / pkg;
573#endif
574}
575
576const std::filesystem::path&
578{
579 static const std::filesystem::path data_dir = get_data_dir(PACKAGE);
580 return data_dir;
581}
582
583std::filesystem::path
585{
586 std::filesystem::path configdir;
587#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
588 std::vector<std::string> paths;
590 if (not paths.empty())
591 configdir = std::filesystem::path(paths[0]);
592#elif defined(__APPLE__)
593 configdir = fileutils::get_home_dir() / "Library" / "Application Support" / pkg;
594#elif defined(_WIN32)
595 std::wstring xdg_env(JAMI_CONFIG_HOME);
596 if (not xdg_env.empty()) {
597 configdir = std::filesystem::path(xdg_env) / pkg;
598 } else if (!strcmp(pkg, "ring")) {
599 configdir = fileutils::get_home_dir() / ".config" / pkg;
600 } else {
601 configdir = fileutils::get_home_dir() / "AppData" / "Local" / pkg;
602 }
603#else
604 std::string xdg_env(XDG_CONFIG_HOME);
605 if (not xdg_env.empty())
606 configdir = std::filesystem::path(xdg_env) / pkg;
607 else
608 configdir = fileutils::get_home_dir() / ".config" / pkg;
609#endif
610 if (!dhtnet::fileutils::recursive_mkdir(configdir, 0700)) {
611 // If directory creation failed
612 if (errno != EEXIST)
613 JAMI_DBG("Unable to create directory: %s!", configdir.c_str());
614 }
615 return configdir;
616}
617
618const std::filesystem::path&
620{
621 static const std::filesystem::path config_dir = get_config_dir(PACKAGE);
622 return config_dir;
623}
624
625#ifdef _WIN32
626bool
627eraseFile_win32(const std::string& path, bool dosync)
628{
629 // Note: from
630 // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-deletefilea#remarks To
631 // delete a read-only file, first you must remove the read-only attribute.
632 SetFileAttributesA(path.c_str(), GetFileAttributesA(path.c_str()) & ~FILE_ATTRIBUTE_READONLY);
634 if (h == INVALID_HANDLE_VALUE) {
635 JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
636 return false;
637 }
638
639 LARGE_INTEGER size;
640 if (!GetFileSizeEx(h, &size)) {
641 JAMI_WARN("Unable to erase file %s: GetFileSizeEx() failed.", path.c_str());
642 CloseHandle(h);
643 return false;
644 }
645 if (size.QuadPart == 0) {
646 CloseHandle(h);
647 return false;
648 }
649
650 uint64_t size_blocks = size.QuadPart / ERASE_BLOCK;
651 if (size.QuadPart % ERASE_BLOCK)
652 size_blocks++;
653
654 char* buffer;
655 try {
656 buffer = new char[ERASE_BLOCK];
657 } catch (std::bad_alloc& ba) {
658 JAMI_WARN("Unable to allocate buffer for erasing %s.", path.c_str());
659 CloseHandle(h);
660 return false;
661 }
662 memset(buffer, 0x00, ERASE_BLOCK);
663
665 if (size.QuadPart < (1024 - 42)) { // a small file can be stored in the MFT record
666 ovlp.Offset = 0;
667 ovlp.OffsetHigh = 0;
668 WriteFile(h, buffer, (DWORD) size.QuadPart, 0, &ovlp);
670 }
671 for (uint64_t i = 0; i < size_blocks; i++) {
672 uint64_t offset = i * ERASE_BLOCK;
673 ovlp.Offset = offset & 0x00000000FFFFFFFF;
674 ovlp.OffsetHigh = offset >> 32;
676 }
677
678 delete[] buffer;
679
680 if (dosync)
682
683 CloseHandle(h);
684 return true;
685}
686
687#else
688
689bool
690eraseFile_posix(const std::string& path, bool dosync)
691{
692 struct stat st;
693 if (stat(path.c_str(), &st) == -1) {
694 JAMI_WARN("Unable to erase file %s: fstat() failed.", path.c_str());
695 return false;
696 }
697 // Remove read-only flag if possible
698 chmod(path.c_str(), st.st_mode | (S_IWGRP + S_IWUSR));
699
700 int fd = open(path.c_str(), O_WRONLY);
701 if (fd == -1) {
702 JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
703 return false;
704 }
705
706 if (st.st_size == 0) {
707 close(fd);
708 return false;
709 }
710
711 lseek(fd, 0, SEEK_SET);
712
713 std::array<char, ERASE_BLOCK> buffer;
714 buffer.fill(0);
715 decltype(st.st_size) written(0);
716 while (written < st.st_size) {
717 auto ret = write(fd, buffer.data(), buffer.size());
718 if (ret < 0) {
719 JAMI_WARNING("Error while overriding file with zeros.");
720 break;
721 } else
722 written += ret;
723 }
724
725 if (dosync)
726 fsync(fd);
727
728 close(fd);
729 return written >= st.st_size;
730}
731#endif
732
733bool
734eraseFile(const std::string& path, bool dosync)
735{
736#ifdef _WIN32
737 return eraseFile_win32(path, dosync);
738#else
739 return eraseFile_posix(path, dosync);
740#endif
741}
742
743int
744remove(const std::filesystem::path& path, bool erase)
745{
746 if (erase and dhtnet::fileutils::isFile(path, false) and !dhtnet::fileutils::hasHardLink(path))
747 eraseFile(path.string(), true);
748
749#ifdef _WIN32
750 // use Win32 api since std::remove will not unlink directory in use
751 if (std::filesystem::is_directory(path))
752 return !RemoveDirectory(path.c_str());
753#endif
754
755 return std::remove(path.string().c_str());
756}
757
758std::string
759sha3File(const std::filesystem::path& path)
760{
763
764 try {
765 if (not std::filesystem::is_regular_file(path)) {
766 JAMI_ERROR("Unable to compute sha3sum of {}: not a regular file", path);
767 return {};
768 }
769 std::ifstream file(path, std::ios::binary | std::ios::in);
770 if (!file) {
771 JAMI_ERROR("Unable to compute sha3sum of {}: failed to open file", path);
772 return {};
773 }
774 constexpr size_t BUFFER_SIZE = 64 * 1024ul;
775 std::vector<char> buffer(BUFFER_SIZE);
776 while (file) {
777 file.read(buffer.data(), BUFFER_SIZE);
778 const auto bytesRead = file.gcount();
779 if (bytesRead == 0)
780 break;
781 sha3_512_update(&ctx, static_cast<size_t>(bytesRead), (const uint8_t*) buffer.data());
782 }
783 } catch (const std::exception& e) {
784 JAMI_ERROR("Unable to compute sha3sum of {}: {}", path, e.what());
785 return {};
786 }
787
788 unsigned char digest[SHA3_512_DIGEST_SIZE];
790 return dht::toHex(digest, SHA3_512_DIGEST_SIZE);
791}
792
793std::string
794sha3sum(const std::vector<uint8_t>& buffer)
795{
798 sha3_512_update(&ctx, buffer.size(), buffer.data());
799 unsigned char digest[SHA3_512_DIGEST_SIZE];
801 return dht::toHex(digest, SHA3_512_DIGEST_SIZE);
802}
803
804int
805accessFile(const std::string& file, int mode)
806{
807#ifdef _WIN32
808 return _waccess(jami::to_wstring(file).c_str(), mode);
809#else
810 return access(file.c_str(), mode);
811#endif
812}
813
815lastWriteTimeInSeconds(const std::filesystem::path& filePath)
816{
817 std::error_code ec;
818 auto lastWrite = std::filesystem::last_write_time(filePath, ec);
819 if (ec) {
820 JAMI_WARNING("Unable to get last write time of {}: {}", filePath, ec.message());
821 return 0;
822 }
823 return std::chrono::duration_cast<std::chrono::seconds>(lastWrite.time_since_epoch()).count();
824}
825
826std::string
828{
829 const auto& localDir = get_data_dir(); // ~/.local/share/jami/
830 auto fullIdPath = localDir / "local_device_id";
831 std::string localDeviceId;
832
833 if (std::filesystem::exists(fullIdPath)) {
834 // Get the id inside the file
835 std::ifstream inStream(fullIdPath);
836 std::getline(inStream, localDeviceId);
837 inStream.close();
838 if (!localDeviceId.empty())
839 return localDeviceId;
840 }
841
842 // Generate a random hex string
843 {
844 std::random_device randomDevice;
845 localDeviceId = to_hex_string(std::uniform_int_distribution<uint64_t>()(randomDevice));
846 }
847
848 // Create a new file and write the id in it
849 std::error_code ec;
850 std::filesystem::create_directories(localDir, ec);
851 std::ofstream outStream(fullIdPath);
852 if (outStream) {
853 outStream << localDeviceId << '\n';
854 outStream.close();
855 } else {
856 JAMI_ERROR("Unable to create local device id file: {}", fullIdPath);
857 }
858 return localDeviceId;
859}
860
861} // namespace fileutils
862} // namespace jami
#define UNUSED
#define XDG_CONFIG_HOME
Definition fileutils.cpp:95
#define ERASE_BLOCK
#define XDG_CACHE_HOME
Definition fileutils.cpp:96
#define PROTECTED_GETENV(str)
Definition fileutils.cpp:88
#define XDG_DATA_HOME
Definition fileutils.cpp:94
#define DIR_SEPARATOR_STR
Definition fileutils.h:33
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_ERROR(formatstr,...)
Definition logger.h:243
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:238
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
#define JAMI_LOG(formatstr,...)
Definition logger.h:237
void compressGzip(const std::string &str, const std::string &path)
Compress string to a Gzip file.
Definition archiver.cpp:66
std::vector< uint8_t > decompress(const std::vector< uint8_t > &str)
Decompress an STL string using zlib and return the original data.
Definition archiver.cpp:101
std::vector< uint8_t > compress(const std::string &str)
Compress a STL string using zlib with given compression level and return the binary data.
Definition archiver.cpp:51
std::vector< unsigned char > decode(std::string_view str)
Definition base64.cpp:35
bool createSymlink(const std::filesystem::path &linkFile, const std::filesystem::path &target)
const std::filesystem::path & get_data_dir()
void set_resource_dir_path(const std::filesystem::path &resourceDirPath)
Set the program's resource directory path.
std::vector< uint8_t > loadCacheFile(const std::filesystem::path &path, std::chrono::system_clock::duration maxAge)
static constexpr auto ARCHIVE_AUTH_SCHEME_PASSWORD
Definition fileutils.h:115
int accessFile(const std::string &file, int mode)
Windows compatibility wrapper for checking read-only attribute.
std::string getOrCreateLocalDeviceId()
std::string sha3File(const std::filesystem::path &path)
std::filesystem::path get_home_dir_impl()
int remove(const std::filesystem::path &path, bool erase)
bool createFileLink(const std::filesystem::path &linkFile, const std::filesystem::path &target, bool hard)
void saveFile(const std::filesystem::path &path, const uint8_t *data, size_t data_size, mode_t UNUSED mode)
bool eraseFile(const std::string &path, bool dosync)
static constexpr auto ARCHIVE_AUTH_SCHEME_NONE
Definition fileutils.h:114
std::string getCleanPath(const std::string &base, const std::string &path)
If path is contained in base, return the suffix, otherwise return the full path.
ArchiveStorageData readArchive(const std::filesystem::path &path, std::string_view scheme, const std::string &pwd)
static std::filesystem::path resource_dir_path
const std::filesystem::path & get_config_dir()
bool createHardlink(const std::filesystem::path &linkFile, const std::filesystem::path &target)
std::string loadCacheTextFile(const std::filesystem::path &path, std::chrono::system_clock::duration maxAge)
std::vector< uint8_t > loadFile(const std::filesystem::path &path, const std::filesystem::path &default_dir)
Read the full content of a file at path.
uint64_t lastWriteTimeInSeconds(const std::filesystem::path &filePath)
Return the last write time (epoch time) of a given file path (in seconds).
static constexpr auto ARCHIVE_AUTH_SCHEME_KEY
Definition fileutils.h:116
bool isPathRelative(const std::filesystem::path &path)
std::string sha3sum(const std::vector< uint8_t > &buffer)
std::string_view getFileExtension(std::string_view filename)
const std::filesystem::path & get_cache_dir()
bool isDirectoryWritable(const std::string &directory)
std::filesystem::path getFullPath(const std::filesystem::path &base, const std::filesystem::path &path)
If path is relative, it is appended to base.
bool writeArchive(const std::string &archive_str, const std::filesystem::path &path, std::string_view scheme, const std::string &password, const std::vector< uint8_t > &password_salt)
const std::filesystem::path & get_resource_dir_path()
Get the resource directory path that was set with set_resource_dir_path.
std::string expand_path(const std::string &path)
Expand the given path.
const std::filesystem::path & get_home_dir()
std::string loadTextFile(const std::filesystem::path &path, const std::filesystem::path &default_dir)
bool eraseFile_posix(const std::string &path, bool dosync)
void emitSignal(Args... args)
Definition jami_signal.h:64
std::string to_hex_string(uint64_t id)
std::string to_string(double value)
std::vector< uint8_t > salt
Definition fileutils.h:121
#define S_IWGRP
#define S_IWUSR
Definition windirent.h:110