summaryrefslogtreecommitdiff
path: root/src/common/file_util.cpp
diff options
context:
space:
mode:
authorGravatar bunnei2020-12-11 20:07:37 -0800
committerGravatar GitHub2020-12-11 20:07:37 -0800
commitc918c6480f9426518df5e741db34620aec350c48 (patch)
tree248c4a7d84ea6f1c1a7781a8490261165745ac0d /src/common/file_util.cpp
parentMerge pull request #5172 from lioncash/svc-wide (diff)
parentRevert "Merge pull request #5173 from lioncash/common-fs" (diff)
downloadyuzu-c918c6480f9426518df5e741db34620aec350c48.tar.gz
yuzu-c918c6480f9426518df5e741db34620aec350c48.tar.xz
yuzu-c918c6480f9426518df5e741db34620aec350c48.zip
Merge pull request #5187 from Morph1984/revert-stdfs
fs: Revert all std::filesystem changes
Diffstat (limited to 'src/common/file_util.cpp')
-rw-r--r--src/common/file_util.cpp445
1 files changed, 343 insertions, 102 deletions
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 8e061ff6c..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,122 +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 if (Exists(path)) { 139 // Return true because we care about the file no
102 LOG_DEBUG(Common_Filesystem, "path exists {}", path.string()); 140 // being there, not the actual delete.
141 if (!Exists(filename)) {
142 LOG_DEBUG(Common_Filesystem, "{} does not exist", filename);
103 return true; 143 return true;
104 } 144 }
105 145
106 std::error_code ec; 146 // We can't delete a directory
107 const bool success = fs::create_directory(path, ec); 147 if (IsDirectory(filename)) {
148 LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename);
149 return false;
150 }
108 151
109 if (!success) { 152#ifdef _WIN32
110 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());
111 return false; 160 return false;
112 } 161 }
162#endif
113 163
114 return true; 164 return true;
115} 165}
116 166
117bool CreateDirs(const fs::path& path) { 167bool CreateDir(const std::string& path) {
118 LOG_TRACE(Common_Filesystem, "path {}", path.string()); 168 LOG_TRACE(Common_Filesystem, "directory {}", path);
119 169#ifdef _WIN32
120 if (Exists(path)) { 170 if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr))
121 LOG_DEBUG(Common_Filesystem, "path exists {}", path.string()); 171 return true;
172 DWORD error = GetLastError();
173 if (error == ERROR_ALREADY_EXISTS) {
174 LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path);
122 return true; 175 return true;
123 } 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;
124 182
125 std::error_code ec; 183 int err = errno;
126 const bool success = fs::create_directories(path, ec);
127 184
128 if (!success) { 185 if (err == EEXIST) {
129 LOG_ERROR(Common_Filesystem, "Unable to create directories: {}", ec.message()); 186 LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
130 return false; 187 return true;
131 } 188 }
132 189
133 return true; 190 LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
191 return false;
192#endif
134} 193}
135 194
136bool CreateFullPath(const fs::path& path) { 195bool CreateFullPath(const std::string& fullPath) {
137 LOG_TRACE(Common_Filesystem, "path {}", path); 196 int panicCounter = 100;
197 LOG_TRACE(Common_Filesystem, "path {}", fullPath);
138 198
139 if (path.has_extension()) { 199 if (Exists(fullPath)) {
140 return CreateDirs(path.parent_path()); 200 LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath);
141 } else { 201 return true;
142 return CreateDirs(path);
143 } 202 }
144}
145 203
146bool Rename(const fs::path& src, const fs::path& dst) { 204 std::size_t position = 0;
147 LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string()); 205 while (true) {
206 // Find next sub path
207 position = fullPath.find(DIR_SEP_CHR, position);
148 208
149 std::error_code ec; 209 // we're done, yay!
150 fs::rename(src, dst, ec); 210 if (position == fullPath.npos)
211 return true;
151 212
152 if (ec) { 213 // Include the '/' so the first call is CreateDir("/") rather than CreateDir("")
153 LOG_ERROR(Common_Filesystem, "Unable to rename file from {} to {}: {}", src.string(), 214 std::string const subPath(fullPath.substr(0, position + 1));
154 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);
155 return false; 236 return false;
156 } 237 }
157 238
158 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;
159} 249}
160 250
161bool Copy(const fs::path& src, const fs::path& dst) { 251bool Rename(const std::string& srcFilename, const std::string& destFilename) {
162 LOG_TRACE(Common_Filesystem, "{} --> {}", src.string(), dst.string()); 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}
163 265
164 std::error_code ec; 266bool Copy(const std::string& srcFilename, const std::string& destFilename) {
165 const bool success = fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec); 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;
166 272
167 if (!success) { 273 LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
168 LOG_ERROR(Common_Filesystem, "Unable to copy file {} to {}: {}", src.string(), dst.string(), 274 GetLastErrorMsg());
169 ec.message()); 275 return false;
276#else
277 using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
278
279 // Open input file
280 CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose};
281 if (!input) {
282 LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename,
283 destFilename, GetLastErrorMsg());
170 return false; 284 return false;
171 } 285 }
172 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
173 return true; 317 return true;
318#endif
174} 319}
175 320
176u64 GetSize(const fs::path& path) { 321u64 GetSize(const std::string& filename) {
177 std::error_code ec; 322 if (!Exists(filename)) {
178 const auto size = fs::file_size(path, ec); 323 LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
324 return 0;
325 }
179 326
180 if (ec) { 327 if (IsDirectory(filename)) {
181 LOG_ERROR(Common_Filesystem, "Unable to retrieve file size ({}): {}", path.string(), 328 LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename);
182 ec.message());
183 return 0; 329 return 0;
184 } 330 }
185 331
186 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;
187} 354}
188 355
189u64 GetSize(FILE* f) { 356u64 GetSize(FILE* f) {
@@ -233,7 +400,7 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
233 } 400 }
234 // windows loop 401 // windows loop
235 do { 402 do {
236 const std::string virtual_name = std::filesystem::path(ffd.cFileName).string(); 403 const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName));
237#else 404#else
238 DIR* dirp = opendir(directory.c_str()); 405 DIR* dirp = opendir(directory.c_str());
239 if (!dirp) 406 if (!dirp)
@@ -271,58 +438,132 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
271 return true; 438 return true;
272} 439}
273 440
274bool DeleteDirRecursively(const fs::path& path) { 441u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
275 std::error_code ec; 442 unsigned int recursion) {
276 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)++;
277 464
278 if (ec) { 465 // Push into the tree
279 LOG_ERROR(Common_Filesystem, "Unable to completely delete directory {}: {}", path.string(), 466 parent_entry.children.push_back(std::move(entry));
280 ec.message()); 467 return true;
281 return false; 468 };
282 }
283 469
284 return true; 470 u64 num_entries;
471 return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
285} 472}
286 473
287void CopyDir(const fs::path& src, const fs::path& dst) { 474bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) {
288 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;
289 478
290 std::error_code ec; 479 if (IsDirectory(new_path)) {
291 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 };
292 487
293 if (ec) { 488 if (!ForeachDirectoryEntry(nullptr, directory, callback))
294 LOG_ERROR(Common_Filesystem, "Error copying directory {} to {}: {}", src.string(), 489 return false;
295 dst.string(), ec.message());
296 return;
297 }
298 490
299 LOG_TRACE(Common_Filesystem, "Successfully copied directory."); 491 // Delete the outermost directory
492 DeleteDir(directory);
493 return true;
300} 494}
301 495
302std::optional<fs::path> GetCurrentDir() { 496void CopyDir([[maybe_unused]] const std::string& source_path,
303 std::error_code ec; 497 [[maybe_unused]] const std::string& dest_path) {
304 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 }
305 508
306 if (ec) { 509 DIR* dirp = opendir(source_path.c_str());
307 LOG_ERROR(Common_Filesystem, "Unable to retrieve current working directory: {}", 510 if (!dirp) {
308 ec.message()); 511 return;
309 return std::nullopt;
310 } 512 }
311 513
312 return {std::move(path)}; 514 while (struct dirent* result = readdir(dirp)) {
313} 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 }
314 521
315bool SetCurrentDir(const fs::path& path) { 522 std::string source, dest;
316 std::error_code ec; 523 source = source_path + virtualName;
317 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}
318 539
319 if (ec) { 540std::optional<std::string> GetCurrentDir() {
320 LOG_ERROR(Common_Filesystem, "Unable to set {} as working directory: {}", path.string(), 541// Get the current working directory (getcwd uses malloc)
321 ec.message()); 542#ifdef _WIN32
322 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;
323 } 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}
324 560
325 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
326} 567}
327 568
328#if defined(__APPLE__) 569#if defined(__APPLE__)