blob: 7ba5eb855dc7f4265f1b07bf2f43d254d59386ca [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éraud2a4e73d2023-08-27 12:53:55 -040067 if (std::filesystem::exists(path))
68 return true;
69 if (path.has_parent_path())
70 check_dir(path.parent_path(), parentmode, parentmode);
Adrien Béraud6a529fb2023-09-14 14:34:23 -040071 std::error_code ec;
72 if (std::filesystem::create_directory(path, ec)) {
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040073 std::filesystem::permissions(path, (std::filesystem::perms)dirmode);
74 return true;
Adrien Béraud612b55b2023-05-29 10:42:04 -040075 }
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040076 return false;
Adrien Béraud612b55b2023-05-29 10:42:04 -040077}
78
79std::mutex&
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040080getFileLock(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -040081{
82 static std::mutex fileLockLock {};
83 static std::map<std::string, std::mutex> fileLocks {};
84
85 std::lock_guard<std::mutex> l(fileLockLock);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040086 return fileLocks[path.string()];
Adrien Béraud612b55b2023-05-29 10:42:04 -040087}
88
89bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040090isFile(const std::filesystem::path& path, bool resolveSymlink)
Adrien Béraud612b55b2023-05-29 10:42:04 -040091{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040092 auto status = resolveSymlink ? std::filesystem::status(path) : std::filesystem::symlink_status(path);
93 return std::filesystem::is_regular_file(status);
Adrien Béraud612b55b2023-05-29 10:42:04 -040094}
95
96bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040097isDirectory(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -040098{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040099 return std::filesystem::is_directory(path);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400100}
101
102bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400103hasHardLink(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400104{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400105 return std::filesystem::hard_link_count(path) > 1;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400106}
107
108bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400109isSymLink(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400110{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400111 return std::filesystem::is_symlink(path);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400112}
113
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400114template <typename TP>
115std::chrono::system_clock::time_point to_sysclock(TP tp)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400116{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400117 using namespace std::chrono;
118 return time_point_cast<system_clock::duration>(tp - TP::clock::now() + system_clock::now());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400119}
120
121bool
122createSymlink(const std::string& linkFile, const std::string& target)
123{
124 try {
125 std::filesystem::create_symlink(target, linkFile);
126 } catch (const std::exception& e) {
127 //JAMI_ERR("Couldn't create soft link: %s", e.what());
128 return false;
129 }
130 return true;
131}
132
133bool
134createHardlink(const std::string& linkFile, const std::string& target)
135{
136 try {
137 std::filesystem::create_hard_link(target, linkFile);
138 } catch (const std::exception& e) {
139 //JAMI_ERR("Couldn't create hard link: %s", e.what());
140 return false;
141 }
142 return true;
143}
144
145void
146createFileLink(const std::string& linkFile, const std::string& target, bool hard)
147{
148 if (not hard or not createHardlink(linkFile, target))
149 createSymlink(linkFile, target);
150}
151
Adrien Béraud612b55b2023-05-29 10:42:04 -0400152std::vector<uint8_t>
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400153loadFile(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400154{
155 std::vector<uint8_t> buffer;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400156 std::ifstream file = ifstream(path, std::ios::binary);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400157 if (!file)
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400158 throw std::runtime_error("Can't read file: " + path.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400159 file.seekg(0, std::ios::end);
160 auto size = file.tellg();
161 if (size > std::numeric_limits<unsigned>::max())
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400162 throw std::runtime_error("File is too big: " + path.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400163 buffer.resize(size);
164 file.seekg(0, std::ios::beg);
165 if (!file.read((char*) buffer.data(), size))
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400166 throw std::runtime_error("Can't load file: " + path.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400167 return buffer;
168}
169
170void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400171saveFile(const std::filesystem::path& path, const uint8_t* data, size_t data_size, mode_t mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400172{
173 std::ofstream file = fileutils::ofstream(path, std::ios::trunc | std::ios::binary);
174 if (!file.is_open()) {
175 //JAMI_ERR("Could not write data to %s", path.c_str());
176 return;
177 }
178 file.write((char*) data, data_size);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400179 file.close();
180 std::filesystem::permissions(path, (std::filesystem::perms)mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400181}
182
183std::vector<std::string>
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400184readDirectory(const std::filesystem::path& dir)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400185{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400186 std::vector<std::string> files;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400187 std::error_code ec;
188 for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) {
Adrien Béraud82940482023-08-27 13:03:20 -0400189 files.emplace_back(entry.path().filename().string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400190 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400191 return files;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400192}
193
Adrien Béraud612b55b2023-05-29 10:42:04 -0400194bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400195recursive_mkdir(const std::filesystem::path& path, mode_t mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400196{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400197 std::error_code ec;
198 std::filesystem::create_directories(path, ec);
199 if (!ec)
200 std::filesystem::permissions(path, (std::filesystem::perms)mode, ec);
201 return !ec;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400202}
203
204#ifdef _WIN32
205bool
206eraseFile_win32(const std::string& path, bool dosync)
207{
208 HANDLE h
209 = CreateFileA(path.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
210 if (h == INVALID_HANDLE_VALUE) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400211 // JAMI_WARN("Can not open file %s for erasing.", path.c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400212 return false;
213 }
214
215 LARGE_INTEGER size;
216 if (!GetFileSizeEx(h, &size)) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400217 // JAMI_WARN("Can not erase file %s: GetFileSizeEx() failed.", path.c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400218 CloseHandle(h);
219 return false;
220 }
221 if (size.QuadPart == 0) {
222 CloseHandle(h);
223 return false;
224 }
225
226 uint64_t size_blocks = size.QuadPart / ERASE_BLOCK;
227 if (size.QuadPart % ERASE_BLOCK)
228 size_blocks++;
229
230 char* buffer;
231 try {
232 buffer = new char[ERASE_BLOCK];
233 } catch (std::bad_alloc& ba) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400234 // JAMI_WARN("Can not allocate buffer for erasing %s.", path.c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400235 CloseHandle(h);
236 return false;
237 }
238 memset(buffer, 0x00, ERASE_BLOCK);
239
240 OVERLAPPED ovlp;
241 if (size.QuadPart < (1024 - 42)) { // a small file can be stored in the MFT record
242 ovlp.Offset = 0;
243 ovlp.OffsetHigh = 0;
244 WriteFile(h, buffer, (DWORD) size.QuadPart, 0, &ovlp);
245 FlushFileBuffers(h);
246 }
247 for (uint64_t i = 0; i < size_blocks; i++) {
248 uint64_t offset = i * ERASE_BLOCK;
249 ovlp.Offset = offset & 0x00000000FFFFFFFF;
250 ovlp.OffsetHigh = offset >> 32;
251 WriteFile(h, buffer, ERASE_BLOCK, 0, &ovlp);
252 }
253
254 delete[] buffer;
255
256 if (dosync)
257 FlushFileBuffers(h);
258
259 CloseHandle(h);
260 return true;
261}
262
263#else
264
265bool
266eraseFile_posix(const std::string& path, bool dosync)
267{
268 struct stat st;
269 if (stat(path.c_str(), &st) == -1) {
270 //JAMI_WARN("Can not erase file %s: fstat() failed.", path.c_str());
271 return false;
272 }
273 // Remove read-only flag if possible
274 chmod(path.c_str(), st.st_mode | (S_IWGRP+S_IWUSR) );
275
276 int fd = open(path.c_str(), O_WRONLY);
277 if (fd == -1) {
278 //JAMI_WARN("Can not open file %s for erasing.", path.c_str());
279 return false;
280 }
281
282 if (st.st_size == 0) {
283 close(fd);
284 return false;
285 }
286
287 lseek(fd, 0, SEEK_SET);
288
289 std::array<char, ERASE_BLOCK> buffer;
290 buffer.fill(0);
291 decltype(st.st_size) written(0);
292 while (written < st.st_size) {
293 auto ret = write(fd, buffer.data(), buffer.size());
294 if (ret < 0) {
295 //JAMI_WARNING("Error while overriding file with zeros.");
296 break;
297 } else
298 written += ret;
299 }
300
301 if (dosync)
302 fsync(fd);
303
304 close(fd);
305 return written >= st.st_size;
306}
307#endif
308
309bool
310eraseFile(const std::string& path, bool dosync)
311{
312#ifdef _WIN32
313 return eraseFile_win32(path, dosync);
314#else
315 return eraseFile_posix(path, dosync);
316#endif
317}
318
319int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400320remove(const std::filesystem::path& path, bool erase)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400321{
322 if (erase and isFile(path, false) and !hasHardLink(path))
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300323 eraseFile(path.string(), true);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400324
325#ifdef _WIN32
326 // use Win32 api since std::remove will not unlink directory in use
327 if (isDirectory(path))
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300328 return !RemoveDirectory(dhtnet::to_wstring(path.string()).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400329#endif
330
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300331 return std::remove(path.string().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400332}
333
334int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400335removeAll(const std::filesystem::path& path, bool erase)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400336{
337 if (path.empty())
338 return -1;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400339
340 auto status = std::filesystem::status(path);
341 if (std::filesystem::is_directory(status) and not std::filesystem::is_symlink(status)) {
Adrien Béraud85d738b2023-09-06 22:39:46 -0400342 std::error_code ec;
343 for (const auto& entry: std::filesystem::directory_iterator(path, ec)) {
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400344 removeAll(entry.path(), erase);
345 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400346 }
347 return remove(path, erase);
348}
349
350void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400351openStream(std::ifstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400352{
353#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300354 file.open(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400355#else
356 file.open(path, mode);
357#endif
358}
359
360void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400361openStream(std::ofstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400362{
363#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300364 file.open(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400365#else
366 file.open(path, mode);
367#endif
368}
369
370std::ifstream
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400371ifstream(const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400372{
373#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300374 return std::ifstream(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400375#else
376 return std::ifstream(path, mode);
377#endif
378}
379
380std::ofstream
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400381ofstream(const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400382{
383#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300384 return std::ofstream(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400385#else
386 return std::ofstream(path, mode);
387#endif
388}
389
Adrien Béraud612b55b2023-05-29 10:42:04 -0400390int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400391accessFile(const std::filesystem::path& file, int mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400392{
393#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300394 return _waccess(dhtnet::to_wstring(file.string()).c_str(), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400395#else
396 return access(file.c_str(), mode);
397#endif
398}
399
Adrien Béraud612b55b2023-05-29 10:42:04 -0400400} // namespace fileutils
Sébastien Blin464bdff2023-07-19 08:02:53 -0400401} // namespace dhtnet