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