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.cpp449
1 files changed, 107 insertions, 342 deletions
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 18fbfa25b..a286b9341 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -3,6 +3,7 @@
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>
6#include <limits> 7#include <limits>
7#include <memory> 8#include <memory>
8#include <sstream> 9#include <sstream>
@@ -67,290 +68,128 @@
67#include <algorithm> 68#include <algorithm>
68#include <sys/stat.h> 69#include <sys/stat.h>
69 70
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!
77namespace Common::FS { 71namespace Common::FS {
72namespace fs = std::filesystem;
78 73
79// Remove any ending forward slashes from directory paths 74bool Exists(const fs::path& path) {
80// Modifies argument. 75 std::error_code ec;
81static void StripTailDirSlashes(std::string& fname) { 76 return fs::exists(path, ec);
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);
91} 77}
92 78
93bool Exists(const std::string& filename) { 79bool IsDirectory(const fs::path& path) {
94 struct stat file_info; 80 std::error_code ec;
95 81 return fs::is_directory(path, ec);
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);
110} 82}
111 83
112bool IsDirectory(const std::string& filename) { 84bool Delete(const fs::path& path) {
113 struct stat file_info; 85 LOG_TRACE(Common_Filesystem, "file {}", path.string());
114
115 std::string copy(filename);
116 StripTailDirSlashes(copy);
117 86
118#ifdef _WIN32 87 // Return true because we care about the file no
119 // Windows needs a slash to identify a driver root 88 // being there, not the actual delete.
120 if (copy.size() != 0 && copy.back() == ':') 89 if (!Exists(path)) {
121 copy += DIR_SEP_CHR; 90 LOG_DEBUG(Common_Filesystem, "{} does not exist", path.string());
122 91 return true;
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;
131 } 92 }
132 93
133 return S_ISDIR(file_info.st_mode); 94 std::error_code ec;
95 return fs::remove(path, ec);
134} 96}
135 97
136bool Delete(const std::string& filename) { 98bool CreateDir(const fs::path& path) {
137 LOG_TRACE(Common_Filesystem, "file {}", filename); 99 LOG_TRACE(Common_Filesystem, "directory {}", path.string());
138 100
139 // Return true because we care about the file no 101 if (Exists(path)) {
140 // being there, not the actual delete. 102 LOG_DEBUG(Common_Filesystem, "path exists {}", path.string());
141 if (!Exists(filename)) {
142 LOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
143 return true; 103 return true;
144 } 104 }
145 105
146 // We can't delete a directory 106 std::error_code ec;
147 if (IsDirectory(filename)) { 107 const bool success = fs::create_directory(path, ec);
148 LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
149 return false;
150 }
151 108
152#ifdef _WIN32 109 if (!success) {
153 if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) { 110 LOG_ERROR(Common_Filesystem, "Unable to create directory: {}", ec.message());
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());
160 return false; 111 return false;
161 } 112 }
162#endif
163 113
164 return true; 114 return true;
165} 115}
166 116
167bool CreateDir(const std::string& path) { 117bool CreateDirs(const fs::path& path) {
168 LOG_TRACE(Common_Filesystem, "directory {}", path); 118 LOG_TRACE(Common_Filesystem, "path {}", path.string());
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;
182
183 int err = errno;
184 119
185 if (err == EEXIST) { 120 if (Exists(path)) {
186 LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path); 121 LOG_DEBUG(Common_Filesystem, "path exists {}", path.string());
187 return true; 122 return true;
188 } 123 }
189 124
190 LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err)); 125 std::error_code ec;
191 return false; 126 const bool success = fs::create_directories(path, ec);
192#endif
193}
194 127
195bool CreateFullPath(const std::string& fullPath) { 128 if (!success) {
196 int panicCounter = 100; 129 LOG_ERROR(Common_Filesystem, "Unable to create directories: {}", ec.message());
197 LOG_TRACE(Common_Filesystem, "path {}", fullPath); 130 return false;
198
199 if (Exists(fullPath)) {
200 LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
201 return true;
202 } 131 }
203 132
204 std::size_t position = 0; 133 return true;
205 while (true) { 134}
206 // Find next sub path
207 position = fullPath.find(DIR_SEP_CHR, position);
208 135
209 // we're done, yay! 136bool CreateFullPath(const fs::path& path) {
210 if (position == fullPath.npos) 137 LOG_TRACE(Common_Filesystem, "path {}", path);
211 return true;
212 138
213 // Include the '/' so the first call is CreateDir("/") rather than CreateDir("") 139 // Removes trailing slashes and turns any '\' into '/'
214 std::string const subPath(fullPath.substr(0, position + 1)); 140 const auto new_path = SanitizePath(path.string(), DirectorySeparator::ForwardSlash);
215 if (!IsDirectory(subPath) && !CreateDir(subPath)) {
216 LOG_ERROR(Common, "CreateFullPath: directory creation failed");
217 return false;
218 }
219 141
220 // A safety check 142 if (new_path.rfind('.') == std::string::npos) {
221 panicCounter--; 143 // The path is a directory
222 if (panicCounter <= 0) { 144 return CreateDirs(new_path);
223 LOG_ERROR(Common, "CreateFullPath: directory structure is too deep"); 145 } else {
224 return false; 146 // The path is a file
225 } 147 // Creates directory preceding the last '/'
226 position++; 148 return CreateDirs(new_path.substr(0, new_path.rfind('/')));
227 } 149 }
228} 150}
229 151
230bool DeleteDir(const std::string& filename) { 152bool Rename(const fs::path& src, const fs::path& dst) {
231 LOG_TRACE(Common_Filesystem, "directory {}", filename); 153 LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string());
232 154
233 // check if a directory 155 std::error_code ec;
234 if (!IsDirectory(filename)) { 156 fs::rename(src, dst, ec);
235 LOG_ERROR(Common_Filesystem, "Not a directory {}", filename); 157
158 if (ec) {
159 LOG_ERROR(Common_Filesystem, "Unable to rename file from {} to {}: {}", src.string(),
160 dst.string(), ec.message());
236 return false; 161 return false;
237 } 162 }
238 163
239#ifdef _WIN32 164 return true;
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;
264} 165}
265 166
266bool Copy(const std::string& srcFilename, const std::string& destFilename) { 167bool Copy(const fs::path& src, const fs::path& dst) {
267 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); 168 LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string());
268#ifdef _WIN32
269 if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
270 Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
271 return true;
272 169
273 LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, 170 std::error_code ec;
274 GetLastErrorMsg()); 171 const bool success = fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
275 return false;
276#else
277 using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
278 172
279 // Open input file 173 if (!success) {
280 CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose}; 174 LOG_ERROR(Common_Filesystem, "Unable to copy file {} to {}: {}", src.string(), dst.string(),
281 if (!input) { 175 ec.message());
282 LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
283 destFilename, GetLastErrorMsg());
284 return false; 176 return false;
285 } 177 }
286 178
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
317 return true; 179 return true;
318#endif
319} 180}
320 181
321u64 GetSize(const std::string& filename) { 182u64 GetSize(const fs::path& path) {
322 if (!Exists(filename)) { 183 std::error_code ec;
323 LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename); 184 const auto size = fs::file_size(path, ec);
324 return 0;
325 }
326 185
327 if (IsDirectory(filename)) { 186 if (ec) {
328 LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename); 187 LOG_ERROR(Common_Filesystem, "Unable to retrieve file size ({}): {}", path.string(),
188 ec.message());
329 return 0; 189 return 0;
330 } 190 }
331 191
332 struct stat buf; 192 return size;
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;
354} 193}
355 194
356u64 GetSize(FILE* f) { 195u64 GetSize(FILE* f) {
@@ -400,7 +239,7 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
400 } 239 }
401 // windows loop 240 // windows loop
402 do { 241 do {
403 const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName)); 242 const std::string virtual_name = std::filesystem::path(ffd.cFileName).string();
404#else 243#else
405 DIR* dirp = opendir(directory.c_str()); 244 DIR* dirp = opendir(directory.c_str());
406 if (!dirp) 245 if (!dirp)
@@ -438,132 +277,58 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
438 return true; 277 return true;
439} 278}
440 279
441u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, 280bool DeleteDirRecursively(const fs::path& path) {
442 unsigned int recursion) { 281 std::error_code ec;
443 const auto callback = [recursion, &parent_entry](u64* num_entries_out, 282 fs::remove_all(path, ec);
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)++;
464
465 // Push into the tree
466 parent_entry.children.push_back(std::move(entry));
467 return true;
468 };
469
470 u64 num_entries;
471 return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
472}
473
474bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
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;
478 283
479 if (IsDirectory(new_path)) { 284 if (ec) {
480 if (recursion == 0) { 285 LOG_ERROR(Common_Filesystem, "Unable to completely delete directory {}: {}", path.string(),
481 return false; 286 ec.message());
482 }
483 return DeleteDirRecursively(new_path, recursion - 1);
484 }
485 return Delete(new_path);
486 };
487
488 if (!ForeachDirectoryEntry(nullptr, directory, callback))
489 return false; 287 return false;
288 }
490 289
491 // Delete the outermost directory
492 DeleteDir(directory);
493 return true; 290 return true;
494} 291}
495 292
496void CopyDir([[maybe_unused]] const std::string& source_path, 293void CopyDir(const fs::path& src, const fs::path& dst) {
497 [[maybe_unused]] const std::string& dest_path) { 294 constexpr auto copy_flags = fs::copy_options::skip_existing | fs::copy_options::recursive;
498#ifndef _WIN32 295
499 if (source_path == dest_path) { 296 std::error_code ec;
500 return; 297 fs::copy(src, dst, copy_flags, ec);
501 }
502 if (!Exists(source_path)) {
503 return;
504 }
505 if (!Exists(dest_path)) {
506 CreateFullPath(dest_path);
507 }
508 298
509 DIR* dirp = opendir(source_path.c_str()); 299 if (ec) {
510 if (!dirp) { 300 LOG_ERROR(Common_Filesystem, "Error copying directory {} to {}: {}", src.string(),
301 dst.string(), ec.message());
511 return; 302 return;
512 } 303 }
513 304
514 while (struct dirent* result = readdir(dirp)) { 305 LOG_TRACE(Common_Filesystem, "Successfully copied directory.");
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 }
521
522 std::string source, dest;
523 source = source_path + virtualName;
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} 306}
539 307
540std::optional<std::string> GetCurrentDir() { 308std::optional<fs::path> GetCurrentDir() {
541// Get the current working directory (getcwd uses malloc) 309 std::error_code ec;
542#ifdef _WIN32 310 auto path = fs::current_path(ec);
543 wchar_t* dir = _wgetcwd(nullptr, 0); 311
544 if (!dir) { 312 if (ec) {
545#else 313 LOG_ERROR(Common_Filesystem, "Unable to retrieve current working directory: {}",
546 char* dir = getcwd(nullptr, 0); 314 ec.message());
547 if (!dir) {
548#endif
549 LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
550 return std::nullopt; 315 return std::nullopt;
551 } 316 }
552#ifdef _WIN32 317
553 std::string strDir = Common::UTF16ToUTF8(dir); 318 return {std::move(path)};
554#else
555 std::string strDir = dir;
556#endif
557 free(dir);
558 return strDir;
559} 319}
560 320
561bool SetCurrentDir(const std::string& directory) { 321bool SetCurrentDir(const fs::path& path) {
562#ifdef _WIN32 322 std::error_code ec;
563 return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0; 323 fs::current_path(path, ec);
564#else 324
565 return chdir(directory.c_str()) == 0; 325 if (ec) {
566#endif 326 LOG_ERROR(Common_Filesystem, "Unable to set {} as working directory: {}", path.string(),
327 ec.message());
328 return false;
329 }
330
331 return true;
567} 332}
568 333
569#if defined(__APPLE__) 334#if defined(__APPLE__)