summaryrefslogtreecommitdiff
path: root/src/common/file_util.cpp
diff options
context:
space:
mode:
authorGravatar Rodrigo Locatti2020-12-09 03:47:21 -0300
committerGravatar GitHub2020-12-09 03:47:21 -0300
commitce5fcb6bb2c358b0251a2ce87945bda52789a76d (patch)
tree9946623b410e4400c4c418a12a3052111042ac6d /src/common/file_util.cpp
parentMerge pull request #5166 from lioncash/log-cast (diff)
parentfile_util: Migrate remaining file handling functions over to std::filesystem (diff)
downloadyuzu-ce5fcb6bb2c358b0251a2ce87945bda52789a76d.tar.gz
yuzu-ce5fcb6bb2c358b0251a2ce87945bda52789a76d.tar.xz
yuzu-ce5fcb6bb2c358b0251a2ce87945bda52789a76d.zip
Merge pull request #5173 from lioncash/common-fs
common/file_util: Make use of std::filesystem
Diffstat (limited to 'src/common/file_util.cpp')
-rw-r--r--src/common/file_util.cpp439
1 files changed, 89 insertions, 350 deletions
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 18fbfa25b..d5f6ea2ee 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,102 @@
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}
111
112bool IsDirectory(const std::string& filename) {
113 struct stat file_info;
114
115 std::string copy(filename);
116 StripTailDirSlashes(copy);
117
118#ifdef _WIN32
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;
131 }
132
133 return S_ISDIR(file_info.st_mode);
134} 82}
135 83
136bool Delete(const std::string& filename) { 84bool Delete(const fs::path& path) {
137 LOG_TRACE(Common_Filesystem, "file {}", filename); 85 LOG_TRACE(Common_Filesystem, "file {}", path.string());
138 86
139 // Return true because we care about the file no 87 // Return true because we care about the file no
140 // being there, not the actual delete. 88 // being there, not the actual delete.
141 if (!Exists(filename)) { 89 if (!Exists(path)) {
142 LOG_DEBUG(Common_Filesystem, "{} does not exist", filename); 90 LOG_DEBUG(Common_Filesystem, "{} does not exist", path.string());
143 return true; 91 return true;
144 } 92 }
145 93
146 // We can't delete a directory 94 std::error_code ec;
147 if (IsDirectory(filename)) { 95 return fs::remove(path, ec);
148 LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename); 96}
149 return false;
150 }
151 97
152#ifdef _WIN32 98bool CreateDir(const fs::path& path) {
153 if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) { 99 LOG_TRACE(Common_Filesystem, "directory {}", path.string());
154 LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg()); 100
155 return false; 101 std::error_code ec;
156 } 102 const bool success = fs::create_directory(path, ec);
157#else 103
158 if (unlink(filename.c_str()) == -1) { 104 if (!success) {
159 LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg()); 105 LOG_ERROR(Common_Filesystem, "Unable to create directory: {}", ec.message());
160 return false; 106 return false;
161 } 107 }
162#endif
163 108
164 return true; 109 return true;
165} 110}
166 111
167bool CreateDir(const std::string& path) { 112bool CreateFullPath(const fs::path& path) {
168 LOG_TRACE(Common_Filesystem, "directory {}", path); 113 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 114
183 int err = errno; 115 std::error_code ec;
116 const bool success = fs::create_directories(path, ec);
184 117
185 if (err == EEXIST) { 118 if (!success) {
186 LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path); 119 LOG_ERROR(Common_Filesystem, "Unable to create full path: {}", ec.message());
187 return true; 120 return false;
188 } 121 }
189 122
190 LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err)); 123 return true;
191 return false;
192#endif
193} 124}
194 125
195bool CreateFullPath(const std::string& fullPath) { 126bool Rename(const fs::path& src, const fs::path& dst) {
196 int panicCounter = 100; 127 LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string());
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);
208 128
209 // we're done, yay! 129 std::error_code ec;
210 if (position == fullPath.npos) 130 fs::rename(src, dst, ec);
211 return true;
212 131
213 // Include the '/' so the first call is CreateDir("/") rather than CreateDir("") 132 if (ec) {
214 std::string const subPath(fullPath.substr(0, position + 1)); 133 LOG_ERROR(Common_Filesystem, "Unable to rename file from {} to {}: {}", src.string(),
215 if (!IsDirectory(subPath) && !CreateDir(subPath)) { 134 dst.string(), ec.message());
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);
236 return false; 135 return false;
237 } 136 }
238 137
239#ifdef _WIN32 138 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} 139}
265 140
266bool Copy(const std::string& srcFilename, const std::string& destFilename) { 141bool Copy(const fs::path& src, const fs::path& dst) {
267 LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); 142 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 143
273 LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, 144 std::error_code ec;
274 GetLastErrorMsg()); 145 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 146
279 // Open input file 147 if (!success) {
280 CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose}; 148 LOG_ERROR(Common_Filesystem, "Unable to copy file {} to {}: {}", src.string(), dst.string(),
281 if (!input) { 149 ec.message());
282 LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
283 destFilename, GetLastErrorMsg());
284 return false; 150 return false;
285 } 151 }
286 152
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; 153 return true;
318#endif
319} 154}
320 155
321u64 GetSize(const std::string& filename) { 156u64 GetSize(const fs::path& path) {
322 if (!Exists(filename)) { 157 std::error_code ec;
323 LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename); 158 const auto size = fs::file_size(path, ec);
324 return 0;
325 }
326 159
327 if (IsDirectory(filename)) { 160 if (ec) {
328 LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename); 161 LOG_ERROR(Common_Filesystem, "Unable to retrieve file size ({}): {}", path.string(),
162 ec.message());
329 return 0; 163 return 0;
330 } 164 }
331 165
332 struct stat buf; 166 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} 167}
355 168
356u64 GetSize(FILE* f) { 169u64 GetSize(FILE* f) {
@@ -438,132 +251,58 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
438 return true; 251 return true;
439} 252}
440 253
441u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, 254bool DeleteDirRecursively(const fs::path& path) {
442 unsigned int recursion) { 255 std::error_code ec;
443 const auto callback = [recursion, &parent_entry](u64* num_entries_out, 256 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
479 if (IsDirectory(new_path)) {
480 if (recursion == 0) {
481 return false;
482 }
483 return DeleteDirRecursively(new_path, recursion - 1);
484 }
485 return Delete(new_path);
486 };
487 257
488 if (!ForeachDirectoryEntry(nullptr, directory, callback)) 258 if (ec) {
259 LOG_ERROR(Common_Filesystem, "Unable to completely delete directory {}: {}", path.string(),
260 ec.message());
489 return false; 261 return false;
262 }
490 263
491 // Delete the outermost directory
492 DeleteDir(directory);
493 return true; 264 return true;
494} 265}
495 266
496void CopyDir([[maybe_unused]] const std::string& source_path, 267void CopyDir(const fs::path& src, const fs::path& dst) {
497 [[maybe_unused]] const std::string& dest_path) { 268 constexpr auto copy_flags = fs::copy_options::skip_existing | fs::copy_options::recursive;
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 }
508 269
509 DIR* dirp = opendir(source_path.c_str()); 270 std::error_code ec;
510 if (!dirp) { 271 fs::copy(src, dst, copy_flags, ec);
272
273 if (ec) {
274 LOG_ERROR(Common_Filesystem, "Error copying directory {} to {}: {}", src.string(),
275 dst.string(), ec.message());
511 return; 276 return;
512 } 277 }
513 278
514 while (struct dirent* result = readdir(dirp)) { 279 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} 280}
539 281
540std::optional<std::string> GetCurrentDir() { 282std::optional<fs::path> GetCurrentDir() {
541// Get the current working directory (getcwd uses malloc) 283 std::error_code ec;
542#ifdef _WIN32 284 auto path = fs::current_path(ec);
543 wchar_t* dir = _wgetcwd(nullptr, 0); 285
544 if (!dir) { 286 if (ec) {
545#else 287 LOG_ERROR(Common_Filesystem, "Unable to retrieve current working directory: {}",
546 char* dir = getcwd(nullptr, 0); 288 ec.message());
547 if (!dir) {
548#endif
549 LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
550 return std::nullopt; 289 return std::nullopt;
551 } 290 }
552#ifdef _WIN32 291
553 std::string strDir = Common::UTF16ToUTF8(dir); 292 return {std::move(path)};
554#else
555 std::string strDir = dir;
556#endif
557 free(dir);
558 return strDir;
559} 293}
560 294
561bool SetCurrentDir(const std::string& directory) { 295bool SetCurrentDir(const fs::path& path) {
562#ifdef _WIN32 296 std::error_code ec;
563 return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0; 297 fs::current_path(path, ec);
564#else 298
565 return chdir(directory.c_str()) == 0; 299 if (ec) {
566#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;
567} 306}
568 307
569#if defined(__APPLE__) 308#if defined(__APPLE__)