blob: f700ddb424f34f3ee695653221b43c529e7166a7 [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éraud1299a0d2023-09-19 15:03:28 -0400156 std::ifstream file(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{
Adrien Béraud1299a0d2023-09-19 15:03:28 -0400173 std::ofstream file(path, std::ios::trunc | std::ios::binary);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400174 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
Adrien Béraud668b28c2023-10-25 00:50:56 -0400331 std::error_code ec;
332 std::filesystem::remove(path, ec);
333 return ec.value();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400334}
335
336int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400337removeAll(const std::filesystem::path& path, bool erase)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400338{
Adrien Béraudadd101f2023-11-23 14:45:31 -0500339 try {
340 std::error_code ec;
341 if (not erase) {
342 std::filesystem::remove_all(path, ec);
343 return ec.value();
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400344 }
Adrien Béraudadd101f2023-11-23 14:45:31 -0500345 if (path.empty())
346 return -1;
347
348 auto status = std::filesystem::status(path, ec);
349 if (!ec && std::filesystem::is_directory(status) and not std::filesystem::is_symlink(status)) {
350 for (const auto& entry: std::filesystem::directory_iterator(path, ec)) {
351 removeAll(entry.path(), erase);
352 }
353 }
354 return remove(path, erase);
355 } catch (const std::exception& e) {
356 //JAMI_ERR("Error while removing %s: %s", path.c_str(), e.what());
357 return -1;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400358 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400359}
360
361void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400362openStream(std::ifstream& 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
371void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400372openStream(std::ofstream& file, 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 file.open(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400376#else
377 file.open(path, mode);
378#endif
379}
380
381std::ifstream
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400382ifstream(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::ifstream(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400386#else
387 return std::ifstream(path, mode);
388#endif
389}
390
391std::ofstream
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400392ofstream(const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400393{
394#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300395 return std::ofstream(dhtnet::to_wstring(path.string()), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400396#else
397 return std::ofstream(path, mode);
398#endif
399}
400
Adrien Béraud612b55b2023-05-29 10:42:04 -0400401int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400402accessFile(const std::filesystem::path& file, int mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400403{
404#ifdef _WIN32
Aline Gondim Santos406c0f42023-09-13 12:10:23 -0300405 return _waccess(dhtnet::to_wstring(file.string()).c_str(), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400406#else
407 return access(file.c_str(), mode);
408#endif
409}
410
Adrien Béraud612b55b2023-05-29 10:42:04 -0400411} // namespace fileutils
Sébastien Blin464bdff2023-07-19 08:02:53 -0400412} // namespace dhtnet