blob: be911a6f155aa976bbccbac0839b7df9f107af23 [file] [log] [blame]
Adrien Béraud612b55b2023-05-29 10:42:04 -04001/*
2 * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
3 *
4 * Author: Rafaël Carré <rafael.carre@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21//#include "logger.h"
22#include "fileutils.h"
23//#include "archiver.h"
24//#include "compiler_intrinsics.h"
25#include <opendht/crypto.h>
26
27#ifdef RING_UWP
28#include <io.h> // for access and close
29#include "ring_signal.h"
30#endif
31
32#ifdef __APPLE__
33#include <TargetConditionals.h>
34#endif
35
36#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
37#include "client/ring_signal.h"
38#endif
39
40#ifdef _WIN32
41#include <windows.h>
42#include "string_utils.h"
43#endif
44
45#include <sys/types.h>
46#include <sys/stat.h>
47
48#ifndef _MSC_VER
49#include <libgen.h>
50#endif
51
52#ifdef _MSC_VER
53#include "windirent.h"
54#else
55#include <dirent.h>
56#endif
57
58#include <signal.h>
59#include <unistd.h>
60#include <fcntl.h>
61#ifndef _WIN32
62#include <pwd.h>
63#else
64#include <shlobj.h>
65#define NAME_MAX 255
66#endif
67#if !defined __ANDROID__ && !defined _WIN32
68#include <wordexp.h>
69#endif
70
71#include <nettle/sha3.h>
72
73#include <sstream>
74#include <fstream>
75#include <iostream>
76#include <stdexcept>
77#include <limits>
78#include <array>
79
80#include <cstdlib>
81#include <cstring>
82#include <cerrno>
83#include <cstddef>
84#include <ciso646>
85
86#include <pj/ctype.h>
87#include <pjlib-util/md5.h>
88
89#include <filesystem>
90
91#define PIDFILE ".ring.pid"
92#define ERASE_BLOCK 4096
93
94namespace jami {
95namespace fileutils {
96
97// returns true if directory exists
98bool
99check_dir(const char* path, [[maybe_unused]] mode_t dirmode, mode_t parentmode)
100{
101 DIR* dir = opendir(path);
102
103 if (!dir) { // doesn't exist
104 if (not recursive_mkdir(path, parentmode)) {
105 perror(path);
106 return false;
107 }
108#ifndef _WIN32
109 if (chmod(path, dirmode) < 0) {
110 //JAMI_ERR("fileutils::check_dir(): chmod() failed on '%s', %s", path, strerror(errno));
111 return false;
112 }
113#endif
114 } else
115 closedir(dir);
116 return true;
117}
118
119std::string
120expand_path(const std::string& path)
121{
122#if defined __ANDROID__ || defined _MSC_VER || defined WIN32 || defined __APPLE__
123 //JAMI_ERR("Path expansion not implemented, returning original");
124 return path;
125#else
126
127 std::string result;
128
129 wordexp_t p;
130 int ret = wordexp(path.c_str(), &p, 0);
131
132 switch (ret) {
133 case WRDE_BADCHAR:
134 JAMI_ERR("Illegal occurrence of newline or one of |, &, ;, <, >, "
135 "(, ), {, }.");
136 return result;
137 case WRDE_BADVAL:
138 JAMI_ERR("An undefined shell variable was referenced");
139 return result;
140 case WRDE_CMDSUB:
141 JAMI_ERR("Command substitution occurred");
142 return result;
143 case WRDE_SYNTAX:
144 JAMI_ERR("Shell syntax error");
145 return result;
146 case WRDE_NOSPACE:
147 JAMI_ERR("Out of memory.");
148 // This is the only error where we must call wordfree
149 break;
150 default:
151 if (p.we_wordc > 0)
152 result = std::string(p.we_wordv[0]);
153 break;
154 }
155
156 wordfree(&p);
157
158 return result;
159#endif
160}
161
162std::mutex&
163getFileLock(const std::string& path)
164{
165 static std::mutex fileLockLock {};
166 static std::map<std::string, std::mutex> fileLocks {};
167
168 std::lock_guard<std::mutex> l(fileLockLock);
169 return fileLocks[path];
170}
171
172bool
173isFile(const std::string& path, bool resolveSymlink)
174{
175 if (path.empty())
176 return false;
177#ifdef _WIN32
178 if (resolveSymlink) {
179 struct _stat64i32 s;
180 if (_wstat(jami::to_wstring(path).c_str(), &s) == 0)
181 return S_ISREG(s.st_mode);
182 } else {
183 DWORD attr = GetFileAttributes(jami::to_wstring(path).c_str());
184 if ((attr != INVALID_FILE_ATTRIBUTES) && !(attr & FILE_ATTRIBUTE_DIRECTORY)
185 && !(attr & FILE_ATTRIBUTE_REPARSE_POINT))
186 return true;
187 }
188#else
189 if (resolveSymlink) {
190 struct stat s;
191 if (stat(path.c_str(), &s) == 0)
192 return S_ISREG(s.st_mode);
193 } else {
194 struct stat s;
195 if (lstat(path.c_str(), &s) == 0)
196 return S_ISREG(s.st_mode);
197 }
198#endif
199
200 return false;
201}
202
203bool
204isDirectory(const std::string& path)
205{
206 struct stat s;
207 if (stat(path.c_str(), &s) == 0)
208 return s.st_mode & S_IFDIR;
209 return false;
210}
211
212bool
213isDirectoryWritable(const std::string& directory)
214{
215 return accessFile(directory, W_OK) == 0;
216}
217
218bool
219hasHardLink(const std::string& path)
220{
221#ifndef _WIN32
222 struct stat s;
223 if (lstat(path.c_str(), &s) == 0)
224 return s.st_nlink > 1;
225#endif
226 return false;
227}
228
229bool
230isSymLink(const std::string& path)
231{
232#ifndef _WIN32
233 struct stat s;
234 if (lstat(path.c_str(), &s) == 0)
235 return S_ISLNK(s.st_mode);
236#elif !defined(_MSC_VER)
237 DWORD attr = GetFileAttributes(jami::to_wstring(path).c_str());
238 if (attr & FILE_ATTRIBUTE_REPARSE_POINT)
239 return true;
240#endif
241 return false;
242}
243
244std::chrono::system_clock::time_point
245writeTime(const std::string& path)
246{
247#ifndef _WIN32
248 struct stat s;
249 auto ret = stat(path.c_str(), &s);
250 if (ret)
251 throw std::runtime_error("Can't check write time for: " + path);
252 return std::chrono::system_clock::from_time_t(s.st_mtime);
253#else
254#if RING_UWP
255 _CREATEFILE2_EXTENDED_PARAMETERS ext_params = {0};
256 ext_params.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
257 ext_params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
258 ext_params.dwFileFlags = FILE_FLAG_NO_BUFFERING;
259 ext_params.dwSecurityQosFlags = SECURITY_ANONYMOUS;
260 ext_params.lpSecurityAttributes = nullptr;
261 ext_params.hTemplateFile = nullptr;
262 HANDLE h = CreateFile2(jami::to_wstring(path).c_str(),
263 GENERIC_READ,
264 FILE_SHARE_READ,
265 OPEN_EXISTING,
266 &ext_params);
267#elif _WIN32
268 HANDLE h = CreateFileW(jami::to_wstring(path).c_str(),
269 GENERIC_READ,
270 FILE_SHARE_READ,
271 nullptr,
272 OPEN_EXISTING,
273 FILE_ATTRIBUTE_NORMAL,
274 nullptr);
275#endif
276 if (h == INVALID_HANDLE_VALUE)
277 throw std::runtime_error("Can't open: " + path);
278 FILETIME lastWriteTime;
279 if (!GetFileTime(h, nullptr, nullptr, &lastWriteTime))
280 throw std::runtime_error("Can't check write time for: " + path);
281 CloseHandle(h);
282 SYSTEMTIME sTime;
283 if (!FileTimeToSystemTime(&lastWriteTime, &sTime))
284 throw std::runtime_error("Can't check write time for: " + path);
285 struct tm tm
286 {};
287 tm.tm_year = sTime.wYear - 1900;
288 tm.tm_mon = sTime.wMonth - 1;
289 tm.tm_mday = sTime.wDay;
290 tm.tm_hour = sTime.wHour;
291 tm.tm_min = sTime.wMinute;
292 tm.tm_sec = sTime.wSecond;
293 tm.tm_isdst = -1;
294 return std::chrono::system_clock::from_time_t(mktime(&tm));
295#endif
296}
297
298bool
299createSymlink(const std::string& linkFile, const std::string& target)
300{
301 try {
302 std::filesystem::create_symlink(target, linkFile);
303 } catch (const std::exception& e) {
304 //JAMI_ERR("Couldn't create soft link: %s", e.what());
305 return false;
306 }
307 return true;
308}
309
310bool
311createHardlink(const std::string& linkFile, const std::string& target)
312{
313 try {
314 std::filesystem::create_hard_link(target, linkFile);
315 } catch (const std::exception& e) {
316 //JAMI_ERR("Couldn't create hard link: %s", e.what());
317 return false;
318 }
319 return true;
320}
321
322void
323createFileLink(const std::string& linkFile, const std::string& target, bool hard)
324{
325 if (not hard or not createHardlink(linkFile, target))
326 createSymlink(linkFile, target);
327}
328
329std::string_view
330getFileExtension(std::string_view filename)
331{
332 std::string_view result;
333 auto sep = filename.find_last_of('.');
334 if (sep != std::string_view::npos && sep != filename.size() - 1)
335 result = filename.substr(sep + 1);
336 if (result.size() >= 8)
337 return {};
338 return result;
339}
340
341bool
342isPathRelative(const std::string& path)
343{
344#ifndef _WIN32
345 return not path.empty() and not(path[0] == '/');
346#else
347 return not path.empty() and path.find(":") == std::string::npos;
348#endif
349}
350
351std::string
352getCleanPath(const std::string& base, const std::string& path)
353{
354 if (base.empty() or path.size() < base.size())
355 return path;
356 auto base_sep = base + DIR_SEPARATOR_STR;
357 if (path.compare(0, base_sep.size(), base_sep) == 0)
358 return path.substr(base_sep.size());
359 else
360 return path;
361}
362
363std::string
364getFullPath(const std::string& base, const std::string& path)
365{
366 bool isRelative {not base.empty() and isPathRelative(path)};
367 return isRelative ? base + DIR_SEPARATOR_STR + path : path;
368}
369
370std::vector<uint8_t>
371loadFile(const std::string& path, const std::string& default_dir)
372{
373 std::vector<uint8_t> buffer;
374 std::ifstream file = ifstream(getFullPath(default_dir, path), std::ios::binary);
375 if (!file)
376 throw std::runtime_error("Can't read file: " + path);
377 file.seekg(0, std::ios::end);
378 auto size = file.tellg();
379 if (size > std::numeric_limits<unsigned>::max())
380 throw std::runtime_error("File is too big: " + path);
381 buffer.resize(size);
382 file.seekg(0, std::ios::beg);
383 if (!file.read((char*) buffer.data(), size))
384 throw std::runtime_error("Can't load file: " + path);
385 return buffer;
386}
387
388std::string
389loadTextFile(const std::string& path, const std::string& default_dir)
390{
391 std::string buffer;
392 std::ifstream file = ifstream(getFullPath(default_dir, path));
393 if (!file)
394 throw std::runtime_error("Can't read file: " + path);
395 file.seekg(0, std::ios::end);
396 auto size = file.tellg();
397 if (size > std::numeric_limits<unsigned>::max())
398 throw std::runtime_error("File is too big: " + path);
399 buffer.resize(size);
400 file.seekg(0, std::ios::beg);
401 if (!file.read((char*) buffer.data(), size))
402 throw std::runtime_error("Can't load file: " + path);
403 return buffer;
404}
405
406void
407saveFile(const std::string& path, const uint8_t* data, size_t data_size, [[maybe_unused]] mode_t mode)
408{
409 std::ofstream file = fileutils::ofstream(path, std::ios::trunc | std::ios::binary);
410 if (!file.is_open()) {
411 //JAMI_ERR("Could not write data to %s", path.c_str());
412 return;
413 }
414 file.write((char*) data, data_size);
415#ifndef _WIN32
416 if (chmod(path.c_str(), mode) < 0)
417 /*JAMI_WARN("fileutils::saveFile(): chmod() failed on '%s', %s",
418 path.c_str(),
419 strerror(errno))*/;
420#endif
421}
422
423std::vector<uint8_t>
424loadCacheFile(const std::string& path, std::chrono::system_clock::duration maxAge)
425{
426 // writeTime throws exception if file doesn't exist
427 auto duration = std::chrono::system_clock::now() - writeTime(path);
428 if (duration > maxAge)
429 throw std::runtime_error("file too old");
430
431 //JAMI_DBG("Loading cache file '%.*s'", (int) path.size(), path.c_str());
432 return loadFile(path);
433}
434
435std::string
436loadCacheTextFile(const std::string& path, std::chrono::system_clock::duration maxAge)
437{
438 // writeTime throws exception if file doesn't exist
439 auto duration = std::chrono::system_clock::now() - writeTime(path);
440 if (duration > maxAge)
441 throw std::runtime_error("file too old");
442
443 //JAMI_DBG("Loading cache file '%.*s'", (int) path.size(), path.c_str());
444 return loadTextFile(path);
445}
446
447static size_t
448dirent_buf_size([[maybe_unused]] DIR* dirp)
449{
450 long name_max;
451#if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD) && defined(_PC_NAME_MAX)
452 name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
453 if (name_max == -1)
454#if defined(NAME_MAX)
455 name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
456#else
457 return (size_t) (-1);
458#endif
459#else
460#if defined(NAME_MAX)
461 name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
462#else
463#error "buffer size for readdir_r cannot be determined"
464#endif
465#endif
466 size_t name_end = (size_t) offsetof(struct dirent, d_name) + name_max + 1;
467 return name_end > sizeof(struct dirent) ? name_end : sizeof(struct dirent);
468}
469
470std::vector<std::string>
471readDirectory(const std::string& dir)
472{
473 DIR* dp = opendir(dir.c_str());
474 if (!dp)
475 return {};
476
477 size_t size = dirent_buf_size(dp);
478 if (size == (size_t) (-1))
479 return {};
480 std::vector<uint8_t> buf(size);
481 dirent* entry;
482
483 std::vector<std::string> files;
484#ifndef _WIN32
485 while (!readdir_r(dp, reinterpret_cast<dirent*>(buf.data()), &entry) && entry) {
486#else
487 while ((entry = readdir(dp)) != nullptr) {
488#endif
489 std::string fname {entry->d_name};
490 if (fname == "." || fname == "..")
491 continue;
492 files.emplace_back(std::move(fname));
493 }
494 closedir(dp);
495 return files;
496} // namespace fileutils
497
498/*
499std::vector<uint8_t>
500readArchive(const std::string& path, const std::string& pwd)
501{
502 JAMI_DBG("Reading archive from %s", path.c_str());
503
504 auto isUnencryptedGzip = [](const std::vector<uint8_t>& data) {
505 // NOTE: some webserver modify gzip files and this can end with a gunzip in a gunzip
506 // file. So, to make the readArchive more robust, we can support this case by detecting
507 // gzip header via 1f8b 08
508 // We don't need to support more than 2 level, else somebody may be able to send
509 // gunzip in loops and abuse.
510 return data.size() > 3 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08;
511 };
512
513 auto decompress = [](std::vector<uint8_t>& data) {
514 try {
515 data = archiver::decompress(data);
516 } catch (const std::exception& e) {
517 JAMI_ERR("Error decrypting archive: %s", e.what());
518 throw e;
519 }
520 };
521
522 std::vector<uint8_t> data;
523 // Read file
524 try {
525 data = loadFile(path);
526 } catch (const std::exception& e) {
527 JAMI_ERR("Error loading archive: %s", e.what());
528 throw e;
529 }
530
531 if (isUnencryptedGzip(data)) {
532 if (!pwd.empty())
533 JAMI_WARN() << "A gunzip in a gunzip is detected. A webserver may have a bad config";
534
535 decompress(data);
536 }
537
538 if (!pwd.empty()) {
539 // Decrypt
540 try {
541 data = dht::crypto::aesDecrypt(data, pwd);
542 } catch (const std::exception& e) {
543 JAMI_ERR("Error decrypting archive: %s", e.what());
544 throw e;
545 }
546 decompress(data);
547 } else if (isUnencryptedGzip(data)) {
548 JAMI_WARN() << "A gunzip in a gunzip is detected. A webserver may have a bad config";
549 decompress(data);
550 }
551 return data;
552}
553
554void
555writeArchive(const std::string& archive_str, const std::string& path, const std::string& password)
556{
557 JAMI_DBG("Writing archive to %s", path.c_str());
558
559 if (not password.empty()) {
560 // Encrypt using provided password
561 std::vector<uint8_t> data = dht::crypto::aesEncrypt(archiver::compress(archive_str),
562 password);
563 // Write
564 try {
565 saveFile(path, data);
566 } catch (const std::runtime_error& ex) {
567 JAMI_ERR("Export failed: %s", ex.what());
568 return;
569 }
570 } else {
571 JAMI_WARN("Unsecured archiving (no password)");
572 archiver::compressGzip(archive_str, path);
573 }
574}*/
575
576bool
577recursive_mkdir(const std::string& path, mode_t mode)
578{
579#ifndef _WIN32
580 if (mkdir(path.data(), mode) != 0) {
581#else
582 if (_wmkdir(jami::to_wstring(path.data()).c_str()) != 0) {
583#endif
584 if (errno == ENOENT) {
585 recursive_mkdir(path.substr(0, path.find_last_of(DIR_SEPARATOR_CH)), mode);
586#ifndef _WIN32
587 if (mkdir(path.data(), mode) != 0) {
588#else
589 if (_wmkdir(jami::to_wstring(path.data()).c_str()) != 0) {
590#endif
591 //JAMI_ERR("Could not create directory.");
592 return false;
593 }
594 }
595 } // namespace jami
596 return true;
597}
598
599#ifdef _WIN32
600bool
601eraseFile_win32(const std::string& path, bool dosync)
602{
603 HANDLE h
604 = CreateFileA(path.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
605 if (h == INVALID_HANDLE_VALUE) {
606 JAMI_WARN("Can not open file %s for erasing.", path.c_str());
607 return false;
608 }
609
610 LARGE_INTEGER size;
611 if (!GetFileSizeEx(h, &size)) {
612 JAMI_WARN("Can not erase file %s: GetFileSizeEx() failed.", path.c_str());
613 CloseHandle(h);
614 return false;
615 }
616 if (size.QuadPart == 0) {
617 CloseHandle(h);
618 return false;
619 }
620
621 uint64_t size_blocks = size.QuadPart / ERASE_BLOCK;
622 if (size.QuadPart % ERASE_BLOCK)
623 size_blocks++;
624
625 char* buffer;
626 try {
627 buffer = new char[ERASE_BLOCK];
628 } catch (std::bad_alloc& ba) {
629 JAMI_WARN("Can not allocate buffer for erasing %s.", path.c_str());
630 CloseHandle(h);
631 return false;
632 }
633 memset(buffer, 0x00, ERASE_BLOCK);
634
635 OVERLAPPED ovlp;
636 if (size.QuadPart < (1024 - 42)) { // a small file can be stored in the MFT record
637 ovlp.Offset = 0;
638 ovlp.OffsetHigh = 0;
639 WriteFile(h, buffer, (DWORD) size.QuadPart, 0, &ovlp);
640 FlushFileBuffers(h);
641 }
642 for (uint64_t i = 0; i < size_blocks; i++) {
643 uint64_t offset = i * ERASE_BLOCK;
644 ovlp.Offset = offset & 0x00000000FFFFFFFF;
645 ovlp.OffsetHigh = offset >> 32;
646 WriteFile(h, buffer, ERASE_BLOCK, 0, &ovlp);
647 }
648
649 delete[] buffer;
650
651 if (dosync)
652 FlushFileBuffers(h);
653
654 CloseHandle(h);
655 return true;
656}
657
658#else
659
660bool
661eraseFile_posix(const std::string& path, bool dosync)
662{
663 struct stat st;
664 if (stat(path.c_str(), &st) == -1) {
665 //JAMI_WARN("Can not erase file %s: fstat() failed.", path.c_str());
666 return false;
667 }
668 // Remove read-only flag if possible
669 chmod(path.c_str(), st.st_mode | (S_IWGRP+S_IWUSR) );
670
671 int fd = open(path.c_str(), O_WRONLY);
672 if (fd == -1) {
673 //JAMI_WARN("Can not open file %s for erasing.", path.c_str());
674 return false;
675 }
676
677 if (st.st_size == 0) {
678 close(fd);
679 return false;
680 }
681
682 lseek(fd, 0, SEEK_SET);
683
684 std::array<char, ERASE_BLOCK> buffer;
685 buffer.fill(0);
686 decltype(st.st_size) written(0);
687 while (written < st.st_size) {
688 auto ret = write(fd, buffer.data(), buffer.size());
689 if (ret < 0) {
690 //JAMI_WARNING("Error while overriding file with zeros.");
691 break;
692 } else
693 written += ret;
694 }
695
696 if (dosync)
697 fsync(fd);
698
699 close(fd);
700 return written >= st.st_size;
701}
702#endif
703
704bool
705eraseFile(const std::string& path, bool dosync)
706{
707#ifdef _WIN32
708 return eraseFile_win32(path, dosync);
709#else
710 return eraseFile_posix(path, dosync);
711#endif
712}
713
714int
715remove(const std::string& path, bool erase)
716{
717 if (erase and isFile(path, false) and !hasHardLink(path))
718 eraseFile(path, true);
719
720#ifdef _WIN32
721 // use Win32 api since std::remove will not unlink directory in use
722 if (isDirectory(path))
723 return !RemoveDirectory(jami::to_wstring(path).c_str());
724#endif
725
726 return std::remove(path.c_str());
727}
728
729int
730removeAll(const std::string& path, bool erase)
731{
732 if (path.empty())
733 return -1;
734 if (isDirectory(path) and !isSymLink(path)) {
735 auto dir = path;
736 if (dir.back() != DIR_SEPARATOR_CH)
737 dir += DIR_SEPARATOR_CH;
738 for (auto& entry : fileutils::readDirectory(dir))
739 removeAll(dir + entry, erase);
740 }
741 return remove(path, erase);
742}
743
744void
745openStream(std::ifstream& file, const std::string& path, std::ios_base::openmode mode)
746{
747#ifdef _WIN32
748 file.open(jami::to_wstring(path), mode);
749#else
750 file.open(path, mode);
751#endif
752}
753
754void
755openStream(std::ofstream& file, const std::string& path, std::ios_base::openmode mode)
756{
757#ifdef _WIN32
758 file.open(jami::to_wstring(path), mode);
759#else
760 file.open(path, mode);
761#endif
762}
763
764std::ifstream
765ifstream(const std::string& path, std::ios_base::openmode mode)
766{
767#ifdef _WIN32
768 return std::ifstream(jami::to_wstring(path), mode);
769#else
770 return std::ifstream(path, mode);
771#endif
772}
773
774std::ofstream
775ofstream(const std::string& path, std::ios_base::openmode mode)
776{
777#ifdef _WIN32
778 return std::ofstream(jami::to_wstring(path), mode);
779#else
780 return std::ofstream(path, mode);
781#endif
782}
783
784int64_t
785size(const std::string& path)
786{
787 int64_t size = 0;
788 try {
789 std::ifstream file;
790 openStream(file, path, std::ios::binary | std::ios::in);
791 file.seekg(0, std::ios_base::end);
792 size = file.tellg();
793 file.close();
794 } catch (...) {
795 }
796 return size;
797}
798
799std::string
800sha3File(const std::string& path)
801{
802 sha3_512_ctx ctx;
803 sha3_512_init(&ctx);
804
805 std::ifstream file;
806 try {
807 if (!fileutils::isFile(path))
808 return {};
809 openStream(file, path, std::ios::binary | std::ios::in);
810 if (!file)
811 return {};
812 std::vector<char> buffer(8192, 0);
813 while (!file.eof()) {
814 file.read(buffer.data(), buffer.size());
815 std::streamsize readSize = file.gcount();
816 sha3_512_update(&ctx, readSize, (const uint8_t*) buffer.data());
817 }
818 file.close();
819 } catch (...) {
820 return {};
821 }
822
823 unsigned char digest[SHA3_512_DIGEST_SIZE];
824 sha3_512_digest(&ctx, SHA3_512_DIGEST_SIZE, digest);
825
826 char hash[SHA3_512_DIGEST_SIZE * 2];
827
828 for (int i = 0; i < SHA3_512_DIGEST_SIZE; ++i)
829 pj_val_to_hex_digit(digest[i], &hash[2 * i]);
830
831 return {hash, SHA3_512_DIGEST_SIZE * 2};
832}
833
834std::string
835sha3sum(const std::vector<uint8_t>& buffer)
836{
837 sha3_512_ctx ctx;
838 sha3_512_init(&ctx);
839 sha3_512_update(&ctx, buffer.size(), (const uint8_t*) buffer.data());
840
841 unsigned char digest[SHA3_512_DIGEST_SIZE];
842 sha3_512_digest(&ctx, SHA3_512_DIGEST_SIZE, digest);
843
844 char hash[SHA3_512_DIGEST_SIZE * 2];
845
846 for (int i = 0; i < SHA3_512_DIGEST_SIZE; ++i)
847 pj_val_to_hex_digit(digest[i], &hash[2 * i]);
848
849 return {hash, SHA3_512_DIGEST_SIZE * 2};
850}
851
852int
853accessFile(const std::string& file, int mode)
854{
855#ifdef _WIN32
856 return _waccess(jami::to_wstring(file).c_str(), mode);
857#else
858 return access(file.c_str(), mode);
859#endif
860}
861
862uint64_t
863lastWriteTime(const std::string& p)
864{
865#if USE_STD_FILESYSTEM
866 return std::chrono::duration_cast<std::chrono::milliseconds>(
867 std::filesystem::last_write_time(std::filesystem::path(p)).time_since_epoch())
868 .count();
869#else
870 struct stat result;
871 if (stat(p.c_str(), &result) == 0)
872 return result.st_mtime;
873 return 0;
874#endif
875}
876
877} // namespace fileutils
878} // namespace jami