diff options
Diffstat (limited to 'src/common/file_util.cpp')
| -rw-r--r-- | src/common/file_util.cpp | 378 |
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! | ||
| 78 | namespace Common::FS { | 71 | namespace Common::FS { |
| 79 | namespace fs = std::filesystem; | 72 | namespace 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 | ||
| 91 | bool Delete(const std::string& filename) { | 84 | bool 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 | 98 | bool 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 | ||
| 122 | bool CreateDir(const std::string& path) { | 112 | bool 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 | ||
| 150 | bool CreateFullPath(const std::string& fullPath) { | 126 | bool 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 | |||
| 185 | bool 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 | |||
| 206 | bool 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 | ||
| 221 | bool Copy(const std::string& srcFilename, const std::string& destFilename) { | 141 | bool 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 | ||
| 276 | u64 GetSize(const std::string& filename) { | 156 | u64 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 | |||
| 302 | u64 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 | ||
| 311 | u64 GetSize(FILE* f) { | 169 | u64 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 | ||
| 396 | u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, | 254 | bool 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 | |||
| 429 | bool 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 | ||
| 451 | void CopyDir([[maybe_unused]] const std::string& source_path, | 267 | void 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 | ||
| 495 | std::optional<std::string> GetCurrentDir() { | 282 | std::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 | ||
| 516 | bool SetCurrentDir(const std::string& directory) { | 295 | bool 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__) |