blob: 5bd085930188c530018bd5a4ee073d0dcedf5454 [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éraud2a4e73d2023-08-27 12:53:55 -040018
Adrien Béraud612b55b2023-05-29 10:42:04 -040019#include <opendht/crypto.h>
20
21#ifdef RING_UWP
22#include <io.h> // for access and close
Adrien Béraud612b55b2023-05-29 10:42:04 -040023#endif
24
25#ifdef __APPLE__
26#include <TargetConditionals.h>
27#endif
28
Adrien Béraud612b55b2023-05-29 10:42:04 -040029#ifdef _WIN32
30#include <windows.h>
31#include "string_utils.h"
32#endif
33
34#include <sys/types.h>
35#include <sys/stat.h>
Adrien Béraud612b55b2023-05-29 10:42:04 -040036#ifndef _MSC_VER
37#include <libgen.h>
38#endif
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040039#include <fcntl.h>
Adrien Béraud612b55b2023-05-29 10:42:04 -040040#include <signal.h>
41#include <unistd.h>
Adrien Béraud612b55b2023-05-29 10:42:04 -040042
Adrien Béraud612b55b2023-05-29 10:42:04 -040043#include <sstream>
44#include <fstream>
45#include <iostream>
46#include <stdexcept>
47#include <limits>
48#include <array>
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040049#include <filesystem>
Adrien Béraud612b55b2023-05-29 10:42:04 -040050
51#include <cstdlib>
52#include <cstring>
53#include <cerrno>
54#include <cstddef>
55#include <ciso646>
56
Adrien Béraud612b55b2023-05-29 10:42:04 -040057
Adrien Béraud612b55b2023-05-29 10:42:04 -040058#define ERASE_BLOCK 4096
59
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040060namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040061namespace fileutils {
62
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040063// returns true if directory exists or was created
Adrien Béraud612b55b2023-05-29 10:42:04 -040064bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040065check_dir(const std::filesystem::path& path, mode_t dirmode, mode_t parentmode)
Adrien Béraud612b55b2023-05-29 10:42:04 -040066{
Adrien Béraud6a529fb2023-09-14 14:34:23 -040067 fmt::print("check_dir: {}\n", path.string());
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040068 if (std::filesystem::exists(path))
69 return true;
70 if (path.has_parent_path())
71 check_dir(path.parent_path(), parentmode, parentmode);
Adrien Béraud6a529fb2023-09-14 14:34:23 -040072 std::error_code ec;
73 if (std::filesystem::create_directory(path, ec)) {
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040074 std::filesystem::permissions(path, (std::filesystem::perms)dirmode);
75 return true;
Adrien Béraud612b55b2023-05-29 10:42:04 -040076 }
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040077 return false;
Adrien Béraud612b55b2023-05-29 10:42:04 -040078}
79
80std::mutex&
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040081getFileLock(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -040082{
83 static std::mutex fileLockLock {};
84 static std::map<std::string, std::mutex> fileLocks {};
85
86 std::lock_guard<std::mutex> l(fileLockLock);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040087 return fileLocks[path.string()];
Adrien Béraud612b55b2023-05-29 10:42:04 -040088}
89
90bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040091isFile(const std::filesystem::path& path, bool resolveSymlink)
Adrien Béraud612b55b2023-05-29 10:42:04 -040092{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040093 auto status = resolveSymlink ? std::filesystem::status(path) : std::filesystem::symlink_status(path);
94 return std::filesystem::is_regular_file(status);
Adrien Béraud612b55b2023-05-29 10:42:04 -040095}
96
97bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040098isDirectory(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -040099{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400100 return std::filesystem::is_directory(path);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400101}
102
103bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400104hasHardLink(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400105{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400106 return std::filesystem::hard_link_count(path) > 1;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400107}
108
109bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400110isSymLink(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400111{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400112 return std::filesystem::is_symlink(path);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400113}
114
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400115template <typename TP>
116std::chrono::system_clock::time_point to_sysclock(TP tp)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400117{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400118 using namespace std::chrono;
119 return time_point_cast<system_clock::duration>(tp - TP::clock::now() + system_clock::now());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400120}
121
122bool
123createSymlink(const std::string& linkFile, const std::string& target)
124{
125 try {
126 std::filesystem::create_symlink(target, linkFile);
127 } catch (const std::exception& e) {
128 //JAMI_ERR("Couldn't create soft link: %s", e.what());
129 return false;
130 }
131 return true;
132}
133
134bool
135createHardlink(const std::string& linkFile, const std::string& target)
136{
137 try {
138 std::filesystem::create_hard_link(target, linkFile);
139 } catch (const std::exception& e) {
140 //JAMI_ERR("Couldn't create hard link: %s", e.what());
141 return false;
142 }
143 return true;
144}
145
146void
147createFileLink(const std::string& linkFile, const std::string& target, bool hard)
148{
149 if (not hard or not createHardlink(linkFile, target))
150 createSymlink(linkFile, target);
151}
152
Adrien Béraud612b55b2023-05-29 10:42:04 -0400153std::vector<uint8_t>
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400154loadFile(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400155{
156 std::vector<uint8_t> buffer;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400157 std::ifstream file = ifstream(path, std::ios::binary);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400158 if (!file)
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400159 throw std::runtime_error("Can't read file: " + path.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400160 file.seekg(0, std::ios::end);
161 auto size = file.tellg();
162 if (size > std::numeric_limits<unsigned>::max())
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400163 throw std::runtime_error("File is too big: " + path.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400164 buffer.resize(size);
165 file.seekg(0, std::ios::beg);
166 if (!file.read((char*) buffer.data(), size))
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400167 throw std::runtime_error("Can't load file: " + path.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400168 return buffer;
169}
170
171void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400172saveFile(const std::filesystem::path& path, const uint8_t* data, size_t data_size, mode_t mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400173{
174 std::ofstream file = fileutils::ofstream(path, std::ios::trunc | std::ios::binary);
175 if (!file.is_open()) {
176 //JAMI_ERR("Could not write data to %s", path.c_str());
177 return;
178 }
179 file.write((char*) data, data_size);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400180 file.close();
181 std::filesystem::permissions(path, (std::filesystem::perms)mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400182}
183
184std::vector<std::string>
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400185readDirectory(const std::filesystem::path& dir)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400186{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400187 std::vector<std::string> files;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400188 std::error_code ec;
189 for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) {
Adrien Béraud82940482023-08-27 13:03:20 -0400190 files.emplace_back(entry.path().filename().string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400191 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400192 return files;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400193}
194
Adrien Béraud612b55b2023-05-29 10:42:04 -0400195bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400196recursive_mkdir(const std::filesystem::path& path, mode_t mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400197{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400198 std::error_code ec;
199 std::filesystem::create_directories(path, ec);
200 if (!ec)
201 std::filesystem::permissions(path, (std::filesystem::perms)mode, ec);
202 return !ec;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400203}
204
205#ifdef _WIN32
206bool
207eraseFile_win32(const std::string& path, bool dosync)
208{
209 HANDLE h
210 = CreateFileA(path.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
211 if (h == INVALID_HANDLE_VALUE) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400212 // JAMI_WARN("Can not open file %s for erasing.", path.c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400213 return false;
214 }
215
216 LARGE_INTEGER size;
217 if (!GetFileSizeEx(h, &size)) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400218 // JAMI_WARN("Can not erase file %s: GetFileSizeEx() failed.", path.c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400219 CloseHandle(h);
220 return false;
221 }
222 if (size.QuadPart == 0) {
223 CloseHandle(h);
224 return false;
225 }
226
227 uint64_t size_blocks = size.QuadPart / ERASE_BLOCK;
228 if (size.QuadPart % ERASE_BLOCK)
229 size_blocks++;
230
231 char* buffer;
232 try {
233 buffer = new char[ERASE_BLOCK];
234 } catch (std::bad_alloc& ba) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400235 // JAMI_WARN("Can not allocate buffer for erasing %s.", path.c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400236 CloseHandle(h);
237 return false;
238 }
239 memset(buffer, 0x00, ERASE_BLOCK);
240
241 OVERLAPPED ovlp;
242 if (size.QuadPart < (1024 - 42)) { // a small file can be stored in the MFT record
243 ovlp.Offset = 0;
244 ovlp.OffsetHigh = 0;
245 WriteFile(h, buffer, (DWORD) size.QuadPart, 0, &ovlp);
246 FlushFileBuffers(h);
247 }
248 for (uint64_t i = 0; i < size_blocks; i++) {
249 uint64_t offset = i * ERASE_BLOCK;
250 ovlp.Offset = offset & 0x00000000FFFFFFFF;
251 ovlp.OffsetHigh = offset >> 32;
252 WriteFile(h, buffer, ERASE_BLOCK, 0, &ovlp);
253 }
254
255 delete[] buffer;
256
257 if (dosync)
258 FlushFileBuffers(h);
259
260 CloseHandle(h);
261 return true;
262}
263
264#else
265
266bool
267eraseFile_posix(const std::string& path, bool dosync)
268{
269 struct stat st;
270 if (stat(path.c_str(), &st) == -1) {
271 //JAMI_WARN("Can not erase file %s: fstat() failed.", path.c_str());
272 return false;
273 }
274 // Remove read-only flag if possible
275 chmod(path.c_str(), st.st_mode | (S_IWGRP+S_IWUSR) );
276
277 int fd = open(path.c_str(), O_WRONLY);
278 if (fd == -1) {
279 //JAMI_WARN("Can not open file %s for erasing.", path.c_str());
280 return false;
281 }
282
283 if (st.st_size == 0) {
284 close(fd);
285 return false;
286 }
287
288 lseek(fd, 0, SEEK_SET);
289
290 std::array<char, ERASE_BLOCK> buffer;
291 buffer.fill(0);
292 decltype(st.st_size) written(0);
293 while (written < st.st_size) {
294 auto ret = write(fd, buffer.data(), buffer.size());
295 if (ret < 0) {
296 //JAMI_WARNING("Error while overriding file with zeros.");
297 break;
298 } else
299 written += ret;
300 }
301
302 if (dosync)
303 fsync(fd);
304
305 close(fd);
306 return written >= st.st_size;
307}
308#endif
309
310bool
311eraseFile(const std::string& path, bool dosync)
312{
313#ifdef _WIN32
314 return eraseFile_win32(path, dosync);
315#else
316 return eraseFile_posix(path, dosync);
317#endif
318}
319
320int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400321remove(const std::filesystem::path& path, bool erase)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400322{
323 if (erase and isFile(path, false) and !hasHardLink(path))
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300324 eraseFile(path.string(), true);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400325
326#ifdef _WIN32
327 // use Win32 api since std::remove will not unlink directory in use
328 if (isDirectory(path))
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300329 return !RemoveDirectory(dhtnet::to_wstring(path.string()).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400330#endif
331
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300332 return std::remove(path.string().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400333}
334
335int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400336removeAll(const std::filesystem::path& path, bool erase)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400337{
338 if (path.empty())
339 return -1;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400340
341 auto status = std::filesystem::status(path);
342 if (std::filesystem::is_directory(status) and not std::filesystem::is_symlink(status)) {
Adrien Béraud85d738b2023-09-06 22:39:46 -0400343 std::error_code ec;
344 for (const auto& entry: std::filesystem::directory_iterator(path, ec)) {
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400345 removeAll(entry.path(), erase);
346 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400347 }
348 return remove(path, erase);
349}
350
351void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400352openStream(std::ifstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400353{
354#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300355 file.open(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400356#else
357 file.open(path, mode);
358#endif
359}
360
361void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400362openStream(std::ofstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400363{
364#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300365 file.open(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400366#else
367 file.open(path, mode);
368#endif
369}
370
371std::ifstream
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400372ifstream(const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400373{
374#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300375 return std::ifstream(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400376#else
377 return std::ifstream(path, mode);
378#endif
379}
380
381std::ofstream
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400382ofstream(const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400383{
384#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300385 return std::ofstream(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400386#else
387 return std::ofstream(path, mode);
388#endif
389}
390
Adrien Béraud612b55b2023-05-29 10:42:04 -0400391int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400392accessFile(const std::filesystem::path& file, int mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400393{
394#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300395 return _waccess(dhtnet::to_wstring(file.string()).c_str(), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400396#else
397 return access(file.c_str(), mode);
398#endif
399}
400
Adrien Béraud612b55b2023-05-29 10:42:04 -0400401} // namespace fileutils
Sébastien Blin464bdff2023-07-19 08:02:53 -0400402} // namespace dhtnet