Line data Source code
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
98 : const wchar_t*
99 : winGetEnv(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 :
120 : namespace jami {
121 : namespace fileutils {
122 :
123 : static std::filesystem::path resource_dir_path;
124 :
125 : void
126 0 : set_resource_dir_path(const std::filesystem::path& resourceDirPath)
127 : {
128 0 : resource_dir_path = resourceDirPath;
129 0 : }
130 :
131 : const std::filesystem::path&
132 671 : get_resource_dir_path()
133 : {
134 671 : static const std::filesystem::path jami_default_data_dir(JAMI_DATADIR);
135 671 : return resource_dir_path.empty() ? jami_default_data_dir : resource_dir_path;
136 : }
137 :
138 : std::string
139 5 : expand_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 5 : std::string result;
147 :
148 : wordexp_t p;
149 5 : int ret = wordexp(path.c_str(), &p, 0);
150 :
151 5 : switch (ret) {
152 0 : case WRDE_BADCHAR:
153 0 : JAMI_ERR("Illegal occurrence of newline or one of |, &, ;, <, >, "
154 : "(, ), {, }.");
155 0 : return result;
156 0 : case WRDE_BADVAL:
157 0 : JAMI_ERR("An undefined shell variable was referenced");
158 0 : return result;
159 0 : case WRDE_CMDSUB:
160 0 : JAMI_ERR("Command substitution occurred");
161 0 : return result;
162 0 : case WRDE_SYNTAX:
163 0 : JAMI_ERR("Shell syntax error");
164 0 : return result;
165 0 : case WRDE_NOSPACE:
166 0 : JAMI_ERR("Out of memory.");
167 : // This is the only error where we must call wordfree
168 0 : break;
169 5 : default:
170 5 : if (p.we_wordc > 0)
171 5 : result = std::string(p.we_wordv[0]);
172 5 : break;
173 : }
174 :
175 5 : wordfree(&p);
176 :
177 5 : return result;
178 : #endif
179 0 : }
180 :
181 : bool
182 7 : isDirectoryWritable(const std::string& directory)
183 : {
184 7 : return accessFile(directory, W_OK) == 0;
185 : }
186 :
187 : bool
188 14 : createSymlink(const std::filesystem::path& linkFile, const std::filesystem::path& target)
189 : {
190 14 : std::error_code ec;
191 14 : std::filesystem::create_symlink(target, linkFile, ec);
192 14 : if (ec) {
193 3 : JAMI_WARNING("Unable to create soft link from {} to {}: {}", linkFile, target, ec.message());
194 1 : return false;
195 : } else {
196 39 : JAMI_LOG("Created soft link from {} to {}", linkFile, target);
197 : }
198 13 : return true;
199 : }
200 :
201 : bool
202 13 : createHardlink(const std::filesystem::path& linkFile, const std::filesystem::path& target)
203 : {
204 13 : std::error_code ec;
205 13 : std::filesystem::create_hard_link(target, linkFile, ec);
206 13 : if (ec) {
207 0 : JAMI_WARNING("Unable to create hard link from {} to {}: {}", linkFile, target, ec.message());
208 0 : return false;
209 : } else {
210 39 : JAMI_LOG("Created hard link from {} to {}", linkFile, target);
211 : }
212 13 : return true;
213 : }
214 :
215 : bool
216 28 : createFileLink(const std::filesystem::path& linkFile, const std::filesystem::path& target, bool hard)
217 : {
218 28 : if (linkFile == target)
219 0 : return true;
220 28 : std::error_code ec;
221 28 : if (std::filesystem::exists(linkFile, ec)) {
222 4 : if (std::filesystem::is_symlink(linkFile, ec)
223 2 : && std::filesystem::read_symlink(linkFile, ec) == target)
224 1 : return true;
225 1 : std::filesystem::remove(linkFile, ec);
226 : }
227 27 : if (not hard or not createHardlink(linkFile, target))
228 14 : return createSymlink(linkFile, target);
229 13 : return true;
230 : }
231 :
232 : std::string_view
233 69 : getFileExtension(std::string_view filename)
234 : {
235 69 : std::string_view result;
236 69 : auto sep = filename.find_last_of('.');
237 69 : if (sep != std::string_view::npos && sep != filename.size() - 1)
238 0 : result = filename.substr(sep + 1);
239 69 : if (result.size() >= 8)
240 0 : return {};
241 69 : return result;
242 : }
243 :
244 : bool
245 7810 : isPathRelative(const std::filesystem::path& path)
246 : {
247 7810 : return not path.empty() and path.is_relative();
248 : }
249 :
250 : std::string
251 8259 : getCleanPath(const std::string& base, const std::string& path)
252 : {
253 8259 : if (base.empty() or path.size() < base.size())
254 8254 : return path;
255 5 : auto base_sep = base + DIR_SEPARATOR_STR;
256 5 : if (path.compare(0, base_sep.size(), base_sep) == 0)
257 5 : return path.substr(base_sep.size());
258 : else
259 0 : return path;
260 5 : }
261 :
262 : std::filesystem::path
263 12288 : getFullPath(const std::filesystem::path& base, const std::filesystem::path& path)
264 : {
265 12288 : bool isRelative {not base.empty() and isPathRelative(path)};
266 12288 : return isRelative ? base / path : path;
267 : }
268 :
269 : std::vector<uint8_t>
270 5811 : loadFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
271 : {
272 9224 : return dhtnet::fileutils::loadFile(getFullPath(default_dir, path));
273 : }
274 :
275 : std::string
276 9 : loadTextFile(const std::filesystem::path& path, const std::filesystem::path& default_dir)
277 : {
278 9 : std::string buffer;
279 9 : 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 9 : std::ifstream file(fullPath);
286 : #endif
287 :
288 9 : if (!file)
289 3 : throw std::runtime_error("Unable to read file: " + path.string());
290 :
291 6 : file.seekg(0, std::ios::end);
292 6 : auto size = file.tellg();
293 6 : if (size > std::numeric_limits<unsigned>::max())
294 0 : throw std::runtime_error("File is too big: " + path.string());
295 6 : buffer.resize(size);
296 6 : file.seekg(0, std::ios::beg);
297 6 : if (!file.read((char*) buffer.data(), size))
298 0 : throw std::runtime_error("Unable to load file: " + path.string());
299 12 : return buffer;
300 15 : }
301 :
302 : void
303 1285 : saveFile(const std::filesystem::path& path,
304 : const uint8_t* data,
305 : size_t data_size,
306 : mode_t UNUSED mode)
307 : {
308 1285 : std::ofstream file(path, std::ios::trunc | std::ios::binary);
309 1285 : if (!file.is_open()) {
310 6 : JAMI_ERROR("Unable to write data to {}", path);
311 2 : return;
312 : }
313 1283 : file.write((char*) data, data_size);
314 : #ifndef _WIN32
315 1283 : file.close();
316 1283 : if (chmod(path.c_str(), mode) < 0)
317 0 : JAMI_WARNING("fileutils::saveFile(): chmod() failed on {}, {}", path, strerror(errno));
318 : #endif
319 1285 : }
320 :
321 : std::vector<uint8_t>
322 0 : loadCacheFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
323 : {
324 : // last_write_time throws exception if file doesn't exist
325 0 : std::error_code ec;
326 0 : auto writeTime = std::filesystem::last_write_time(path, ec);
327 0 : if (ec)
328 0 : throw std::runtime_error("unable to get last write time of file");
329 0 : auto now = decltype(writeTime)::clock::now();
330 0 : if (now - writeTime > maxAge)
331 0 : throw std::runtime_error("file too old " + dht::print_time_relative(now, writeTime));
332 :
333 0 : JAMI_LOG("Loading cache file '{}'", path);
334 0 : return dhtnet::fileutils::loadFile(path);
335 : }
336 :
337 : std::string
338 0 : loadCacheTextFile(const std::filesystem::path& path, std::chrono::system_clock::duration maxAge)
339 : {
340 : // last_write_time throws exception if file doesn't exist
341 0 : std::error_code ec;
342 0 : auto writeTime = std::filesystem::last_write_time(path, ec);
343 0 : if (ec)
344 0 : throw std::runtime_error("unable to get last write time of file");
345 0 : auto now = decltype(writeTime)::clock::now();
346 0 : if (now - writeTime > maxAge)
347 0 : throw std::runtime_error("file too old " + dht::print_time_relative(now, writeTime));
348 :
349 0 : JAMI_LOG("Loading cache file '{}'", path);
350 0 : return loadTextFile(path);
351 : }
352 :
353 : ArchiveStorageData
354 88 : readArchive(const std::filesystem::path& path, std::string_view scheme, const std::string& pwd)
355 : {
356 264 : JAMI_LOG("Reading archive from {} with scheme '{}'", path, scheme);
357 :
358 162 : 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 162 : return data.size() > 3 && data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08;
365 : };
366 :
367 86 : auto decompress = [](std::vector<uint8_t>& data) {
368 : try {
369 86 : data = archiver::decompress(data);
370 0 : } catch (const std::exception& e) {
371 0 : JAMI_ERROR("Error decrypting archive: {}", e.what());
372 0 : throw e;
373 0 : }
374 86 : };
375 :
376 88 : std::vector<uint8_t> fileContent;
377 :
378 : // Read file
379 : try {
380 88 : fileContent = dhtnet::fileutils::loadFile(path);
381 0 : } catch (const std::exception& e) {
382 0 : JAMI_ERROR("Error loading archive: {}", e.what());
383 0 : throw;
384 0 : }
385 :
386 88 : if (isUnencryptedGzip(fileContent)) {
387 75 : if (!pwd.empty())
388 6 : JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
389 75 : decompress(fileContent);
390 : }
391 :
392 88 : ArchiveStorageData ret;
393 : // ret.data = {fileContent.data(), fileContent.data()+fileContent.size()};
394 :
395 88 : if (!pwd.empty()) {
396 : // Decrypt
397 14 : if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
398 : try {
399 0 : ret.salt = dht::crypto::aesGetSalt(fileContent);
400 0 : fileContent = dht::crypto::aesDecrypt(dht::crypto::aesGetEncrypted(fileContent),
401 0 : base64::decode(pwd));
402 0 : } catch (const std::exception& e) {
403 0 : JAMI_ERROR("Error decrypting archive: {}", e.what());
404 0 : throw;
405 0 : }
406 14 : } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD) {
407 : try {
408 14 : ret.salt = dht::crypto::aesGetSalt(fileContent);
409 14 : fileContent = dht::crypto::aesDecrypt(fileContent, pwd);
410 4 : } catch (const std::exception& e) {
411 12 : JAMI_ERROR("Error decrypting archive: {}", e.what());
412 4 : throw;
413 4 : }
414 : }
415 10 : decompress(fileContent);
416 74 : } else if (isUnencryptedGzip(fileContent)) {
417 3 : JAMI_WARNING("A gunzip in a gunzip is detected. A webserver may have a bad config");
418 1 : decompress(fileContent);
419 : }
420 84 : ret.data = {fileContent.data(), fileContent.data() + fileContent.size()};
421 168 : return ret;
422 92 : }
423 :
424 : bool
425 669 : writeArchive(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 2007 : JAMI_LOG("Writing archive to {} using scheme '{}'", path, scheme);
432 :
433 669 : if (scheme == ARCHIVE_AUTH_SCHEME_KEY) {
434 : // Encrypt using provided key
435 : try {
436 0 : auto key = base64::decode(password);
437 0 : auto newArchive = dht::crypto::aesEncrypt(archiver::compress(archive_str), key);
438 0 : saveFile(path, dht::crypto::aesBuildEncrypted(newArchive, password_salt));
439 0 : } catch (const std::runtime_error& ex) {
440 0 : JAMI_ERROR("Export failed: {}", ex.what());
441 0 : return false;
442 0 : }
443 669 : } else if (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD and not password.empty()) {
444 : // Encrypt using provided password
445 : try {
446 18 : saveFile(path,
447 36 : dht::crypto::aesEncrypt(archiver::compress(archive_str),
448 : password,
449 : password_salt));
450 0 : } catch (const std::runtime_error& ex) {
451 0 : JAMI_ERROR("Export failed: {}", ex.what());
452 0 : return false;
453 0 : }
454 651 : } else if (scheme == ARCHIVE_AUTH_SCHEME_NONE || (scheme == ARCHIVE_AUTH_SCHEME_PASSWORD && password.empty())) {
455 1953 : JAMI_WARNING("Unsecured archiving (no password)");
456 651 : archiver::compressGzip(archive_str, path.string());
457 : } else {
458 0 : JAMI_ERROR("Unsupported scheme: {}", scheme);
459 0 : return false;
460 : }
461 669 : return true;
462 : }
463 :
464 : std::filesystem::path
465 66 : get_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);
470 : emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("cache", &paths);
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())
480 : return jami::to_string(cache_home);
481 : #else
482 66 : const std::string cache_home(XDG_CACHE_HOME);
483 66 : if (not cache_home.empty())
484 0 : return cache_home;
485 : #endif
486 132 : return get_home_dir() / ".cache" / pkg;
487 : #endif
488 66 : }
489 :
490 : const std::filesystem::path&
491 2707 : get_cache_dir()
492 : {
493 2707 : static const std::filesystem::path cache_dir = get_cache_dir(PACKAGE);
494 2707 : return cache_dir;
495 : }
496 :
497 : std::filesystem::path
498 34 : get_home_dir_impl()
499 : {
500 : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
501 : std::vector<std::string> paths;
502 : paths.reserve(1);
503 : emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("files", &paths);
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 34 : std::string home(PROTECTED_GETENV("HOME"));
517 34 : if (not home.empty())
518 34 : return home;
519 :
520 : // 2) try getting it from getpwuid_r (i.e. /etc/passwd)
521 0 : const long max = sysconf(_SC_GETPW_R_SIZE_MAX);
522 0 : if (max != -1) {
523 0 : char buf[max];
524 : struct passwd pwbuf, *pw;
525 0 : if (getpwuid_r(getuid(), &pwbuf, buf, sizeof(buf), &pw) == 0 and pw != NULL)
526 0 : return pw->pw_dir;
527 0 : }
528 :
529 0 : return {};
530 : #endif
531 34 : }
532 :
533 : const std::filesystem::path&
534 201 : get_home_dir()
535 : {
536 201 : static const std::filesystem::path home_dir = get_home_dir_impl();
537 201 : return home_dir;
538 : }
539 :
540 : std::filesystem::path
541 66 : get_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);
546 : emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("files", &paths);
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 66 : std::string_view data_home(XDG_DATA_HOME);
564 66 : if (not data_home.empty())
565 0 : 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 132 : return get_home_dir() / ".local" / "share" / pkg;
569 : #endif
570 : }
571 :
572 : const std::filesystem::path&
573 65739 : get_data_dir()
574 : {
575 65739 : static const std::filesystem::path data_dir = get_data_dir(PACKAGE);
576 65739 : return data_dir;
577 : }
578 :
579 : std::filesystem::path
580 67 : get_config_dir(const char* pkg)
581 : {
582 67 : std::filesystem::path configdir;
583 : #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS)
584 : std::vector<std::string> paths;
585 : emitSignal<libjami::ConfigurationSignal::GetAppDataPath>("config", &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 67 : std::string xdg_env(XDG_CONFIG_HOME);
601 67 : if (not xdg_env.empty())
602 0 : configdir = std::filesystem::path(xdg_env) / pkg;
603 : else
604 67 : configdir = fileutils::get_home_dir() / ".config" / pkg;
605 : #endif
606 67 : if (!dhtnet::fileutils::recursive_mkdir(configdir, 0700)) {
607 : // If directory creation failed
608 0 : if (errno != EEXIST)
609 0 : JAMI_DBG("Unable to create directory: %s!", configdir.c_str());
610 : }
611 134 : return configdir;
612 67 : }
613 :
614 : const std::filesystem::path&
615 283 : get_config_dir()
616 : {
617 283 : static const std::filesystem::path config_dir = get_config_dir(PACKAGE);
618 283 : return config_dir;
619 : }
620 :
621 : #ifdef _WIN32
622 : bool
623 : eraseFile_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
630 : = CreateFileA(path.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
631 : if (h == INVALID_HANDLE_VALUE) {
632 : JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
633 : return false;
634 : }
635 :
636 : LARGE_INTEGER size;
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 :
647 : uint64_t size_blocks = size.QuadPart / ERASE_BLOCK;
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 :
661 : OVERLAPPED ovlp;
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);
666 : FlushFileBuffers(h);
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;
672 : WriteFile(h, buffer, ERASE_BLOCK, 0, &ovlp);
673 : }
674 :
675 : delete[] buffer;
676 :
677 : if (dosync)
678 : FlushFileBuffers(h);
679 :
680 : CloseHandle(h);
681 : return true;
682 : }
683 :
684 : #else
685 :
686 : bool
687 0 : eraseFile_posix(const std::string& path, bool dosync)
688 : {
689 : struct stat st;
690 0 : if (stat(path.c_str(), &st) == -1) {
691 0 : JAMI_WARN("Unable to erase file %s: fstat() failed.", path.c_str());
692 0 : return false;
693 : }
694 : // Remove read-only flag if possible
695 0 : chmod(path.c_str(), st.st_mode | (S_IWGRP + S_IWUSR));
696 :
697 0 : int fd = open(path.c_str(), O_WRONLY);
698 0 : if (fd == -1) {
699 0 : JAMI_WARN("Unable to open file %s for erasing.", path.c_str());
700 0 : return false;
701 : }
702 :
703 0 : if (st.st_size == 0) {
704 0 : close(fd);
705 0 : return false;
706 : }
707 :
708 0 : lseek(fd, 0, SEEK_SET);
709 :
710 : std::array<char, ERASE_BLOCK> buffer;
711 0 : buffer.fill(0);
712 0 : decltype(st.st_size) written(0);
713 0 : while (written < st.st_size) {
714 0 : auto ret = write(fd, buffer.data(), buffer.size());
715 0 : if (ret < 0) {
716 0 : JAMI_WARNING("Error while overriding file with zeros.");
717 0 : break;
718 : } else
719 0 : written += ret;
720 : }
721 :
722 0 : if (dosync)
723 0 : fsync(fd);
724 :
725 0 : close(fd);
726 0 : return written >= st.st_size;
727 : }
728 : #endif
729 :
730 : bool
731 0 : eraseFile(const std::string& path, bool dosync)
732 : {
733 : #ifdef _WIN32
734 : return eraseFile_win32(path, dosync);
735 : #else
736 0 : return eraseFile_posix(path, dosync);
737 : #endif
738 : }
739 :
740 : int
741 0 : remove(const std::filesystem::path& path, bool erase)
742 : {
743 0 : if (erase and dhtnet::fileutils::isFile(path, false) and !dhtnet::fileutils::hasHardLink(path))
744 0 : 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 0 : return std::remove(path.string().c_str());
753 : }
754 :
755 : int64_t
756 42 : size(const std::filesystem::path& path)
757 : {
758 42 : int64_t size = 0;
759 : try {
760 42 : std::ifstream file(path, std::ios::binary | std::ios::in);
761 42 : file.seekg(0, std::ios_base::end);
762 42 : size = file.tellg();
763 42 : file.close();
764 42 : } catch (...) {
765 0 : }
766 42 : return size;
767 : }
768 :
769 : std::string
770 51 : sha3File(const std::filesystem::path& path)
771 : {
772 : sha3_512_ctx ctx;
773 51 : sha3_512_init(&ctx);
774 :
775 : try {
776 51 : if (not std::filesystem::is_regular_file(path)) {
777 0 : JAMI_ERROR("Unable to compute sha3sum of {}: not a regular file", path);
778 0 : return {};
779 : }
780 51 : std::ifstream file(path, std::ios::binary | std::ios::in);
781 51 : if (!file) {
782 0 : JAMI_ERROR("Unable to compute sha3sum of {}: failed to open file", path);
783 0 : return {};
784 : }
785 51 : std::vector<char> buffer(8192, 0);
786 6945 : while (!file.eof()) {
787 6894 : file.read(buffer.data(), buffer.size());
788 6894 : std::streamsize readSize = file.gcount();
789 6894 : sha3_512_update(&ctx, readSize, (const uint8_t*) buffer.data());
790 : }
791 51 : } catch (const std::exception& e) {
792 0 : JAMI_ERROR("Unable to compute sha3sum of {}: {}", path, e.what());
793 0 : return {};
794 0 : }
795 :
796 : unsigned char digest[SHA3_512_DIGEST_SIZE];
797 51 : sha3_512_digest(&ctx, SHA3_512_DIGEST_SIZE, digest);
798 :
799 : char hash[SHA3_512_DIGEST_SIZE * 2];
800 :
801 3315 : for (int i = 0; i < SHA3_512_DIGEST_SIZE; ++i)
802 3264 : pj_val_to_hex_digit(digest[i], &hash[2 * i]);
803 :
804 51 : return {hash, SHA3_512_DIGEST_SIZE * 2};
805 : }
806 :
807 : std::string
808 0 : sha3sum(const uint8_t* data, size_t size)
809 : {
810 : sha3_512_ctx ctx;
811 0 : sha3_512_init(&ctx);
812 0 : sha3_512_update(&ctx, size, data);
813 : unsigned char digest[SHA3_512_DIGEST_SIZE];
814 0 : sha3_512_digest(&ctx, SHA3_512_DIGEST_SIZE, digest);
815 0 : return dht::toHex(digest, SHA3_512_DIGEST_SIZE);
816 : }
817 :
818 : int
819 7 : accessFile(const std::string& file, int mode)
820 : {
821 : #ifdef _WIN32
822 : return _waccess(jami::to_wstring(file).c_str(), mode);
823 : #else
824 7 : return access(file.c_str(), mode);
825 : #endif
826 : }
827 :
828 : uint64_t
829 11 : lastWriteTimeInSeconds(const std::filesystem::path& filePath)
830 : {
831 11 : std::error_code ec;
832 11 : auto lastWrite = std::filesystem::last_write_time(filePath, ec);
833 11 : if (ec) {
834 6 : JAMI_WARNING("Unable to get last write time of {}: {}", filePath, ec.message());
835 2 : return 0;
836 : }
837 9 : return std::chrono::duration_cast<std::chrono::seconds>(lastWrite.time_since_epoch()).count();
838 : }
839 :
840 : } // namespace fileutils
841 : } // namespace jami
|