summaryrefslogtreecommitdiff
path: root/src/common/file_util.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/common/file_util.cpp378
1 files changed, 81 insertions, 297 deletions
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 67fcef3e4..d5f6ea2ee 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -68,13 +68,6 @@
68#include <algorithm> 68#include <algorithm>
69#include <sys/stat.h> 69#include <sys/stat.h>
70 70
71#ifndef S_ISDIR
72#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
73#endif
74
75// This namespace has various generic functions related to files and paths.
76// The code still needs a ton of cleanup.
77// REMEMBER: strdup considered harmful!
78namespace Common::FS { 71namespace Common::FS {
79namespace fs = std::filesystem; 72namespace fs = std::filesystem;
80 73
@@ -88,224 +81,89 @@ bool IsDirectory(const fs::path& path) {
88 return fs::is_directory(path, ec); 81 return fs::is_directory(path, ec);
89} 82}
90 83
91bool Delete(const std::string& filename) { 84bool Delete(const fs::path& path) {
92 LOG_TRACE(Common_Filesystem, "file {}", filename); 85 LOG_TRACE(Common_Filesystem, "file {}", path.string());
93 86
94 // Return true because we care about the file no 87 // Return true because we care about the file no
95 // being there, not the actual delete. 88 // being there, not the actual delete.
96 if (!Exists(filename)) { 89 if (!Exists(path)) {
97 LOG_DEBUG(Common_Filesystem, "{} does not exist", filename); 90 LOG_DEBUG(Common_Filesystem, "{} does not exist", path.string());
98 return true; 91 return true;
99 } 92 }
100 93
101 // We can't delete a directory 94 std::error_code ec;
102 if (IsDirectory(filename)) { 95 return fs::remove(path, ec);
103 LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename); 96}
104 return false;
105 }
106 97
107#ifdef _WIN32 98bool CreateDir(const fs::path& path) {
108 if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) { 99 LOG_TRACE(Common_Filesystem, "directory {}", path.string());
109 LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg()); 100
110 return false; 101 std::error_code ec;
111 } 102 const bool success = fs::create_directory(path, ec);
112#else 103
113 if (unlink(filename.c_str()) == -1) { 104 if (!success) {
114 LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg()); 105 LOG_ERROR(Common_Filesystem, "Unable to create directory: {}", ec.message());
115 return false; 106 return false;
116 } 107 }
117#endif
118 108
119 return true; 109 return true;
120} 110}
121 111
122bool CreateDir(const std::string& path) { 112bool CreateFullPath(const fs::path& path) {
123 LOG_TRACE(Common_Filesystem, "directory {}", path); 113 LOG_TRACE(Common_Filesystem, "path {}", path.string());
124#ifdef _WIN32
125 if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
126 return true;
127 DWORD error = GetLastError();
128 if (error == ERROR_ALREADY_EXISTS) {
129 LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
130 return true;
131 }
132 LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
133 return false;
134#else
135 if (mkdir(path.c_str(), 0755) == 0)
136 return true;
137 114
138 int err = errno; 115 std::error_code ec;
116 const bool success = fs::create_directories(path, ec);
139 117
140 if (err == EEXIST) { 118 if (!success) {
141 LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path); 119 LOG_ERROR(Common_Filesystem, "Unable to create full path: {}", ec.message());
142 return true; 120 return false;
143 } 121 }
144 122
145 LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err)); 123 return true;
146 return false;
147#endif
148} 124}
149 125
150bool CreateFullPath(const std::string& fullPath) { 126bool Rename(const fs::path& src, const fs::path& dst) {
151 int panicCounter = 100; 127 LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string());
152 LOG_TRACE(Common_Filesystem, "path {}", fullPath);
153
154 if (Exists(fullPath)) {
155 LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
156 return true;
157 }
158 128
159 std::size_t position = 0; 129 std::error_code ec;
160 while (true) { 130 fs::rename(src, dst, ec);
161 // Find next sub path
162 position = fullPath.find(DIR_SEP_CHR, position);
163
164 // we're done, yay!
165 if (position == fullPath.npos)
166 return true;
167
168 // Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
169 std::string const subPath(fullPath.substr(0, position + 1));
170 if (!IsDirectory(subPath) && !CreateDir(subPath)) {
171 LOG_ERROR(Common, "CreateFullPath: directory creation failed");
172 return false;
173 }
174
175 // A safety check
176 panicCounter--;
177 if (panicCounter <= 0) {
178 LOG_ERROR(Common, "CreateFullPath: directory structure is too deep");
179 return false;
180 }
181 position++;
182 }
183}
184
185bool DeleteDir(const std::string& filename) {
186 LOG_TRACE(Common_Filesystem, "directory {}", filename);
187 131
188 // check if a directory 132 if (ec) {
189 if (!IsDirectory(filename)) { 133 LOG_ERROR(Common_Filesystem, "Unable to rename file from {} to {}: {}", src.string(),
190 LOG_ERROR(Common_Filesystem, "Not a directory {}", filename); 134 dst.string(), ec.message());
191 return false; 135 return false;
192 } 136 }
193 137
194#ifdef _WIN32 138 return true;
195 if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str()))
196 return true;
197#else
198 if (rmdir(filename.c_str()) == 0)
199 return true;
200#endif
201 LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg());
202
203 return false;
204}
205
206bool Rename(const std::string& srcFilename, const std::string& destFilename) {
207 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
208#ifdef _WIN32
209 if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
210 Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
211 return true;
212#else
213 if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
214 return true;
215#endif
216 LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
217 GetLastErrorMsg());
218 return false;
219} 139}
220 140
221bool Copy(const std::string& srcFilename, const std::string& destFilename) { 141bool Copy(const fs::path& src, const fs::path& dst) {
222 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); 142 LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string());
223#ifdef _WIN32
224 if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(),
225 Common::UTF8ToUTF16W(destFilename).c_str(), FALSE))
226 return true;
227 143
228 LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, 144 std::error_code ec;
229 GetLastErrorMsg()); 145 const bool success = fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
230 return false;
231#else
232 using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
233
234 // Open input file
235 CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose};
236 if (!input) {
237 LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
238 destFilename, GetLastErrorMsg());
239 return false;
240 }
241 146
242 // open output file 147 if (!success) {
243 CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose}; 148 LOG_ERROR(Common_Filesystem, "Unable to copy file {} to {}: {}", src.string(), dst.string(),
244 if (!output) { 149 ec.message());
245 LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename,
246 destFilename, GetLastErrorMsg());
247 return false; 150 return false;
248 } 151 }
249 152
250 // copy loop
251 std::array<char, 1024> buffer;
252 while (!feof(input.get())) {
253 // read input
254 std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get());
255 if (rnum != buffer.size()) {
256 if (ferror(input.get()) != 0) {
257 LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
258 srcFilename, destFilename, GetLastErrorMsg());
259 return false;
260 }
261 }
262
263 // write output
264 std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get());
265 if (wnum != rnum) {
266 LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
267 destFilename, GetLastErrorMsg());
268 return false;
269 }
270 }
271
272 return true; 153 return true;
273#endif
274} 154}
275 155
276u64 GetSize(const std::string& filename) { 156u64 GetSize(const fs::path& path) {
277 if (!Exists(filename)) { 157 std::error_code ec;
278 LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename); 158 const auto size = fs::file_size(path, ec);
279 return 0;
280 }
281 159
282 if (IsDirectory(filename)) { 160 if (ec) {
283 LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename); 161 LOG_ERROR(Common_Filesystem, "Unable to retrieve file size ({}): {}", path.string(),
162 ec.message());
284 return 0; 163 return 0;
285 } 164 }
286 165
287 struct stat buf; 166 return size;
288#ifdef _WIN32
289 if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0)
290#else
291 if (stat(filename.c_str(), &buf) == 0)
292#endif
293 {
294 LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size);
295 return buf.st_size;
296 }
297
298 LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg());
299 return 0;
300}
301
302u64 GetSize(const int fd) {
303 struct stat buf;
304 if (fstat(fd, &buf) != 0) {
305 LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg());
306 return 0;
307 }
308 return buf.st_size;
309} 167}
310 168
311u64 GetSize(FILE* f) { 169u64 GetSize(FILE* f) {
@@ -393,132 +251,58 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
393 return true; 251 return true;
394} 252}
395 253
396u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, 254bool DeleteDirRecursively(const fs::path& path) {
397 unsigned int recursion) { 255 std::error_code ec;
398 const auto callback = [recursion, &parent_entry](u64* num_entries_out, 256 fs::remove_all(path, ec);
399 const std::string& directory,
400 const std::string& virtual_name) -> bool {
401 FSTEntry entry;
402 entry.virtualName = virtual_name;
403 entry.physicalName = directory + DIR_SEP + virtual_name;
404
405 if (IsDirectory(entry.physicalName)) {
406 entry.isDirectory = true;
407 // is a directory, lets go inside if we didn't recurse to often
408 if (recursion > 0) {
409 entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
410 *num_entries_out += entry.size;
411 } else {
412 entry.size = 0;
413 }
414 } else { // is a file
415 entry.isDirectory = false;
416 entry.size = GetSize(entry.physicalName);
417 }
418 (*num_entries_out)++;
419
420 // Push into the tree
421 parent_entry.children.push_back(std::move(entry));
422 return true;
423 };
424
425 u64 num_entries;
426 return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
427}
428
429bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
430 const auto callback = [recursion](u64*, const std::string& directory,
431 const std::string& virtual_name) {
432 const std::string new_path = directory + DIR_SEP_CHR + virtual_name;
433
434 if (IsDirectory(new_path)) {
435 if (recursion == 0) {
436 return false;
437 }
438 return DeleteDirRecursively(new_path, recursion - 1);
439 }
440 return Delete(new_path);
441 };
442 257
443 if (!ForeachDirectoryEntry(nullptr, directory, callback)) 258 if (ec) {
259 LOG_ERROR(Common_Filesystem, "Unable to completely delete directory {}: {}", path.string(),
260 ec.message());
444 return false; 261 return false;
262 }
445 263
446 // Delete the outermost directory
447 DeleteDir(directory);
448 return true; 264 return true;
449} 265}
450 266
451void CopyDir([[maybe_unused]] const std::string& source_path, 267void CopyDir(const fs::path& src, const fs::path& dst) {
452 [[maybe_unused]] const std::string& dest_path) { 268 constexpr auto copy_flags = fs::copy_options::skip_existing | fs::copy_options::recursive;
453#ifndef _WIN32 269
454 if (source_path == dest_path) { 270 std::error_code ec;
455 return; 271 fs::copy(src, dst, copy_flags, ec);
456 }
457 if (!Exists(source_path)) {
458 return;
459 }
460 if (!Exists(dest_path)) {
461 CreateFullPath(dest_path);
462 }
463 272
464 DIR* dirp = opendir(source_path.c_str()); 273 if (ec) {
465 if (!dirp) { 274 LOG_ERROR(Common_Filesystem, "Error copying directory {} to {}: {}", src.string(),
275 dst.string(), ec.message());
466 return; 276 return;
467 } 277 }
468 278
469 while (struct dirent* result = readdir(dirp)) { 279 LOG_TRACE(Common_Filesystem, "Successfully copied directory.");
470 const std::string virtualName(result->d_name);
471 // check for "." and ".."
472 if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
473 ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) {
474 continue;
475 }
476
477 std::string source, dest;
478 source = source_path + virtualName;
479 dest = dest_path + virtualName;
480 if (IsDirectory(source)) {
481 source += '/';
482 dest += '/';
483 if (!Exists(dest)) {
484 CreateFullPath(dest);
485 }
486 CopyDir(source, dest);
487 } else if (!Exists(dest)) {
488 Copy(source, dest);
489 }
490 }
491 closedir(dirp);
492#endif
493} 280}
494 281
495std::optional<std::string> GetCurrentDir() { 282std::optional<fs::path> GetCurrentDir() {
496// Get the current working directory (getcwd uses malloc) 283 std::error_code ec;
497#ifdef _WIN32 284 auto path = fs::current_path(ec);
498 wchar_t* dir = _wgetcwd(nullptr, 0); 285
499 if (!dir) { 286 if (ec) {
500#else 287 LOG_ERROR(Common_Filesystem, "Unable to retrieve current working directory: {}",
501 char* dir = getcwd(nullptr, 0); 288 ec.message());
502 if (!dir) {
503#endif
504 LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
505 return std::nullopt; 289 return std::nullopt;
506 } 290 }
507#ifdef _WIN32 291
508 std::string strDir = Common::UTF16ToUTF8(dir); 292 return {std::move(path)};
509#else
510 std::string strDir = dir;
511#endif
512 free(dir);
513 return strDir;
514} 293}
515 294
516bool SetCurrentDir(const std::string& directory) { 295bool SetCurrentDir(const fs::path& path) {
517#ifdef _WIN32 296 std::error_code ec;
518 return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0; 297 fs::current_path(path, ec);
519#else 298
520 return chdir(directory.c_str()) == 0; 299 if (ec) {
521#endif 300 LOG_ERROR(Common_Filesystem, "Unable to set {} as working directory: {}", path.string(),
301 ec.message());
302 return false;
303 }
304
305 return true;
522} 306}
523 307
524#if defined(__APPLE__) 308#if defined(__APPLE__)