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