summaryrefslogtreecommitdiff
path: root/src/common/file_util.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/file_util.cpp')
-rw-r--r--src/common/file_util.cpp439
1 files changed, 350 insertions, 89 deletions
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index d5f6ea2ee..18fbfa25b 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -3,7 +3,6 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <array> 5#include <array>
6#include <filesystem>
7#include <limits> 6#include <limits>
8#include <memory> 7#include <memory>
9#include <sstream> 8#include <sstream>
@@ -68,102 +67,290 @@
68#include <algorithm> 67#include <algorithm>
69#include <sys/stat.h> 68#include <sys/stat.h>
70 69
70#ifndef S_ISDIR
71#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
72#endif
73
74// This namespace has various generic functions related to files and paths.
75// The code still needs a ton of cleanup.
76// REMEMBER: strdup considered harmful!
71namespace Common::FS { 77namespace Common::FS {
72namespace fs = std::filesystem;
73 78
74bool Exists(const fs::path& path) { 79// Remove any ending forward slashes from directory paths
75 std::error_code ec; 80// Modifies argument.
76 return fs::exists(path, ec); 81static void StripTailDirSlashes(std::string& fname) {
82 if (fname.length() <= 1) {
83 return;
84 }
85
86 std::size_t i = fname.length();
87 while (i > 0 && fname[i - 1] == DIR_SEP_CHR) {
88 --i;
89 }
90 fname.resize(i);
77} 91}
78 92
79bool IsDirectory(const fs::path& path) { 93bool Exists(const std::string& filename) {
80 std::error_code ec; 94 struct stat file_info;
81 return fs::is_directory(path, ec); 95
96 std::string copy(filename);
97 StripTailDirSlashes(copy);
98
99#ifdef _WIN32
100 // Windows needs a slash to identify a driver root
101 if (copy.size() != 0 && copy.back() == ':')
102 copy += DIR_SEP_CHR;
103
104 int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
105#else
106 int result = stat(copy.c_str(), &file_info);
107#endif
108
109 return (result == 0);
82} 110}
83 111
84bool Delete(const fs::path& path) { 112bool IsDirectory(const std::string& filename) {
85 LOG_TRACE(Common_Filesystem, "file {}", path.string()); 113 struct stat file_info;
86 114
87 // Return true because we care about the file no 115 std::string copy(filename);
88 // being there, not the actual delete. 116 StripTailDirSlashes(copy);
89 if (!Exists(path)) { 117
90 LOG_DEBUG(Common_Filesystem, "{} does not exist", path.string()); 118#ifdef _WIN32
91 return true; 119 // Windows needs a slash to identify a driver root
120 if (copy.size() != 0 && copy.back() == ':')
121 copy += DIR_SEP_CHR;
122
123 int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
124#else
125 int result = stat(copy.c_str(), &file_info);
126#endif
127
128 if (result < 0) {
129 LOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg());
130 return false;
92 } 131 }
93 132
94 std::error_code ec; 133 return S_ISDIR(file_info.st_mode);
95 return fs::remove(path, ec);
96} 134}
97 135
98bool CreateDir(const fs::path& path) { 136bool Delete(const std::string& filename) {
99 LOG_TRACE(Common_Filesystem, "directory {}", path.string()); 137 LOG_TRACE(Common_Filesystem, "file {}", filename);
100 138
101 std::error_code ec; 139 // Return true because we care about the file no
102 const bool success = fs::create_directory(path, ec); 140 // being there, not the actual delete.
141 if (!Exists(filename)) {
142 LOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
143 return true;
144 }
145
146 // We can't delete a directory
147 if (IsDirectory(filename)) {
148 LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
149 return false;
150 }
103 151
104 if (!success) { 152#ifdef _WIN32
105 LOG_ERROR(Common_Filesystem, "Unable to create directory: {}", ec.message()); 153 if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
154 LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
155 return false;
156 }
157#else
158 if (unlink(filename.c_str()) == -1) {
159 LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
106 return false; 160 return false;
107 } 161 }
162#endif
108 163
109 return true; 164 return true;
110} 165}
111 166
112bool CreateFullPath(const fs::path& path) { 167bool CreateDir(const std::string& path) {
113 LOG_TRACE(Common_Filesystem, "path {}", path.string()); 168 LOG_TRACE(Common_Filesystem, "directory {}", path);
169#ifdef _WIN32
170 if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
171 return true;
172 DWORD error = GetLastError();
173 if (error == ERROR_ALREADY_EXISTS) {
174 LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
175 return true;
176 }
177 LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
178 return false;
179#else
180 if (mkdir(path.c_str(), 0755) == 0)
181 return true;
114 182
115 std::error_code ec; 183 int err = errno;
116 const bool success = fs::create_directories(path, ec);
117 184
118 if (!success) { 185 if (err == EEXIST) {
119 LOG_ERROR(Common_Filesystem, "Unable to create full path: {}", ec.message()); 186 LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
120 return false; 187 return true;
121 } 188 }
122 189
123 return true; 190 LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
191 return false;
192#endif
124} 193}
125 194
126bool Rename(const fs::path& src, const fs::path& dst) { 195bool CreateFullPath(const std::string& fullPath) {
127 LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string()); 196 int panicCounter = 100;
197 LOG_TRACE(Common_Filesystem, "path {}", fullPath);
198
199 if (Exists(fullPath)) {
200 LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
201 return true;
202 }
203
204 std::size_t position = 0;
205 while (true) {
206 // Find next sub path
207 position = fullPath.find(DIR_SEP_CHR, position);
128 208
129 std::error_code ec; 209 // we're done, yay!
130 fs::rename(src, dst, ec); 210 if (position == fullPath.npos)
211 return true;
131 212
132 if (ec) { 213 // Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
133 LOG_ERROR(Common_Filesystem, "Unable to rename file from {} to {}: {}", src.string(), 214 std::string const subPath(fullPath.substr(0, position + 1));
134 dst.string(), ec.message()); 215 if (!IsDirectory(subPath) && !CreateDir(subPath)) {
216 LOG_ERROR(Common, "CreateFullPath: directory creation failed");
217 return false;
218 }
219
220 // A safety check
221 panicCounter--;
222 if (panicCounter <= 0) {
223 LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
224 return false;
225 }
226 position++;
227 }
228}
229
230bool DeleteDir(const std::string& filename) {
231 LOG_TRACE(Common_Filesystem, "directory {}", filename);
232
233 // check if a directory
234 if (!IsDirectory(filename)) {
235 LOG_ERROR(Common_Filesystem, "Not a directory {}", filename);
135 return false; 236 return false;
136 } 237 }
137 238
138 return true; 239#ifdef _WIN32
240 if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str()))
241 return true;
242#else
243 if (rmdir(filename.c_str()) == 0)
244 return true;
245#endif
246 LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
247
248 return false;
249}
250
251bool Rename(const std::string& srcFilename, const std::string& destFilename) {
252 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
253#ifdef _WIN32
254 if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
255 Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
256 return true;
257#else
258 if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
259 return true;
260#endif
261 LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
262 GetLastErrorMsg());
263 return false;
139} 264}
140 265
141bool Copy(const fs::path& src, const fs::path& dst) { 266bool Copy(const std::string& srcFilename, const std::string& destFilename) {
142 LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string()); 267 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
268#ifdef _WIN32
269 if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
270 Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
271 return true;
143 272
144 std::error_code ec; 273 LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
145 const bool success = fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec); 274 GetLastErrorMsg());
275 return false;
276#else
277 using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
146 278
147 if (!success) { 279 // Open input file
148 LOG_ERROR(Common_Filesystem, "Unable to copy file {} to {}: {}", src.string(), dst.string(), 280 CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose};
149 ec.message()); 281 if (!input) {
282 LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
283 destFilename, GetLastErrorMsg());
150 return false; 284 return false;
151 } 285 }
152 286
287 // open output file
288 CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose};
289 if (!output) {
290 LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
291 destFilename, GetLastErrorMsg());
292 return false;
293 }
294
295 // copy loop
296 std::array<char, 1024> buffer;
297 while (!feof(input.get())) {
298 // read input
299 std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get());
300 if (rnum != buffer.size()) {
301 if (ferror(input.get()) != 0) {
302 LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
303 srcFilename, destFilename, GetLastErrorMsg());
304 return false;
305 }
306 }
307
308 // write output
309 std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get());
310 if (wnum != rnum) {
311 LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
312 destFilename, GetLastErrorMsg());
313 return false;
314 }
315 }
316
153 return true; 317 return true;
318#endif
154} 319}
155 320
156u64 GetSize(const fs::path& path) { 321u64 GetSize(const std::string& filename) {
157 std::error_code ec; 322 if (!Exists(filename)) {
158 const auto size = fs::file_size(path, ec); 323 LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
324 return 0;
325 }
159 326
160 if (ec) { 327 if (IsDirectory(filename)) {
161 LOG_ERROR(Common_Filesystem, "Unable to retrieve file size ({}): {}", path.string(), 328 LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename);
162 ec.message());
163 return 0; 329 return 0;
164 } 330 }
165 331
166 return size; 332 struct stat buf;
333#ifdef _WIN32
334 if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0)
335#else
336 if (stat(filename.c_str(), &buf) == 0)
337#endif
338 {
339 LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size);
340 return buf.st_size;
341 }
342
343 LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg());
344 return 0;
345}
346
347u64 GetSize(const int fd) {
348 struct stat buf;
349 if (fstat(fd, &buf) != 0) {
350 LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg());
351 return 0;
352 }
353 return buf.st_size;
167} 354}
168 355
169u64 GetSize(FILE* f) { 356u64 GetSize(FILE* f) {
@@ -251,58 +438,132 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
251 return true; 438 return true;
252} 439}
253 440
254bool DeleteDirRecursively(const fs::path& path) { 441u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
255 std::error_code ec; 442 unsigned int recursion) {
256 fs::remove_all(path, ec); 443 const auto callback = [recursion, &parent_entry](u64* num_entries_out,
444 const std::string& directory,
445 const std::string& virtual_name) -> bool {
446 FSTEntry entry;
447 entry.virtualName = virtual_name;
448 entry.physicalName = directory + DIR_SEP + virtual_name;
449
450 if (IsDirectory(entry.physicalName)) {
451 entry.isDirectory = true;
452 // is a directory, lets go inside if we didn't recurse to often
453 if (recursion > 0) {
454 entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
455 *num_entries_out += entry.size;
456 } else {
457 entry.size = 0;
458 }
459 } else { // is a file
460 entry.isDirectory = false;
461 entry.size = GetSize(entry.physicalName);
462 }
463 (*num_entries_out)++;
257 464
258 if (ec) { 465 // Push into the tree
259 LOG_ERROR(Common_Filesystem, "Unable to completely delete directory {}: {}", path.string(), 466 parent_entry.children.push_back(std::move(entry));
260 ec.message()); 467 return true;
261 return false; 468 };
262 }
263 469
264 return true; 470 u64 num_entries;
471 return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
265} 472}
266 473
267void CopyDir(const fs::path& src, const fs::path& dst) { 474bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
268 constexpr auto copy_flags = fs::copy_options::skip_existing | fs::copy_options::recursive; 475 const auto callback = [recursion](u64*, const std::string& directory,
476 const std::string& virtual_name) {
477 const std::string new_path = directory + DIR_SEP_CHR + virtual_name;
269 478
270 std::error_code ec; 479 if (IsDirectory(new_path)) {
271 fs::copy(src, dst, copy_flags, ec); 480 if (recursion == 0) {
481 return false;
482 }
483 return DeleteDirRecursively(new_path, recursion - 1);
484 }
485 return Delete(new_path);
486 };
272 487
273 if (ec) { 488 if (!ForeachDirectoryEntry(nullptr, directory, callback))
274 LOG_ERROR(Common_Filesystem, "Error copying directory {} to {}: {}", src.string(), 489 return false;
275 dst.string(), ec.message());
276 return;
277 }
278 490
279 LOG_TRACE(Common_Filesystem, "Successfully copied directory."); 491 // Delete the outermost directory
492 DeleteDir(directory);
493 return true;
280} 494}
281 495
282std::optional<fs::path> GetCurrentDir() { 496void CopyDir([[maybe_unused]] const std::string& source_path,
283 std::error_code ec; 497 [[maybe_unused]] const std::string& dest_path) {
284 auto path = fs::current_path(ec); 498#ifndef _WIN32
499 if (source_path == dest_path) {
500 return;
501 }
502 if (!Exists(source_path)) {
503 return;
504 }
505 if (!Exists(dest_path)) {
506 CreateFullPath(dest_path);
507 }
285 508
286 if (ec) { 509 DIR* dirp = opendir(source_path.c_str());
287 LOG_ERROR(Common_Filesystem, "Unable to retrieve current working directory: {}", 510 if (!dirp) {
288 ec.message()); 511 return;
289 return std::nullopt;
290 } 512 }
291 513
292 return {std::move(path)}; 514 while (struct dirent* result = readdir(dirp)) {
293} 515 const std::string virtualName(result->d_name);
516 // check for "." and ".."
517 if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
518 ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) {
519 continue;
520 }
294 521
295bool SetCurrentDir(const fs::path& path) { 522 std::string source, dest;
296 std::error_code ec; 523 source = source_path + virtualName;
297 fs::current_path(path, ec); 524 dest = dest_path + virtualName;
525 if (IsDirectory(source)) {
526 source += '/';
527 dest += '/';
528 if (!Exists(dest)) {
529 CreateFullPath(dest);
530 }
531 CopyDir(source, dest);
532 } else if (!Exists(dest)) {
533 Copy(source, dest);
534 }
535 }
536 closedir(dirp);
537#endif
538}
298 539
299 if (ec) { 540std::optional<std::string> GetCurrentDir() {
300 LOG_ERROR(Common_Filesystem, "Unable to set {} as working directory: {}", path.string(), 541// Get the current working directory (getcwd uses malloc)
301 ec.message()); 542#ifdef _WIN32
302 return false; 543 wchar_t* dir = _wgetcwd(nullptr, 0);
544 if (!dir) {
545#else
546 char* dir = getcwd(nullptr, 0);
547 if (!dir) {
548#endif
549 LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
550 return std::nullopt;
303 } 551 }
552#ifdef _WIN32
553 std::string strDir = Common::UTF16ToUTF8(dir);
554#else
555 std::string strDir = dir;
556#endif
557 free(dir);
558 return strDir;
559}
304 560
305 return true; 561bool SetCurrentDir(const std::string& directory) {
562#ifdef _WIN32
563 return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
564#else
565 return chdir(directory.c_str()) == 0;
566#endif
306} 567}
307 568
308#if defined(__APPLE__) 569#if defined(__APPLE__)