From 8a351b047f34e107c4e1d35e69b107e8dbe7a7bb Mon Sep 17 00:00:00 2001 From: Rye Mutt <rye@alchemyviewer.org> Date: Wed, 3 Mar 2021 16:33:25 -0500 Subject: [PATCH] Large cleanup of LLFileSystem to remove error-prone api and file path usage --- indra/llcommon/llfile.cpp | 138 +++++++++++++++++++++++++ indra/llcommon/llfile.h | 14 ++- indra/llfilesystem/llfilesystem.cpp | 135 ++++++++++++------------ indra/llfilesystem/llfilesystem.h | 9 +- indra/newview/llfloateruipreview.cpp | 6 +- indra/newview/llviewerassetstorage.cpp | 2 +- 6 files changed, 226 insertions(+), 78 deletions(-) diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index 974a7dd6d62..ff25f1fde5e 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -173,6 +173,70 @@ int warnif(const std::string& desc, const std::string& filename, int rc, int acc return rc; } +int warnif(const std::string& desc, const boost::filesystem::path& filename, int rc, int accept = 0) +{ + if (rc < 0) + { + // Capture errno before we start emitting output + int errn = errno; + // For certain operations, a particular errno value might be + // acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit + // EEXIST. Don't warn if caller explicitly says this errno is okay. + if (errn != accept) + { +#if LL_WINDOWS + LL_WARNS("LLFile") << "Couldn't " << desc << " '" << ll_convert_wide_to_string(filename.native()) + << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL; +#else + LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename.native() + << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL; +#endif + } +#if 0 && LL_WINDOWS // turn on to debug file-locking problems + // If the problem is "Permission denied," maybe it's because another + // process has the file open. Try to find out. + if (errn == EACCES) // *not* EPERM + { + // Only do any of this stuff (before LL_ENDL) if it will be logged. + LL_DEBUGS("LLFile") << empty; + // would be nice to use LLDir for this, but dependency goes the + // wrong way + const char* TEMP = LLFile::tmpdir(); + if (!(TEMP && *TEMP)) + { + LL_CONT << "No $TEMP, not running 'handle'"; + } + else + { + std::string tf(TEMP); + tf += "\\handle.tmp"; + // http://technet.microsoft.com/en-us/sysinternals/bb896655 + std::string cmd(STRINGIZE("handle \"" << filename + // "openfiles /query /v | fgrep -i \"" << filename + << "\" > \"" << tf << '"')); + LL_CONT << cmd; + if (system(cmd.c_str()) != 0) + { + LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655"; + } + else + { + std::ifstream inf(tf); + std::string line; + while (std::getline(inf, line)) + { + LL_CONT << '\n' << line; + } + } + LLFile::remove(tf); + } + LL_CONT << LL_ENDL; + } +#endif // LL_WINDOWS hack to identify processes holding file open + } + return rc; +} + // static int LLFile::mkdir(const std::string& dirname, int perms) { @@ -220,6 +284,16 @@ LLFILE* LLFile::fopen(const std::string& filename, const char* mode) /* Flawfind #endif } +// static +LLFILE* LLFile::fopen(const boost::filesystem::path& filename, MODE_T accessmode) /* Flawfinder: ignore */ +{ +#if LL_WINDOWS + return _wfopen(filename.c_str(), accessmode); +#else + return ::fopen(filename.c_str(), accessmode); /* Flawfinder: ignore */ +#endif +} + LLFILE* LLFile::_fsopen(const std::string& filename, const char* mode, int sharingFlag) { #if LL_WINDOWS @@ -254,6 +328,16 @@ int LLFile::remove(const std::string& filename, int supress_error) return warnif("remove", filename, rc, supress_error); } +int LLFile::remove(const boost::filesystem::path& filename, int supress_error) +{ +#if LL_WINDOWS + int rc = _wremove(filename.c_str()); +#else + int rc = ::remove(filename.c_str()); +#endif + return warnif("remove", filename, rc, supress_error); +} + int LLFile::rename(const std::string& filename, const std::string& newname, int supress_error) { #if LL_WINDOWS @@ -297,6 +381,48 @@ int LLFile::rename(const std::string& filename, const std::string& newname, int return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, supress_error); } +int LLFile::rename(const boost::filesystem::path& filename, const boost::filesystem::path& newname, int supress_error) +{ +#if LL_WINDOWS + int rc = _wrename(filename.c_str(), newname.c_str()); + return warnif(STRINGIZE("rename to '" << ll_convert_wide_to_string(newname.native()) << "' from"), filename, rc, supress_error); +#else + int rc = ::rename(filename.c_str(), newname.c_str()); + // Note: This workaround generalises the solution previously applied in llxfer_file. + // In doing this we add more failure modes to the operation, the copy can fail, the unlink can fail, in fact the copy can fail for multiple reasons. + // "A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools." - Douglas Adams, Mostly harmless + if (rc) + { + S32 error_number = errno; + LL_INFOS("LLFile") << "Rename failure (" << error_number << ") - " << filename << " to " << newname << LL_ENDL; + if (EXDEV == error_number) + { + if (copy(filename, newname) == true) // sigh in their wisdom LL decided that copy returns bool true not 0 whjen no error. using == true to make that clear. + { + LL_INFOS("LLFile") << "Rename across mounts not supported; copying+unlinking the file instead." << LL_ENDL; + rc = LLFile::remove(filename); + if (rc) + { + LL_WARNS("LLFile") << "unlink failed during copy/unlink workaround. Please check for stray file: " << filename << LL_ENDL; + } + } + else + { + LL_WARNS("LLFile") << "Copy failure during rename workaround for rename " << filename << " to " << newname << " (check both filenames and maunally rectify)" << LL_ENDL; + } + rc = 0; // We need to reset rc here to avoid the higher level function taking corrective action on what could be bad files. + } + else + { + LL_WARNS("LLFile") << "Rename fatally failed, no workaround attempted for errno=" + << errno << "." << LL_ENDL; + // rc will propogate alllowing corrective action above. Not entirely happy with this but it is what already happens so we're not making it worse. + } + } + return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, supress_error); +#endif +} + bool LLFile::copy(const std::string& from, const std::string& to) { bool copied = false; @@ -341,6 +467,18 @@ int LLFile::stat(const std::string& filename, llstat* filestatus) return warnif("stat", filename, rc, ENOENT); } +int LLFile::stat(const boost::filesystem::path& filename, llstat* filestatus) +{ +#if LL_WINDOWS + int rc = _wstat(filename.c_str(), filestatus); +#else + int rc = ::stat(filename.c_str(), filestatus); +#endif + // We use stat() to determine existence (see isfile(), isdir()). + // Don't spam the log if the subject pathname doesn't exist. + return warnif("stat", filename, rc, ENOENT); +} + bool LLFile::isdir(const std::string& filename) { llstat st; diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index a2f3a12aca1..7bc56247314 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -58,11 +58,20 @@ typedef struct stat llstat; #include "llstring.h" // safe char* -> std::string conversion +#include <boost/filesystem.hpp> + +#if LL_WINDOWS +#define MODE_T const wchar_t* +#else +#define MODE_T const char* +#endif + class LL_COMMON_API LLFile { public: // All these functions take UTF8 path/filenames. static LLFILE* fopen(const std::string& filename,const char* accessmode); /* Flawfinder: ignore */ + static LLFILE* fopen(const boost::filesystem::path& filename, MODE_T accessmode); /* Flawfinder: ignore */ static LLFILE* _fsopen(const std::string& filename,const char* accessmode,int sharingFlag); static int close(LLFILE * file); @@ -74,10 +83,13 @@ class LL_COMMON_API LLFile static int rmdir(const std::string& filename); static int remove(const std::string& filename, int supress_error = 0); + static int remove(const boost::filesystem::path& filename, int supress_error = 0); static int rename(const std::string& filename,const std::string& newname, int supress_error = 0); + static int rename(const boost::filesystem::path& filename, const boost::filesystem::path& newname, int supress_error = 0); static bool copy(const std::string& from, const std::string& to); - static int stat(const std::string& filename,llstat* file_status); + static int stat(const std::string& filename, llstat* file_status); + static int stat(const boost::filesystem::path& filename, llstat* file_status); static bool isdir(const std::string& filename); static bool isfile(const std::string& filename); static LLFILE * _Fiopen(const std::string& filename, diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp index 25b335fc1b6..4bc3785a0dd 100644 --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -36,84 +36,52 @@ #include <boost/filesystem.hpp> -static LLTrace::BlockTimerStatHandle FTM_VFILE_WAIT("VFile Wait"); +#ifndef TEXT +#define TEXT(quote) +#endif LLFileSystem::LLFileSystem(const LLUUID& file_id, const LLAssetType::EType file_type, S32 mode) + : mFileID(file_id), + mFileType(file_type), + mPosition(0), + mMode(mode), + mBytesRead(0) { - mFileType = file_type; - mFileID = file_id; - mPosition = 0; - mBytesRead = 0; - mMode = mode; + const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type); +#if LL_WINDOWS + mFilePath = ll_convert_string_to_wide(filename); +#else + mFilePath = filename; +#endif } // static bool LLFileSystem::getExists(const LLUUID& file_id, const LLAssetType::EType file_type) { - const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type); - - llstat stat; - if (LLFile::stat(filename, &stat) == 0) - { - return S_ISREG(stat.st_mode) && stat.st_size > 0; - } - return false; + LLFileSystem file(file_id, file_type, READ); + return file.exists(); } // static bool LLFileSystem::removeFile(const LLUUID& file_id, const LLAssetType::EType file_type) { - const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type); - - LLFile::remove(filename, ENOENT); - - return true; + LLFileSystem file(file_id, file_type, READ_WRITE); + return file.remove(); } // static bool LLFileSystem::renameFile(const LLUUID& old_file_id, const LLAssetType::EType old_file_type, const LLUUID& new_file_id, const LLAssetType::EType new_file_type) { - const std::string old_filename = LLDiskCache::metaDataToFilepath(old_file_id, old_file_type); - const std::string new_filename = LLDiskCache::metaDataToFilepath(new_file_id, new_file_type); - - // Rename needs the new file to not exist. - LLFile::remove(new_filename, ENOENT); - - if (LLFile::rename(old_filename, new_filename) != 0) - { - // We would like to return FALSE here indicating the operation - // failed but the original code does not and doing so seems to - // break a lot of things so we go with the flow... - //return FALSE; - LL_WARNS() << "Failed to rename " << old_file_id << " to " << new_file_id << " reason: " << strerror(errno) << LL_ENDL; - } - - return TRUE; -} - -// static -S32 LLFileSystem::getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type) -{ - const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type); - - S32 file_size = 0; - llstat stat; - if (LLFile::stat(filename, &stat) == 0) - { - file_size = stat.st_size; - } - - return file_size; + LLFileSystem file(old_file_id, old_file_type, READ_WRITE); + return file.rename(new_file_id, new_file_type); } BOOL LLFileSystem::read(U8* buffer, S32 bytes) { BOOL success = TRUE; - const std::string filename = LLDiskCache::metaDataToFilepath(mFileID, mFileType); - - LLUniqueFile filep = LLFile::fopen(filename, "rb"); + LLUniqueFile filep = LLFile::fopen(mFilePath, TEXT("rb")); if (filep) { fseek(filep, mPosition, SEEK_SET); @@ -158,7 +126,7 @@ BOOL LLFileSystem::read(U8* buffer, S32 bytes) // even though we are reading and not writing because this is the // way the cache works - it relies on a valid "last accessed time" for // each file so it knows how to remove the oldest, unused files - updateFileAccessTime(filename); + updateFileAccessTime(); return success; } @@ -175,13 +143,11 @@ BOOL LLFileSystem::eof() BOOL LLFileSystem::write(const U8* buffer, S32 bytes) { - const std::string filename = LLDiskCache::metaDataToFilepath(mFileID, mFileType); - BOOL success = FALSE; if (mMode == APPEND) { - LLUniqueFile filep = LLFile::fopen(filename, "ab"); + LLUniqueFile filep = LLFile::fopen(mFilePath, TEXT("ab")); if (filep) { fwrite((const void*)buffer, bytes, 1, filep); @@ -191,7 +157,7 @@ BOOL LLFileSystem::write(const U8* buffer, S32 bytes) } else { - LLUniqueFile filep = LLFile::fopen(filename, "wb"); + LLUniqueFile filep = LLFile::fopen(mFilePath, TEXT("wb")); if (filep) { fwrite((const void*)buffer, bytes, 1, filep); @@ -242,7 +208,14 @@ S32 LLFileSystem::tell() const S32 LLFileSystem::getSize() { - return LLFileSystem::getFileSize(mFileID, mFileType); + S32 file_size = 0; + llstat stat; + if (LLFile::stat(mFilePath.c_str(), &stat) == 0) + { + file_size = stat.st_size; + } + + return file_size; } S32 LLFileSystem::getMaxSize() @@ -253,22 +226,50 @@ S32 LLFileSystem::getMaxSize() BOOL LLFileSystem::rename(const LLUUID& new_id, const LLAssetType::EType new_type) { - LLFileSystem::renameFile(mFileID, mFileType, new_id, new_type); +#if LL_WINDOWS + boost::filesystem::path new_filename = ll_convert_string_to_wide(LLDiskCache::metaDataToFilepath(new_id, new_type)); +#else + boost::filesystem::path new_filename = LLDiskCache::metaDataToFilepath(new_id, new_type); +#endif + + // Rename needs the new file to not exist. + LLFile::remove(new_filename, ENOENT); + + if (LLFile::rename(mFilePath, new_filename) != 0) + { + // We would like to return FALSE here indicating the operation + // failed but the original code does not and doing so seems to + // break a lot of things so we go with the flow... + //return FALSE; + LL_WARNS() << "Failed to rename " << mFileID << " to " << new_id << " reason: " << strerror(errno) << LL_ENDL; + } mFileID = new_id; mFileType = new_type; + mFilePath = std::move(new_filename); + return TRUE; } BOOL LLFileSystem::remove() { - LLFileSystem::removeFile(mFileID, mFileType); + LLFile::remove(mFilePath, ENOENT); return TRUE; } -void LLFileSystem::updateFileAccessTime(const std::string& file_path) +BOOL LLFileSystem::exists() +{ + llstat stat; + if (LLFile::stat(mFilePath, &stat) == 0) + { + return S_ISREG(stat.st_mode) && stat.st_size > 0; + } + return false; +} + +void LLFileSystem::updateFileAccessTime() { /** * Threshold in time_t units that is used to decide if the last access time @@ -284,14 +285,8 @@ void LLFileSystem::updateFileAccessTime(const std::string& file_path) // current time const std::time_t cur_time = std::time(nullptr); -#if LL_WINDOWS - boost::filesystem::path path(ll_convert_string_to_wide(file_path)); -#else - boost::filesystem::path path(file_path); -#endif - // file last write time - const std::time_t last_write_time = boost::filesystem::last_write_time(path); + const std::time_t last_write_time = boost::filesystem::last_write_time(mFilePath); // delta between cur time and last time the file was written const std::time_t delta_time = cur_time - last_write_time; @@ -300,6 +295,6 @@ void LLFileSystem::updateFileAccessTime(const std::string& file_path) // before the last one if (delta_time > time_threshold) { - boost::filesystem::last_write_time(path, cur_time); + boost::filesystem::last_write_time(mFilePath, cur_time); } } diff --git a/indra/llfilesystem/llfilesystem.h b/indra/llfilesystem/llfilesystem.h index d4d5fa2a58a..63b46e8e759 100644 --- a/indra/llfilesystem/llfilesystem.h +++ b/indra/llfilesystem/llfilesystem.h @@ -34,6 +34,8 @@ #include "llassettype.h" #include "lldiskcache.h" +#include <boost/filesystem.hpp> + class LLFileSystem { public: @@ -52,19 +54,19 @@ class LLFileSystem S32 getMaxSize(); BOOL rename(const LLUUID& new_id, const LLAssetType::EType new_type); BOOL remove(); + BOOL exists(); /** * Update the "last write time" of a file to "now". This must be called whenever a * file in the cache is read (not written) so that the last time the file was * accessed is up to date (This is used in the mechanism for purging the cache) */ - void updateFileAccessTime(const std::string& file_path); + void updateFileAccessTime(); static bool getExists(const LLUUID& file_id, const LLAssetType::EType file_type); static bool removeFile(const LLUUID& file_id, const LLAssetType::EType file_type); static bool renameFile(const LLUUID& old_file_id, const LLAssetType::EType old_file_type, const LLUUID& new_file_id, const LLAssetType::EType new_file_type); - static S32 getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type); public: enum @@ -76,8 +78,9 @@ class LLFileSystem }; protected: - LLAssetType::EType mFileType; + boost::filesystem::path mFilePath; LLUUID mFileID; + LLAssetType::EType mFileType; S32 mPosition; S32 mMode; S32 mBytesRead; diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp index 03968415216..6029f6aba99 100644 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -890,7 +890,7 @@ void LLFloaterUIPreview::displayFloater(BOOL click, S32 ID) std::string full_path = getLocalizedDirectory() + path; std::string floater_lang = "EN"; llstat dummy; - if(!LLFile::stat(full_path.c_str(), &dummy)) // if the file does not exist + if(!LLFile::stat(full_path, &dummy)) // if the file does not exist { floater_lang = getLocStr(ID); } @@ -965,7 +965,7 @@ void LLFloaterUIPreview::onClickEditFloater() // stat file to see if it exists (some localized versions may not have it there are no diffs, and then we try to open an nonexistent file) llstat dummy; - if(LLFile::stat(file_path.c_str(), &dummy)) // if the file does not exist + if(LLFile::stat(file_path, &dummy)) // if the file does not exist { popupAndPrintWarning("No file for this floater exists in the selected localization. Opening the EN version instead."); file_path = get_xui_dir() + mDelim + "en" + mDelim + file_name; // open the en version instead, by default @@ -1119,7 +1119,7 @@ void LLFloaterUIPreview::onClickToggleDiffHighlighting() } llstat dummy; - if(LLFile::stat(path_in_textfield.c_str(), &dummy) && !error) // check if the file exists (empty check is reduntant but useful for the informative error message) + if(LLFile::stat(path_in_textfield, &dummy) && !error) // check if the file exists (empty check is reduntant but useful for the informative error message) { std::string warning = std::string("Unable to highlight differences because an invalid path to a difference file was provided:\"") + path_in_textfield + "\""; popupAndPrintWarning(warning); diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index 5af87d07329..2542e857346 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -186,7 +186,7 @@ void LLViewerAssetStorage::storeAssetData( else { // LLAssetStorage metric: Successful Request - S32 size = LLFileSystem::getFileSize(asset_id, asset_type); + S32 size = vfile.getSize(); const char *message = "Added to upload queue"; reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, size, MR_OKAY, __FILE__, __LINE__, message ); -- GitLab