diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index be3065487..2a09adfd5 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -1611,27 +1611,32 @@ bool FileSystem::EnsureDirectoryExists(const char* path, bool recursive, Error* return FileSystem::CreateDirectory(path, recursive, error); } -bool FileSystem::RecursiveDeleteDirectory(const char* path) +bool FileSystem::RecursiveDeleteDirectory(const char* path, Error* error) { FindResultsArray results; if (FindFiles(path, "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES, &results)) { for (const FILESYSTEM_FIND_DATA& fd : results) { - if (fd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) + // don't recurse into symlinked directories, just remove the link itself + if ((fd.Attributes & (FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY | FILESYSTEM_FILE_ATTRIBUTE_LINK)) == + FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) { - if (!RecursiveDeleteDirectory(fd.FileName.c_str())) + if (!RecursiveDeleteDirectory(fd.FileName.c_str(), error)) return false; } else { - if (!DeleteFile(fd.FileName.c_str())) + if (!DeleteFile(fd.FileName.c_str(), error)) + { + Error::AddPrefixFmt(error, "Failed to delete {}: ", fd.FileName); return false; + } } } } - return DeleteDirectory(path); + return DeleteDirectory(path, error); } bool FileSystem::CopyFilePath(const char* source, const char* destination, bool replace) @@ -2156,17 +2161,32 @@ bool FileSystem::CreateDirectory(const char* Path, bool Recursive, Error* error) bool FileSystem::DeleteFile(const char* path, Error* error) { const std::wstring wpath = GetWin32Path(path); + + // Need to handle both links/junctions and files as per unix. const DWORD fileAttributes = GetFileAttributesW(wpath.c_str()); - if (fileAttributes == INVALID_FILE_ATTRIBUTES || fileAttributes & FILE_ATTRIBUTE_DIRECTORY) + if (fileAttributes == INVALID_FILE_ATTRIBUTES || + ((fileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) == FILE_ATTRIBUTE_DIRECTORY)) { Error::SetStringView(error, "File does not exist."); return false; } - if (!DeleteFileW(wpath.c_str())) + // if it's a junction/symlink, we need to use RemoveDirectory() instead + if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - Error::SetWin32(error, "DeleteFileW() failed: ", GetLastError()); - return false; + if (!RemoveDirectoryW(wpath.c_str())) + { + Error::SetWin32(error, "RemoveDirectoryW() failed: ", GetLastError()); + return false; + } + } + else + { + if (!DeleteFileW(wpath.c_str())) + { + Error::SetWin32(error, "DeleteFileW() failed: ", GetLastError()); + return false; + } } return true; @@ -2186,10 +2206,23 @@ bool FileSystem::RenamePath(const char* old_path, const char* new_path, Error* e return true; } -bool FileSystem::DeleteDirectory(const char* path) +bool FileSystem::DeleteDirectory(const char* path, Error* error) { const std::wstring wpath = GetWin32Path(path); - return RemoveDirectoryW(wpath.c_str()); + const DWORD fileAttributes = GetFileAttributesW(wpath.c_str()); + if (fileAttributes == INVALID_FILE_ATTRIBUTES || !(fileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + Error::SetStringView(error, "File does not exist."); + return false; + } + + if (!RemoveDirectoryW(wpath.c_str())) + { + Error::SetWin32(error, "RemoveDirectoryW() failed: ", GetLastError()); + return false; + } + + return true; } std::string FileSystem::GetProgramPath() @@ -2273,10 +2306,10 @@ bool FileSystem::SetPathCompression(const char* path, bool enable) #elif !defined(__ANDROID__) -static u32 TranslateStatAttributes(struct stat& st) +static u32 TranslateStatAttributes(struct stat& st, struct stat& st_link) { return (S_ISDIR(st.st_mode) ? FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY : 0) | - (S_ISLNK(st.st_mode) ? FILESYSTEM_FILE_ATTRIBUTE_LINK : 0); + (S_ISLNK(st_link.st_mode) ? FILESYSTEM_FILE_ATTRIBUTE_LINK : 0); } static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, const char* Path, const char* Pattern, @@ -2330,12 +2363,12 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co else full_path = fmt::format("{}/{}", OriginPath, pDirEnt->d_name); - struct stat sDir; - if (stat(full_path.c_str(), &sDir) < 0) + struct stat sDir, sDirLink; + if (stat(full_path.c_str(), &sDir) < 0 || lstat(full_path.c_str(), &sDirLink) < 0) continue; FILESYSTEM_FIND_DATA outData; - outData.Attributes = TranslateStatAttributes(sDir); + outData.Attributes = TranslateStatAttributes(sDir, sDirLink); if (S_ISDIR(sDir.st_mode)) { @@ -2478,18 +2511,18 @@ bool FileSystem::StatFile(std::FILE* fp, struct stat* st, Error* error) bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd, Error* error) { // stat file - struct stat sysStatData; - if (stat(path, &sysStatData) < 0) + struct stat ssd, ssd_link; + if (stat(path, &ssd) < 0 || lstat(path, &ssd_link) < 0) { Error::SetErrno(error, "stat() failed: ", errno); return false; } // parse attributes - sd->CreationTime = sysStatData.st_ctime; - sd->ModificationTime = sysStatData.st_mtime; - sd->Attributes = TranslateStatAttributes(sysStatData); - sd->Size = S_ISREG(sysStatData.st_mode) ? sysStatData.st_size : 0; + sd->CreationTime = ssd.st_ctime; + sd->ModificationTime = ssd.st_mtime; + sd->Attributes = TranslateStatAttributes(ssd, ssd_link); + sd->Size = S_ISREG(ssd.st_mode) ? ssd.st_size : 0; // ok return true; @@ -2505,18 +2538,18 @@ bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd, Error* error) } // stat file - struct stat sysStatData; - if (fstat(fd, &sysStatData) != 0) + struct stat ssd; + if (fstat(fd, &ssd) != 0) { Error::SetErrno(error, "stat() failed: ", errno); return false; } // parse attributes - sd->CreationTime = sysStatData.st_ctime; - sd->ModificationTime = sysStatData.st_mtime; - sd->Attributes = TranslateStatAttributes(sysStatData); - sd->Size = S_ISREG(sysStatData.st_mode) ? sysStatData.st_size : 0; + sd->CreationTime = ssd.st_ctime; + sd->ModificationTime = ssd.st_mtime; + sd->Attributes = TranslateStatAttributes(ssd, ssd); + sd->Size = S_ISREG(ssd.st_mode) ? ssd.st_size : 0; return true; } @@ -2655,8 +2688,8 @@ bool FileSystem::CreateDirectory(const char* path, bool recursive, Error* error) bool FileSystem::DeleteFile(const char* path, Error* error) { - struct stat sysStatData; - if (stat(path, &sysStatData) != 0 || S_ISDIR(sysStatData.st_mode)) + struct stat sd; + if (lstat(path, &sd) != 0 || (S_ISDIR(sd.st_mode) && !S_ISLNK(sd.st_mode))) { Error::SetStringView(error, "File does not exist."); return false; @@ -2675,21 +2708,38 @@ bool FileSystem::RenamePath(const char* old_path, const char* new_path, Error* e { if (rename(old_path, new_path) != 0) { - const int err = errno; - Error::SetErrno(error, "rename() failed: ", err); + Error::SetErrno(error, "rename() failed: ", errno); return false; } return true; } -bool FileSystem::DeleteDirectory(const char* path) +bool FileSystem::DeleteDirectory(const char* path, Error* error) { - struct stat sysStatData; - if (stat(path, &sysStatData) != 0 || !S_ISDIR(sysStatData.st_mode)) + struct stat sd; + if (stat(path, &sd) != 0 || !S_ISDIR(sd.st_mode)) return false; - return (rmdir(path) == 0); + // if it's a symlink, use unlink() instead + if (S_ISLNK(sd.st_mode)) + { + if (unlink(path) != 0) + { + Error::SetErrno(error, "unlink() failed: ", errno); + return false; + } + } + else + { + if (rmdir(path) != 0) + { + Error::SetErrno(error, "rmdir() failed: ", errno); + return false; + } + } + + return true; } std::string FileSystem::GetProgramPath() diff --git a/src/common/file_system.h b/src/common/file_system.h index 654fa28e2..155e94187 100644 --- a/src/common/file_system.h +++ b/src/common/file_system.h @@ -202,10 +202,10 @@ bool CreateDirectory(const char* path, bool recursive, Error* error = nullptr); bool EnsureDirectoryExists(const char* path, bool recursive, Error* error = nullptr); /// Removes a directory. -bool DeleteDirectory(const char* path); +bool DeleteDirectory(const char* path, Error* error = nullptr); /// Recursively removes a directory and all subdirectories/files. -bool RecursiveDeleteDirectory(const char* path); +bool RecursiveDeleteDirectory(const char* path, Error* error = nullptr); /// Copies one file to another, optionally replacing it if it already exists. bool CopyFilePath(const char* source, const char* destination, bool replace); diff --git a/src/updater/updater.cpp b/src/updater/updater.cpp index e2350fe50..14b5105f7 100644 --- a/src/updater/updater.cpp +++ b/src/updater/updater.cpp @@ -133,22 +133,27 @@ bool Updater::RecursiveDeleteDirectory(const char* path, bool remove_dir) return true; #else + Error error; FileSystem::FindResultsArray results; if (FileSystem::FindFiles(path, "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES, &results)) { for (const FILESYSTEM_FIND_DATA& fd : results) { - if (fd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) + if ((fd.Attributes & (FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY | FILESYSTEM_FILE_ATTRIBUTE_LINK)) == + FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) { if (!RecursiveDeleteDirectory(fd.FileName.c_str(), true)) return false; } else { - m_progress->FormatInformation("Removing directory '{}'.", fd.FileName); - if (!FileSystem::DeleteFile(fd.FileName.c_str())) + m_progress->FormatInformation("Removing file '{}'.", fd.FileName); + if (!FileSystem::DeleteFile(fd.FileName.c_str(), &error)) + { + m_progress->FormatModalError("DeleteFile({}) failed: {}", fd.FileName, error.GetDescription()); return false; + } } } } @@ -157,7 +162,13 @@ bool Updater::RecursiveDeleteDirectory(const char* path, bool remove_dir) return true; m_progress->FormatInformation("Removing directory '{}'.", path); - return FileSystem::DeleteDirectory(path); + if (!FileSystem::DeleteDirectory(path, &error)) + { + m_progress->FormatModalError("DeleteDirectory({}) failed: {}", path, error.GetDescription()); + return false; + } + + return true; #endif }