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