Ring Daemon 16.0.0
Loading...
Searching...
No Matches
fileutils.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 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
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 if (std::filesystem::exists(linkFile, ec)) {
222 if (std::filesystem::is_symlink(linkFile, ec)
223 && std::filesystem::read_symlink(linkFile, ec) == target)
224 return true;
225 std::filesystem::remove(linkFile, ec);
226 }
229 return true;
230}
231
232std::string_view
233getFileExtension(std::string_view filename)
234{
235 std::string_view result;
236 auto sep = filename.find_last_of('.');
237 if (sep != std::string_view::npos && sep != filename.size() - 1)
238 result = filename.substr(sep + 1);
239 if (result.size() >= 8)
240 return {};
241 return result;
242}
243
244bool
245isPathRelative(const std::filesystem::path& path)
246{
247 return not path.empty() and path.is_relative();
248}
249
250std::string
251getCleanPath(const std::string& base, const std::string& path)
252{
253 if (base.empty() or path.size() < base.size())
254 return path;
255 auto base_sep = base + DIR_SEPARATOR_STR;
256 if (path.compare(0, base_sep.size(), base_sep) == 0)
257 return path.substr(base_sep.size());
258 else
259 return path;
260}
261
262std::filesystem::path
263getFullPath(const std::filesystem::path& base, const std::filesystem::path& path)
264{
265 bool isRelative {not base.empty() and isPathRelative(path)};
266 return isRelative ? base / path : path;
267}
268
269std::vector<uint8_t>
270loadFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
271{
272 return dhtnet::fileutils::loadFile(getFullPath(default_dir, path));
273}
274
275std::string
276loadTextFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
277{
278 std::string buffer;
279 auto fullPath = getFullPath(default_dir, path);
280
281 // Open with explicit share mode to allow reading even if file is opened elsewhere
282#ifdef _WIN32
283 std::ifstream file(fullPath, std::ios::in | std::ios::binary, _SH_DENYNO);
284#else
285 std::ifstream file(fullPath);
286#endif
287
288 if (!file)
289 throw std::runtime_error("Unable to read file: " + path.string());
290
291 file.seekg(0, std::ios::end);
292 auto size = file.tellg();
293 if (size > std::numeric_limits<unsigned>::max())
294 throw std::runtime_error("File is too big: " + path.string());
295 buffer.resize(size);
296 file.seekg(0, std::ios::beg);
297 if (!file.read((char*) buffer.data(), size))
298 throw std::runtime_error("Unable to load file: " + path.string());
299 return buffer;
300}
301
302void
303saveFile(const std::filesystem::path& path,
304 const uint8_t* data,
305 size_t data_size,
306 mode_t UNUSED mode)
307{
308 std::ofstream file(path, std::ios::trunc | std::ios::binary);
309 if (!file.is_open()) {
310 JAMI_ERROR("Unable to write data to {}", path);
311 return;
312 }
313 file.write((char*) data, data_size);
314#ifndef _WIN32
315 file.close();
316 if (chmod(path.c_str(), mode) < 0)
317 JAMI_WARNING("fileutils::saveFile(): chmod() failed on {}, {}", path, strerror(errno));
318#endif
319}
320
321std::vector<uint8_t>
322loadCacheFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
323{
324 // last_write_time throws exception if file doesn't exist
325 std::error_code ec;
326 auto writeTime = std::filesystem::last_write_time(path, ec);
327 if (ec)
328 throw std::runtime_error("unable to get last write time of file");
329 auto now = decltype(writeTime)::clock::now();
330 if (now - writeTime > maxAge)
331 throw std::runtime_error("file too old " + dht::print_time_relative(now, writeTime));
332
333 JAMI_LOG("Loading cache file '{}'", path);
334 return dhtnet::fileutils::loadFile(path);
335}
336
337std::string
338loadCacheTextFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
339{
340 // last_write_time throws exception if file doesn't exist
341 std::error_code ec;
342 auto writeTime = std::filesystem::last_write_time(path, ec);
343 if (ec)
344 throw std::runtime_error("unable to get last write time of file");
345 auto now = decltype(writeTime)::clock::now();
346 if (now - writeTime > maxAge)
347 throw std::runtime_error("file too old " + dht::print_time_relative(now, writeTime));
348
349 JAMI_LOG("Loading cache file '{}'", path);
350 return loadTextFile(path);
351}
352
353ArchiveStorageData
354readArchive(const std::filesystem::path& path, std::string_view scheme, const std::string& pwd)
355{
356 JAMI_LOG("Reading archive from {} with scheme '{}'", path, scheme);
357
358 auto isUnencryptedGzip = [](const std::vector<uint8_t>& data) {
359 // NOTE: some webserver modify gzip files and this can end with a gunzip in a gunzip
360 // file. So, to make the readArchive more robust, we can support this case by detecting
361 // gzip header via 1f8b 08
362 // We don't need to support more than 2 level, else somebody may be able to send
363 // gunzip in loops and abuse.
364 return data.size() > 3 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08;
365 };
366
367 auto decompress = [](std::vector<uint8_t>& data) {
368 try {
369 data = archiver::decompress(data);
370 } catch (const std::exception& e) {
371 JAMI_ERROR("Error decrypting archive: {}", e.what());
372 throw e;
373 }
374 };
375
376 std::vector<uint8_t> fileContent;
377
378 // Read file
379 try {
380 fileContent = dhtnet::fileutils::loadFile(path);
381 } catch (const std::exception& e) {
382 JAMI_ERROR("Error loading archive: {}", e.what());
383 throw;
384 }
385
387 if (!pwd.empty())
388 JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
389 decompress(fileContent);
390 }
391
393 // ret.data = {fileContent.data(), fileContent.data()+fileContent.size()};
394
395 if (!pwd.empty()) {
396 // Decrypt
397 if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
398 try {
399 ret.salt = dht::crypto::aesGetSalt(fileContent);
400 fileContent = dht::crypto::aesDecrypt(dht::crypto::aesGetEncrypted(fileContent),
402 } catch (const std::exception& e) {
403 JAMI_ERROR("Error decrypting archive: {}", e.what());
404 throw;
405 }
406 } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD) {
407 try {
408 ret.salt = dht::crypto::aesGetSalt(fileContent);
409 fileContent = dht::crypto::aesDecrypt(fileContent, pwd);
410 } catch (const std::exception& e) {
411 JAMI_ERROR("Error decrypting archive: {}", e.what());
412 throw;
413 }
414 }
415 decompress(fileContent);
416 } else if (isUnencryptedGzip(fileContent)) {
417 JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
418 decompress(fileContent);
419 }
420 ret.data = {fileContent.data(), fileContent.data() + fileContent.size()};
421 return ret;
422}
423
424bool
425writeArchive(const std::string& archive_str,
426 const std::filesystem::path& path,
427 std::string_view scheme,
428 const std::string& password,
429 const std::vector<uint8_t>& password_salt)
430{
431 JAMI_LOG("Writing archive to {} using scheme '{}'", path, scheme);
432
433 if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
434 // Encrypt using provided key
435 try {
436 auto key = base64::decode(password);
437 auto newArchive = dht::crypto::aesEncrypt(archiver::compress(archive_str), key);
438 saveFile(path, dht::crypto::aesBuildEncrypted(newArchive, password_salt));
439 } catch (const std::runtime_error& ex) {
440 JAMI_ERROR("Export failed: {}", ex.what());
441 return false;
442 }
443 } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD and not password.empty()) {
444 // Encrypt using provided password
445 try {
446 saveFile(path,
447 dht::crypto::aesEncrypt(archiver::compress(archive_str),
448 password,
449 password_salt));
450 } catch (const std::runtime_error& ex) {
451 JAMI_ERROR("Export failed: {}", ex.what());
452 return false;
453 }
454 } else if (scheme == ARCHIVE_AUTH_SCHEME_NONE || (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD && password.empty())) {
455 JAMI_WARNING("Unsecured archiving (no password)");
456 archiver::compressGzip(archive_str, path.string());
457 } else {
458 JAMI_ERROR("Unsupported scheme: {}", scheme);
459 return false;
460 }
461 return true;
462}
463
464std::filesystem::path
465get_cache_dir(const char* pkg)
466{
467#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
468 std::vector<std::string> paths;
469 paths.reserve(1);
471 if (not paths.empty())
472 return paths[0];
473 return {};
474#elif defined(__APPLE__)
475 return get_home_dir() / "Library" / "Caches" / pkg;
476#else
477#ifdef _WIN32
478 const std::wstring cache_home(JAMI_CACHE_HOME);
479 if (not cache_home.empty())
481#else
482 const std::string cache_home(XDG_CACHE_HOME);
483 if (not cache_home.empty())
484 return cache_home;
485#endif
486 return get_home_dir() / ".cache" / pkg;
487#endif
488}
489
490const std::filesystem::path&
492{
493 static const std::filesystem::path cache_dir = get_cache_dir(PACKAGE);
494 return cache_dir;
495}
496
497std::filesystem::path
499{
500#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
501 std::vector<std::string> paths;
502 paths.reserve(1);
504 if (not paths.empty())
505 return paths[0];
506 return {};
507#elif defined _WIN32
508 TCHAR path[MAX_PATH];
509 if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_PROFILE, nullptr, 0, path))) {
510 return jami::to_string(path);
511 }
512 return {};
513#else
514
515 // 1) try getting user's home directory from the environment
516 std::string home(PROTECTED_GETENV("HOME"));
517 if (not home.empty())
518 return home;
519
520 // 2) try getting it from getpwuid_r (i.e. /etc/passwd)
521 const long max = sysconf(_SC_GETPW_R_SIZE_MAX);
522 if (max != -1) {
523 char buf[max];
524 struct passwd pwbuf, *pw;
525 if (getpwuid_r(getuid(), &pwbuf, buf, sizeof(buf), &pw) == 0 and pw != NULL)
526 return pw->pw_dir;
527 }
528
529 return {};
530#endif
531}
532
533const std::filesystem::path&
535{
536 static const std::filesystem::path home_dir = get_home_dir_impl();
537 return home_dir;
538}
539
540std::filesystem::path
541get_data_dir(const char* pkg)
542{
543#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
544 std::vector<std::string> paths;
545 paths.reserve(1);
547 if (not paths.empty())
548 return paths[0];
549 return {};
550#elif defined(__APPLE__)
551 return get_home_dir() / "Library" / "Application Support" / pkg;
552#elif defined(_WIN32)
553 std::wstring data_home(JAMI_DATA_HOME);
554 if (not data_home.empty())
555 return std::filesystem::path(data_home) / pkg;
556
557 if (!strcmp(pkg, "ring")) {
558 return get_home_dir() / ".local" / "share" / pkg;
559 } else {
560 return get_home_dir() / "AppData" / "Local" / pkg;
561 }
562#else
563 std::string_view data_home(XDG_DATA_HOME);
564 if (not data_home.empty())
565 return std::filesystem::path(data_home) / pkg;
566 // "If $XDG_DATA_HOME is either not set or empty, a default equal to
567 // $HOME/.local/share should be used."
568 return get_home_dir() / ".local" / "share" / pkg;
569#endif
570}
571
572const std::filesystem::path&
574{
575 static const std::filesystem::path data_dir = get_data_dir(PACKAGE);
576 return data_dir;
577}
578
579std::filesystem::path
580get_config_dir(const char* pkg)
581{
582 std::filesystem::path configdir;
583#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
584 std::vector<std::string> paths;
586 if (not paths.empty())
587 configdir = std::filesystem::path(paths[0]);
588#elif defined(__APPLE__)
589 configdir = fileutils::get_home_dir() / "Library" / "Application Support" / pkg;
590#elif defined(_WIN32)
591 std::wstring xdg_env(JAMI_CONFIG_HOME);
592 if (not xdg_env.empty()) {
593 configdir = std::filesystem::path(xdg_env) / pkg;
594 } else if (!strcmp(pkg, "ring")) {
595 configdir = fileutils::get_home_dir() / ".config" / pkg;
596 } else {
597 configdir = fileutils::get_home_dir() / "AppData" / "Local" / pkg;
598 }
599#else
600 std::string xdg_env(XDG_CONFIG_HOME);
601 if (not xdg_env.empty())
602 configdir = std::filesystem::path(xdg_env) / pkg;
603 else
604 configdir = fileutils::get_home_dir() / ".config" / pkg;
605#endif
606 if (!dhtnet::fileutils::recursive_mkdir(configdir, 0700)) {
607 // If directory creation failed
608 if (errno != EEXIST)
609 JAMI_DBG("Unable to create directory: %s!", configdir.c_str());
610 }
611 return configdir;
612}
613
614const std::filesystem::path&
616{
617 static const std::filesystem::path config_dir = get_config_dir(PACKAGE);
618 return config_dir;
619}
620
621#ifdef _WIN32
622bool
623eraseFile_win32(const std::string& path, bool dosync)
624{
625 // Note: from
626 // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-deletefilea#remarks To
627 // delete a read-only file, first you must remove the read-only attribute.
628 SetFileAttributesA(path.c_str(), GetFileAttributesA(path.c_str()) & ~FILE_ATTRIBUTE_READONLY);
629 HANDLE h
631 if (h == INVALID_HANDLE_VALUE) {
632 JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
633 return false;
634 }
635
637 if (!GetFileSizeEx(h, &size)) {
638 JAMI_WARN("Unable to erase file %s: GetFileSizeEx() failed.", path.c_str());
639 CloseHandle(h);
640 return false;
641 }
642 if (size.QuadPart == 0) {
643 CloseHandle(h);
644 return false;
645 }
646
648 if (size.QuadPart % ERASE_BLOCK)
649 size_blocks++;
650
651 char* buffer;
652 try {
653 buffer = new char[ERASE_BLOCK];
654 } catch (std::bad_alloc& ba) {
655 JAMI_WARN("Unable to allocate buffer for erasing %s.", path.c_str());
656 CloseHandle(h);
657 return false;
658 }
659 memset(buffer, 0x00, ERASE_BLOCK);
660
662 if (size.QuadPart < (1024 - 42)) { // a small file can be stored in the MFT record
663 ovlp.Offset = 0;
664 ovlp.OffsetHigh = 0;
665 WriteFile(h, buffer, (DWORD) size.QuadPart, 0, &ovlp);
667 }
668 for (uint64_t i = 0; i < size_blocks; i++) {
669 uint64_t offset = i * ERASE_BLOCK;
670 ovlp.Offset = offset & 0x00000000FFFFFFFF;
671 ovlp.OffsetHigh = offset >> 32;
673 }
674
675 delete[] buffer;
676
677 if (dosync)
679
680 CloseHandle(h);
681 return true;
682}
683
684#else
685
686bool
687eraseFile_posix(const std::string& path, bool dosync)
688{
689 struct stat st;
690 if (stat(path.c_str(), &st) == -1) {
691 JAMI_WARN("Unable to erase file %s: fstat() failed.", path.c_str());
692 return false;
693 }
694 // Remove read-only flag if possible
695 chmod(path.c_str(), st.st_mode | (S_IWGRP + S_IWUSR));
696
697 int fd = open(path.c_str(), O_WRONLY);
698 if (fd == -1) {
699 JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
700 return false;
701 }
702
703 if (st.st_size == 0) {
704 close(fd);
705 return false;
706 }
707
708 lseek(fd, 0, SEEK_SET);
709
710 std::array<char, ERASE_BLOCK> buffer;
711 buffer.fill(0);
712 decltype(st.st_size) written(0);
713 while (written < st.st_size) {
714 auto ret = write(fd, buffer.data(), buffer.size());
715 if (ret < 0) {
716 JAMI_WARNING("Error while overriding file with zeros.");
717 break;
718 } else
719 written += ret;
720 }
721
722 if (dosync)
723 fsync(fd);
724
725 close(fd);
726 return written >= st.st_size;
727}
728#endif
729
730bool
731eraseFile(const std::string& path, bool dosync)
732{
733#ifdef _WIN32
734 return eraseFile_win32(path, dosync);
735#else
736 return eraseFile_posix(path, dosync);
737#endif
738}
739
740int
741remove(const std::filesystem::path& path, bool erase)
742{
743 if (erase and dhtnet::fileutils::isFile(path, false) and !dhtnet::fileutils::hasHardLink(path))
744 eraseFile(path.string(), true);
745
746#ifdef _WIN32
747 // use Win32 api since std::remove will not unlink directory in use
748 if (std::filesystem::is_directory(path))
749 return !RemoveDirectory(path.c_str());
750#endif
751
752 return std::remove(path.string().c_str());
753}
754
756size(const std::filesystem::path& path)
757{
758 int64_t size = 0;
759 try {
760 std::ifstream file(path, std::ios::binary | std::ios::in);
761 file.seekg(0, std::ios_base::end);
762 size = file.tellg();
763 file.close();
764 } catch (...) {
765 }
766 return size;
767}
768
769std::string
770sha3File(const std::filesystem::path& path)
771{
774
775 try {
776 if (not std::filesystem::is_regular_file(path)) {
777 JAMI_ERROR("Unable to compute sha3sum of {}: not a regular file", path);
778 return {};
779 }
780 std::ifstream file(path, std::ios::binary | std::ios::in);
781 if (!file) {
782 JAMI_ERROR("Unable to compute sha3sum of {}: failed to open file", path);
783 return {};
784 }
785 std::vector<char> buffer(8192, 0);
786 while (!file.eof()) {
787 file.read(buffer.data(), buffer.size());
788 std::streamsize readSize = file.gcount();
789 sha3_512_update(&ctx, readSize, (const uint8_t*) buffer.data());
790 }
791 } catch (const std::exception& e) {
792 JAMI_ERROR("Unable to compute sha3sum of {}: {}", path, e.what());
793 return {};
794 }
795
796 unsigned char digest[SHA3_512_DIGEST_SIZE];
798
799 char hash[SHA3_512_DIGEST_SIZE * 2];
800
801 for (int i = 0; i < SHA3_512_DIGEST_SIZE; ++i)
803
804 return {hash, SHA3_512_DIGEST_SIZE * 2};
805}
806
807std::string
808sha3sum(const uint8_t* data, size_t size)
809{
812 sha3_512_update(&ctx, size, data);
813 unsigned char digest[SHA3_512_DIGEST_SIZE];
815 return dht::toHex(digest, SHA3_512_DIGEST_SIZE);
816}
817
818int
819accessFile(const std::string& file, int mode)
820{
821#ifdef _WIN32
822 return _waccess(jami::to_wstring(file).c_str(), mode);
823#else
824 return access(file.c_str(), mode);
825#endif
826}
827
829lastWriteTimeInSeconds(const std::filesystem::path& filePath)
830{
831 std::error_code ec;
832 auto lastWrite = std::filesystem::last_write_time(filePath, ec);
833 if (ec) {
834 JAMI_WARNING("Unable to get last write time of {}: {}", filePath, ec.message());
835 return 0;
836 }
837 return std::chrono::duration_cast<std::chrono::seconds>(lastWrite.time_since_epoch()).count();
838}
839
840} // namespace fileutils
841} // 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:218
#define JAMI_ERROR(formatstr,...)
Definition logger.h:228
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_WARN(...)
Definition logger.h:217
#define JAMI_WARNING(formatstr,...)
Definition logger.h:227
#define JAMI_LOG(formatstr,...)
Definition logger.h:225
void compressGzip(const std::string &str, const std::string &path)
Compress string to a Gzip file.
Definition archiver.cpp:76
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:111
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:56
std::vector< uint8_t > decode(std::string_view str)
Definition base64.cpp:47
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:125
int accessFile(const std::string &file, int mode)
Windows compatibility wrapper for checking read-only attribute.
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:124
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.
int64_t size(const std::filesystem::path &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:126
bool isPathRelative(const std::filesystem::path &path)
std::string sha3sum(const uint8_t *data, size_t size)
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 ring_signal.h:64
std::string to_string(double value)
std::vector< uint8_t > salt
Definition fileutils.h:131
#define S_IWGRP
#define S_IWUSR
Definition windirent.h:110