blob: 8bc16c105e52aa151312a94af14e87e713c032bb [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);
71 if (std::filesystem::create_directory(path)) {
72 std::filesystem::permissions(path, (std::filesystem::perms)dirmode);
73 return true;
Adrien Béraud612b55b2023-05-29 10:42:04 -040074 }
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040075 return false;
Adrien Béraud612b55b2023-05-29 10:42:04 -040076}
77
78std::mutex&
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040079getFileLock(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -040080{
81 static std::mutex fileLockLock {};
82 static std::map<std::string, std::mutex> fileLocks {};
83
84 std::lock_guard<std::mutex> l(fileLockLock);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040085 return fileLocks[path.string()];
Adrien Béraud612b55b2023-05-29 10:42:04 -040086}
87
88bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040089isFile(const std::filesystem::path& path, bool resolveSymlink)
Adrien Béraud612b55b2023-05-29 10:42:04 -040090{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040091 auto status = resolveSymlink ? std::filesystem::status(path) : std::filesystem::symlink_status(path);
92 return std::filesystem::is_regular_file(status);
Adrien Béraud612b55b2023-05-29 10:42:04 -040093}
94
95bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040096isDirectory(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -040097{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -040098 return std::filesystem::is_directory(path);
Adrien Béraud612b55b2023-05-29 10:42:04 -040099}
100
101bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400102hasHardLink(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400103{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400104 return std::filesystem::hard_link_count(path) > 1;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400105}
106
107bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400108isSymLink(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400109{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400110 return std::filesystem::is_symlink(path);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400111}
112
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400113template <typename TP>
114std::chrono::system_clock::time_point to_sysclock(TP tp)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400115{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400116 using namespace std::chrono;
117 return time_point_cast<system_clock::duration>(tp - TP::clock::now() + system_clock::now());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400118}
119
120bool
121createSymlink(const std::string& linkFile, const std::string& target)
122{
123 try {
124 std::filesystem::create_symlink(target, linkFile);
125 } catch (const std::exception& e) {
126 //JAMI_ERR("Couldn't create soft link: %s", e.what());
127 return false;
128 }
129 return true;
130}
131
132bool
133createHardlink(const std::string& linkFile, const std::string& target)
134{
135 try {
136 std::filesystem::create_hard_link(target, linkFile);
137 } catch (const std::exception& e) {
138 //JAMI_ERR("Couldn't create hard link: %s", e.what());
139 return false;
140 }
141 return true;
142}
143
144void
145createFileLink(const std::string& linkFile, const std::string& target, bool hard)
146{
147 if (not hard or not createHardlink(linkFile, target))
148 createSymlink(linkFile, target);
149}
150
Adrien Béraud612b55b2023-05-29 10:42:04 -0400151std::vector<uint8_t>
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400152loadFile(const std::filesystem::path& path)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400153{
154 std::vector<uint8_t> buffer;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400155 std::ifstream file = ifstream(path, std::ios::binary);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400156 if (!file)
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400157 throw std::runtime_error("Can't read file: " + path.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400158 file.seekg(0, std::ios::end);
159 auto size = file.tellg();
160 if (size > std::numeric_limits<unsigned>::max())
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400161 throw std::runtime_error("File is too big: " + path.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400162 buffer.resize(size);
163 file.seekg(0, std::ios::beg);
164 if (!file.read((char*) buffer.data(), size))
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400165 throw std::runtime_error("Can't load file: " + path.string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400166 return buffer;
167}
168
169void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400170saveFile(const std::filesystem::path& path, const uint8_t* data, size_t data_size, mode_t mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400171{
172 std::ofstream file = fileutils::ofstream(path, std::ios::trunc | std::ios::binary);
173 if (!file.is_open()) {
174 //JAMI_ERR("Could not write data to %s", path.c_str());
175 return;
176 }
177 file.write((char*) data, data_size);
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400178 file.close();
179 std::filesystem::permissions(path, (std::filesystem::perms)mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400180}
181
182std::vector<std::string>
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400183readDirectory(const std::filesystem::path& dir)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400184{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400185 std::vector<std::string> files;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400186 std::error_code ec;
187 for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) {
Adrien Béraud82940482023-08-27 13:03:20 -0400188 files.emplace_back(entry.path().filename().string());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400189 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400190 return files;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400191}
192
Adrien Béraud612b55b2023-05-29 10:42:04 -0400193bool
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400194recursive_mkdir(const std::filesystem::path& path, mode_t mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400195{
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400196 std::error_code ec;
197 std::filesystem::create_directories(path, ec);
198 if (!ec)
199 std::filesystem::permissions(path, (std::filesystem::perms)mode, ec);
200 return !ec;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400201}
202
203#ifdef _WIN32
204bool
205eraseFile_win32(const std::string& path, bool dosync)
206{
207 HANDLE h
208 = CreateFileA(path.c_str(), GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
209 if (h == INVALID_HANDLE_VALUE) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400210 // JAMI_WARN("Can not open file %s for erasing.", path.c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400211 return false;
212 }
213
214 LARGE_INTEGER size;
215 if (!GetFileSizeEx(h, &size)) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400216 // JAMI_WARN("Can not erase file %s: GetFileSizeEx() failed.", path.c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400217 CloseHandle(h);
218 return false;
219 }
220 if (size.QuadPart == 0) {
221 CloseHandle(h);
222 return false;
223 }
224
225 uint64_t size_blocks = size.QuadPart / ERASE_BLOCK;
226 if (size.QuadPart % ERASE_BLOCK)
227 size_blocks++;
228
229 char* buffer;
230 try {
231 buffer = new char[ERASE_BLOCK];
232 } catch (std::bad_alloc& ba) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400233 // JAMI_WARN("Can not allocate buffer for erasing %s.", path.c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400234 CloseHandle(h);
235 return false;
236 }
237 memset(buffer, 0x00, ERASE_BLOCK);
238
239 OVERLAPPED ovlp;
240 if (size.QuadPart < (1024 - 42)) { // a small file can be stored in the MFT record
241 ovlp.Offset = 0;
242 ovlp.OffsetHigh = 0;
243 WriteFile(h, buffer, (DWORD) size.QuadPart, 0, &ovlp);
244 FlushFileBuffers(h);
245 }
246 for (uint64_t i = 0; i < size_blocks; i++) {
247 uint64_t offset = i * ERASE_BLOCK;
248 ovlp.Offset = offset & 0x00000000FFFFFFFF;
249 ovlp.OffsetHigh = offset >> 32;
250 WriteFile(h, buffer, ERASE_BLOCK, 0, &ovlp);
251 }
252
253 delete[] buffer;
254
255 if (dosync)
256 FlushFileBuffers(h);
257
258 CloseHandle(h);
259 return true;
260}
261
262#else
263
264bool
265eraseFile_posix(const std::string& path, bool dosync)
266{
267 struct stat st;
268 if (stat(path.c_str(), &st) == -1) {
269 //JAMI_WARN("Can not erase file %s: fstat() failed.", path.c_str());
270 return false;
271 }
272 // Remove read-only flag if possible
273 chmod(path.c_str(), st.st_mode | (S_IWGRP+S_IWUSR) );
274
275 int fd = open(path.c_str(), O_WRONLY);
276 if (fd == -1) {
277 //JAMI_WARN("Can not open file %s for erasing.", path.c_str());
278 return false;
279 }
280
281 if (st.st_size == 0) {
282 close(fd);
283 return false;
284 }
285
286 lseek(fd, 0, SEEK_SET);
287
288 std::array<char, ERASE_BLOCK> buffer;
289 buffer.fill(0);
290 decltype(st.st_size) written(0);
291 while (written < st.st_size) {
292 auto ret = write(fd, buffer.data(), buffer.size());
293 if (ret < 0) {
294 //JAMI_WARNING("Error while overriding file with zeros.");
295 break;
296 } else
297 written += ret;
298 }
299
300 if (dosync)
301 fsync(fd);
302
303 close(fd);
304 return written >= st.st_size;
305}
306#endif
307
308bool
309eraseFile(const std::string& path, bool dosync)
310{
311#ifdef _WIN32
312 return eraseFile_win32(path, dosync);
313#else
314 return eraseFile_posix(path, dosync);
315#endif
316}
317
318int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400319remove(const std::filesystem::path& path, bool erase)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400320{
321 if (erase and isFile(path, false) and !hasHardLink(path))
322 eraseFile(path, true);
323
324#ifdef _WIN32
325 // use Win32 api since std::remove will not unlink directory in use
326 if (isDirectory(path))
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400327 return !RemoveDirectory(dhtnet::to_wstring(path).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400328#endif
329
330 return std::remove(path.c_str());
331}
332
333int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400334removeAll(const std::filesystem::path& path, bool erase)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400335{
336 if (path.empty())
337 return -1;
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400338
339 auto status = std::filesystem::status(path);
340 if (std::filesystem::is_directory(status) and not std::filesystem::is_symlink(status)) {
341 for (const auto& entry: std::filesystem::directory_iterator(path)) {
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400342 removeAll(entry.path(), erase);
343 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400344 }
345 return remove(path, erase);
346}
347
348void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400349openStream(std::ifstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400350{
351#ifdef _WIN32
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400352 file.open(dhtnet::to_wstring(path), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400353#else
354 file.open(path, mode);
355#endif
356}
357
358void
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400359openStream(std::ofstream& file, const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400360{
361#ifdef _WIN32
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400362 file.open(dhtnet::to_wstring(path), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400363#else
364 file.open(path, mode);
365#endif
366}
367
368std::ifstream
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400369ifstream(const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400370{
371#ifdef _WIN32
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400372 return std::ifstream(dhtnet::to_wstring(path), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400373#else
374 return std::ifstream(path, mode);
375#endif
376}
377
378std::ofstream
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400379ofstream(const std::filesystem::path& path, std::ios_base::openmode mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400380{
381#ifdef _WIN32
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400382 return std::ofstream(dhtnet::to_wstring(path), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400383#else
384 return std::ofstream(path, mode);
385#endif
386}
387
Adrien Béraud612b55b2023-05-29 10:42:04 -0400388int
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400389accessFile(const std::filesystem::path& file, int mode)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400390{
391#ifdef _WIN32
Adrien Béraud1ae60aa2023-07-07 09:55:09 -0400392 return _waccess(dhtnet::to_wstring(file).c_str(), mode);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400393#else
394 return access(file.c_str(), mode);
395#endif
396}
397
Adrien Béraud612b55b2023-05-29 10:42:04 -0400398} // namespace fileutils
Sébastien Blin464bdff2023-07-19 08:02:53 -0400399} // namespace dhtnet