From df90cdb34a59839ddf53266d18f825be643fa440 Mon Sep 17 00:00:00 2001 From: Frederick Martian Date: Sat, 25 Oct 2025 17:08:35 +0200 Subject: [PATCH 1/2] Add a new static function LLFile::size() to determine the size of a file_name. Replace LLAPRFile::remove(), LLAPRFile::size() and LLAPRFile::isExist() with according functions from LLFile and retire these LLAPRFile methods and the never used LLAPRFile::rename(), LLAPRFile::makeDir() and LLAPRFile::removeDir() functions. Also clean up remarks about the threading safety of the APRCachePool, which is not used in these locations anymore. --- indra/llcommon/llapr.cpp | 118 -------------------------- indra/llcommon/llapr.h | 9 -- indra/llcommon/llfile.cpp | 39 +++++++++ indra/llcommon/llfile.h | 7 ++ indra/newview/llappviewer.cpp | 18 ++-- indra/newview/lltexturecache.cpp | 38 ++++----- indra/newview/llviewerassetupload.cpp | 2 +- indra/newview/llvocache.cpp | 4 +- 8 files changed, 74 insertions(+), 161 deletions(-) diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index 01763c49aab..fc69f7ae7a9 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -628,124 +628,6 @@ S32 LLAPRFile::writeEx(const std::string& filename, const void *buf, S32 offset, return (S32)bytes_written; } -//static -bool LLAPRFile::remove(const std::string& filename, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_remove(filename.c_str(), scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to remove filename: " << filename << LL_ENDL; - return false; - } - return true; -} - -//static -bool LLAPRFile::rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_rename(filename.c_str(), newname.c_str(), scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to rename filename: " << filename << LL_ENDL; - return false; - } - return true; -} - -//static -bool LLAPRFile::isExist(const std::string& filename, LLVolatileAPRPool* pool, apr_int32_t flags) -{ - apr_file_t* apr_file; - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_open(&apr_file, filename.c_str(), flags, APR_OS_DEFAULT, scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS || !apr_file) - { - return false; - } - else - { - apr_file_close(apr_file) ; - return true; - } -} - -//static -S32 LLAPRFile::size(const std::string& filename, LLVolatileAPRPool* pool) -{ - apr_file_t* apr_file; - apr_finfo_t info; - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_open(&apr_file, filename.c_str(), APR_READ, APR_OS_DEFAULT, scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS || !apr_file) - { - return 0; - } - else - { - apr_status_t s = apr_file_info_get(&info, APR_FINFO_SIZE, apr_file); - - apr_file_close(apr_file) ; - - if (s == APR_SUCCESS) - { - return (S32)info.size; - } - else - { - return 0; - } - } -} - -//static -bool LLAPRFile::makeDir(const std::string& dirname, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_dir_make(dirname.c_str(), APR_FPROT_OS_DEFAULT, scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to make directory: " << dirname << LL_ENDL; - return false; - } - return true; -} - -//static -bool LLAPRFile::removeDir(const std::string& dirname, LLVolatileAPRPool* pool) -{ - apr_status_t s; - - LLAPRFilePoolScope scope(pool); - s = apr_file_remove(dirname.c_str(), scope.getVolatileAPRPool()); - - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to remove directory: " << dirname << LL_ENDL; - return false; - } - return true; -} // //end of static components of LLAPRFile //******************************************************************************************************************************* diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index 693cd7c01fa..1045429e067 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -183,19 +183,10 @@ class LL_COMMON_API LLAPRFile : boost::noncopyable static apr_status_t close(apr_file_t* file) ; static S32 seek(apr_file_t* file, apr_seek_where_t where, S32 offset); public: - // returns false if failure: - static bool remove(const std::string& filename, LLVolatileAPRPool* pool = NULL); - static bool rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool = NULL); - static bool isExist(const std::string& filename, LLVolatileAPRPool* pool = NULL, apr_int32_t flags = APR_READ); - static S32 size(const std::string& filename, LLVolatileAPRPool* pool = NULL); - static bool makeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); - static bool removeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); - // Returns bytes read/written, 0 if read/write fails: static S32 readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); static S32 writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); // offset<0 means append //******************************************************************************************************************************* }; - #endif // LL_LLAPR_H diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index a539e4fe282..4ef39674c2c 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -497,6 +497,45 @@ int LLFile::stat(const std::string& filename, llstat* filestatus, int suppress_e return warnif("stat", filename, rc, suppress_error); } +// static +S64 LLFile::size(const std::string& filename, int suppress_error) +{ +#if LL_WINDOWS + std::wstring utf16filename = utf8path_to_wstring(filename); + HANDLE file_handle = CreateFileW(utf16filename.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE != file_handle) + { + LARGE_INTEGER file_size; + if (GetFileSizeEx(file_handle, &file_size)) + { + CloseHandle(file_handle); + return file_size.QuadPart; + } + } + // Get last error before calling the CloseHandle() function + int rc = set_errno_from_oserror(GetLastError()); + if (INVALID_HANDLE_VALUE != file_handle) + { + CloseHandle(file_handle); + } +#else + llstat filestatus; + int rc = ::stat(filename.c_str(), &filestatus); + if (rc == 0) + { + if (S_ISREG(filestatus.st_mode) + { + return filestatus.st_size; + } + // We got something else than a file + rc = -1; + errno = EACCES; + } +#endif + warnif("size", filename, rc, suppress_error); + return 0; +} + // static unsigned short LLFile::getattr(const std::string& filename, bool dontFollowSymLink, int suppress_error) { diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index 04a2946ac42..61ec8a5e4e0 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -148,6 +148,13 @@ class LL_COMMON_API LLFile /// @returns 0 on failure and a st_mode value with either S_IFDIR or S_IFREG set otherwise /// together with the three access bits which under Windows only the write bit is relevant. + /// get the size of a file in bytes + static S64 size(const std::string& filename, int suppress_error = ENOENT); + ///> returns the size of a file in bytes + /// we pass by default ENOENT in the optional 'suppress_error' parameter to not spam the log with + /// warnings when the file does not exist + /// @returns the file size on success and 0 on failure. + /// check if filename is an existing directory static bool isdir(const std::string& filename); ///< @returns true if the path is for an existing directory diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index bfd8b1dcc74..f27a43c3e33 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2271,7 +2271,7 @@ void errorCallback(LLError::ELevel level, const std::string &error_string) LLAppViewer::instance()->writeDebugInfo(); std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); - if (!LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) + if (!LLFile::isfile(error_marker_file)) { // If marker doesn't exist, create a marker with llerror code for next launch // otherwise don't override existing file @@ -3898,7 +3898,7 @@ void LLAppViewer::processMarkerFiles() bool marker_is_same_version = true; // first, look for the marker created at startup and deleted on a clean exit mMarkerFileName = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,MARKER_FILE_NAME); - if (LLAPRFile::isExist(mMarkerFileName, NULL, LL_APR_RB)) + if (LLFile::isfile(mMarkerFileName)) { // File exists... // first, read it to see if it was created by the same version (we need this later) @@ -3990,7 +3990,7 @@ void LLAppViewer::processMarkerFiles() // check for any last exec event report based on whether or not it happened during logout // (the logout marker is created when logout begins) std::string logout_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, LOGOUT_MARKER_FILE_NAME); - if(LLAPRFile::isExist(logout_marker_file, NULL, LL_APR_RB)) + if(LLFile::isfile(logout_marker_file)) { if (markerIsSameVersion(logout_marker_file)) { @@ -4001,11 +4001,11 @@ void LLAppViewer::processMarkerFiles() { LL_INFOS("MarkerFile") << "Logout crash marker '"<< logout_marker_file << "' found, but versions did not match" << LL_ENDL; } - LLAPRFile::remove(logout_marker_file); + LLFile::remove(logout_marker_file); } // and last refine based on whether or not a marker created during a non-llerr crash is found std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); - if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) + if(LLFile::isfile(error_marker_file)) { S32 marker_code = getMarkerErrorCode(error_marker_file); if (marker_code >= 0) @@ -4030,7 +4030,7 @@ void LLAppViewer::processMarkerFiles() { LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL; } - LLAPRFile::remove(error_marker_file); + LLFile::remove(error_marker_file); } } @@ -4041,7 +4041,7 @@ void LLAppViewer::removeMarkerFiles() if (mMarkerFile.getFileHandle()) { mMarkerFile.close() ; - LLAPRFile::remove( mMarkerFileName ); + LLFile::remove( mMarkerFileName ); LL_DEBUGS("MarkerFile") << "removed exec marker '"<getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); - return LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB); + return LLFile::isfile(error_marker_file); } void LLAppViewer::outOfMemorySoftQuit() diff --git a/indra/newview/lltexturecache.cpp b/indra/newview/lltexturecache.cpp index 1a7ce74ccc6..e59cf701778 100644 --- a/indra/newview/lltexturecache.cpp +++ b/indra/newview/lltexturecache.cpp @@ -180,7 +180,7 @@ class LLTextureCacheLocalFileWorker : public LLTextureCacheWorker bool LLTextureCacheLocalFileWorker::doRead() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - S32 local_size = LLAPRFile::size(mFileName, mCache->getLocalAPRFilePool()); + S32 local_size = (S32)LLFile::size(mFileName); if (local_size > 0 && mFileName.size() > 4) { @@ -296,7 +296,7 @@ bool LLTextureCacheRemoteWorker::doRead() // Is it a JPEG2000 file? { local_filename = filename + ".j2c"; - local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); + local_size = (S32)LLFile::size(local_filename); if (local_size > 0) { mImageFormat = IMG_CODEC_J2C; @@ -306,7 +306,7 @@ bool LLTextureCacheRemoteWorker::doRead() if (local_size == 0) { local_filename = filename + ".jpg"; - local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); + local_size = (S32)LLFile::size(local_filename); if (local_size > 0) { mImageFormat = IMG_CODEC_JPEG; @@ -317,7 +317,7 @@ bool LLTextureCacheRemoteWorker::doRead() if (local_size == 0) { local_filename = filename + ".tga"; - local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); + local_size = (S32)LLFile::size(local_filename); if (local_size > 0) { mImageFormat = IMG_CODEC_TGA; @@ -446,7 +446,7 @@ bool LLTextureCacheRemoteWorker::doRead() if (!done && (mState == BODY)) { std::string filename = mCache->getTextureFileName(mID); - S32 filesize = LLAPRFile::size(filename, mCache->getLocalAPRFilePool()); + S32 filesize = (S32)LLFile::size(filename); if (filesize && (filesize + TEXTURE_CACHE_ENTRY_SIZE) > mOffset) { @@ -891,7 +891,7 @@ bool LLTextureCache::isInLocal(const LLUUID& id) // Is it a JPEG2000 file? { local_filename = filename + ".j2c"; - local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); + local_size = (S32)LLFile::size(local_filename); if (local_size > 0) { return true ; @@ -901,7 +901,7 @@ bool LLTextureCache::isInLocal(const LLUUID& id) // If not, is it a jpeg file? { local_filename = filename + ".jpg"; - local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); + local_size = (S32)LLFile::size(local_filename); if (local_size > 0) { return true ; @@ -911,7 +911,7 @@ bool LLTextureCache::isInLocal(const LLUUID& id) // Hmm... What about a targa file? (used for UI texture mostly) { local_filename = filename + ".tga"; - local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); + local_size = (S32)LLFile::size(local_filename); if (local_size > 0) { return true ; @@ -966,11 +966,10 @@ void LLTextureCache::purgeCache(ELLPath location, bool remove_dir) if(LLFile::isdir(mTexturesDirName)) { std::string file_name = gDirUtilp->getExpandedFilename(location, entries_filename); - // mHeaderAPRFilePoolp because we are under header mutex, and can be in main thread - LLAPRFile::remove(file_name, mHeaderAPRFilePoolp); + LLFile::remove(file_name); file_name = gDirUtilp->getExpandedFilename(location, cache_filename); - LLAPRFile::remove(file_name, mHeaderAPRFilePoolp); + LLFile::remove(file_name); purgeAllTextures(true); } @@ -1071,7 +1070,7 @@ void LLTextureCache::readEntriesHeader() { // mHeaderEntriesInfo initializes to default values so safe not to read it llassert_always(mHeaderAPRFile == NULL); - if (LLAPRFile::isExist(mHeaderEntriesFileName, mHeaderAPRFilePoolp)) + if (LLFile::isfile(mHeaderEntriesFileName)) { LLAPRFile::readEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo), mHeaderAPRFilePoolp); @@ -1795,8 +1794,7 @@ void LLTextureCache::purgeTextures(bool validate) { std::string filename = getTextureFileName(entries[idx].mID); LL_DEBUGS("TextureCache") << "Validating: " << filename << "Size: " << entries[idx].mBodySize << LL_ENDL; - // mHeaderAPRFilePoolp because this is under header mutex in main thread - S32 bodysize = LLAPRFile::size(filename, mHeaderAPRFilePoolp); + S32 bodysize = (S32)LLFile::size(filename); if (bodysize != entries[idx].mBodySize) { LL_WARNS("TextureCache") << "TEXTURE CACHE BODY HAS BAD SIZE: " << bodysize << " != " << entries[idx].mBodySize << filename << LL_ENDL; @@ -2147,7 +2145,7 @@ void LLTextureCache::openFastCache(bool first_time) mFastCachePadBuffer = (U8*)ll_aligned_malloc_16(TEXTURE_FAST_CACHE_ENTRY_SIZE); } mFastCachePoolp = new LLVolatileAPRPool(); // is_local= true by default, so not thread safe by default - if (LLAPRFile::isExist(mFastCacheFileName, mFastCachePoolp)) + if (LLFile::isfile(mFastCacheFileName)) { mFastCachep = new LLAPRFile(mFastCacheFileName, APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ; } @@ -2230,9 +2228,7 @@ void LLTextureCache::removeCachedTexture(const LLUUID& id) mTexturesSizeMap.erase(id); } mHeaderIDMap.erase(id); - // We are inside header's mutex so mHeaderAPRFilePoolp is safe to use, - // but getLocalAPRFilePool() is not safe, it might be in use by worker - LLAPRFile::remove(getTextureFileName(id), mHeaderAPRFilePoolp); + LLFile::remove(getTextureFileName(id)); } //called after mHeaderMutex is locked. @@ -2245,9 +2241,7 @@ void LLTextureCache::removeEntry(S32 idx, Entry& entry, std::string& filename) if (entry.mBodySize == 0) // Always attempt to remove when mBodySize > 0. { // Sanity check. Shouldn't exist when body size is 0. - // We are inside header's mutex so mHeaderAPRFilePoolp is safe to use, - // but getLocalAPRFilePool() is not safe, it might be in use by worker - if (LLAPRFile::isExist(filename, mHeaderAPRFilePoolp)) + if (LLFile::isfile(filename)) { LL_WARNS("TextureCache") << "Entry has body size of zero but file " << filename << " exists. Deleting this file, too." << LL_ENDL; } @@ -2267,7 +2261,7 @@ void LLTextureCache::removeEntry(S32 idx, Entry& entry, std::string& filename) if (file_maybe_exists) { - LLAPRFile::remove(filename, mHeaderAPRFilePoolp); + LLFile::remove(filename); } } diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 7d5386110d4..556ceca0c5c 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -482,7 +482,7 @@ LLSD LLNewFileResourceUploadInfo::exportTempFile() } else { - S32 size = LLAPRFile::size(getFileName()); + S32 size = (S32)LLFile::size(getFileName()); U8* buffer = new(std::nothrow) U8[size]; if (!buffer) { diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp index 5d456b1a194..9537ad745bb 100644 --- a/indra/newview/llvocache.cpp +++ b/indra/newview/llvocache.cpp @@ -1370,7 +1370,7 @@ void LLVOCache::removeFromCache(HeaderEntryInfo* entry) std::string filename; getObjectCacheFilename(entry->mHandle, filename); LL_WARNS("GLTF", "VOCache") << "Removing object cache for handle " << entry->mHandle << "Filename: " << filename << LL_ENDL; - LLAPRFile::remove(filename, mLocalAPRFilePoolp); + LLFile::remove(filename); // Note: `removeFromCache` should take responsibility for cleaning up all cache artefacts specfic to the handle/entry. // as such this now includes the generic extras @@ -1394,7 +1394,7 @@ void LLVOCache::readCacheHeader() clearCacheInMemory(); bool success = true ; - if (LLAPRFile::isExist(mHeaderFileName, mLocalAPRFilePoolp)) + if (LLFile::isfile(mHeaderFileName)) { LLAPRFile apr_file(mHeaderFileName, APR_READ|APR_BINARY, mLocalAPRFilePoolp); From 004f5081428ad4ed36065c92f38e87efe33abbb9 Mon Sep 17 00:00:00 2001 From: Frederick Martian Date: Sun, 26 Oct 2025 19:59:02 +0100 Subject: [PATCH 2/2] Clarify some documentation and add an LLFile:read() and LLFile::write() static function. These functions will be used to replace LLAPRFile::readEx() and LLAPRFile::writeEx() in an upcoming patch. --- indra/llcommon/llfile.cpp | 157 +++++++++++++++++++++++++++++--------- indra/llcommon/llfile.h | 33 +++++--- 2 files changed, 144 insertions(+), 46 deletions(-) diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index 4ef39674c2c..9fff614efbe 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -155,12 +155,15 @@ static std::wstring utf8path_to_wstring(const std::string& utf8path) } return ll_convert(utf8path); } - -static unsigned short get_fileattr(const std::wstring& utf16path, bool dontFollowSymLink = false) +// This function retrieves the file attributes as they are used in Posix +// Like the Posix file functions it returns 0 on success, on failure it returns -1 and sets the errno variable +// if dontfollowSymlink is true it returns the attributes of the symlink itself if it is one +static int get_fileattr(const std::wstring& utf16path, unsigned short& st_mode, bool dontFollowSymLink = false) { - unsigned long flags = FILE_FLAG_BACKUP_SEMANTICS; + unsigned long flags = FILE_FLAG_BACKUP_SEMANTICS; // This allows CreateFile() to open a handle to a directory if (dontFollowSymLink) { + // This lets CreateFile() open a handle to a symlink rather than to the resolved target flags |= FILE_FLAG_OPEN_REPARSE_POINT; } HANDLE file_handle = CreateFileW(utf16path.c_str(), FILE_READ_ATTRIBUTES, @@ -175,27 +178,27 @@ static unsigned short get_fileattr(const std::wstring& utf16path, bool dontFollo bool is_directory = (attribute_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) || (iswalpha(utf16path[0]) && utf16path[1] == ':' && (!utf16path[2] || (is_slash(utf16path[2]) && !utf16path[3]))); - unsigned short st_mode = is_directory ? S_IFDIR : - (attribute_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ? S_IFLNK : S_IFREG); + st_mode = is_directory ? S_IFDIR : + (attribute_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ? S_IFLNK : S_IFREG); st_mode |= (attribute_info.FileAttributes & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD | S_IWRITE; - // we do not try to guess executable flag + // we do not try to guess the executable flag // propagate user bits to group/other fields: st_mode |= (st_mode & 0700) >> 3; st_mode |= (st_mode & 0700) >> 6; CloseHandle(file_handle); - return st_mode; + return 0; } } // Retrieve last error and set errno before calling CloseHandle() - set_errno_from_oserror(GetLastError()); + int rc = set_errno_from_oserror(GetLastError()); if (file_handle != INVALID_HANDLE_VALUE) { CloseHandle(file_handle); } - return 0; + return rc; } #else @@ -343,7 +346,11 @@ int LLFile::rmdir(const std::string& dirname, int suppress_error) { #if LL_WINDOWS std::wstring utf16dirname = utf8path_to_wstring(dirname); - int rc = _wrmdir(utf16dirname.c_str()); + if (RemoveDirectoryW(utf16dirname.c_str())) + { + return 0; + } + int rc = set_errno_from_oserror(GetLastError()); #else int rc = ::rmdir(dirname.c_str()); #endif @@ -402,27 +409,31 @@ int LLFile::remove(const std::string& filename, int suppress_error) // as its siblings unlink() and _wunlink(). // If we really only want to support files we should instead use // unlink() in the non-Windows part below too - int rc = -1; std::wstring utf16filename = utf8path_to_wstring(filename); - unsigned short st_mode = get_fileattr(utf16filename); - if (S_ISDIR(st_mode)) - { - rc = _wrmdir(utf16filename.c_str()); - } - else if (S_ISREG(st_mode)) - { - rc = _wunlink(utf16filename.c_str()); - } - else if (st_mode) - { - // it is something else than a file or directory - // this should not really happen as long as we do not allow for symlink - // detection in the optional parameter to get_fileattr() - rc = set_errno_from_oserror(ERROR_INVALID_PARAMETER); - } - else + unsigned short st_mode; + int rc = get_fileattr(utf16filename, st_mode); + if (rc == 0) { - // get_fileattr() failed and already set errno, preserve it for correct error reporting + if (S_ISDIR(st_mode)) + { + if (RemoveDirectoryW(utf16filename.c_str())) + { + return 0; + } + } + else if (S_ISREG(st_mode)) + { + if (DeleteFileW(utf16filename.c_str())) + { + return 0; + } + } + else + { + SetLastError(ERROR_INVALID_PARAMETER); + } + // Deleting the file or directory failed + rc = set_errno_from_oserror(GetLastError()); } #else int rc = ::remove(filename.c_str()); @@ -439,17 +450,91 @@ int LLFile::rename(const std::string& filename, const std::string& newname, int // API MoveFileEx() and use its flags to specify that overwrite is allowed. std::wstring utf16filename = utf8path_to_wstring(filename); std::wstring utf16newname = utf8path_to_wstring(newname); - int rc = 0; - if (!MoveFileExW(utf16filename.c_str(), utf16newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) + if (MoveFileExW(utf16filename.c_str(), utf16newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) { - rc = set_errno_from_oserror(GetLastError()); + return 0; } + int rc = set_errno_from_oserror(GetLastError()); #else int rc = ::rename(filename.c_str(),newname.c_str()); #endif return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, suppress_error); } +// static +U64 LLFile::read(const std::string& filename, void* buf, U64 offset, U64 nbytes) +{ + LLFILE* file_handle = LLFile::fopen(filename, "rb"); + if (file_handle == nullptr) + { + warnif("read, opening file", filename, -1); + return 0; + } + + if (offset > 0) + { +#if LL_WINDOWS + // On Windows fseek() uses a long value as offset which is always 32-bit + int rc = _fseeki64(file_handle, (long long)offset, SEEK_SET); +#else + // offset is of type long which is 64-bit in 64-bit GCC + int rc = fseek(file_handle, (long)offset, SEEK_SET); +#endif + if (rc) + { + warnif("read, setting file offset", filename, rc); + fclose(file_handle); + return 0; + } + } + + // element_count is of size_t which is 64-bit on all 64-bit systems + U64 bytes_read = fread(buf, 1, nbytes, file_handle); + if (bytes_read == 0) + { + warnif("read from file", filename, -1); + } + fclose(file_handle); + return bytes_read; +} + +// static +U64 LLFile::write(const std::string& filename, const void* buf, U64 offset, U64 nbytes) +{ + LLFILE* file_handle = LLFile::fopen(filename, "wb"); + if (file_handle == nullptr) + { + warnif("write, opening file", filename, -1); + return 0; + } + + if (offset > 0) + { +#if LL_WINDOWS + // On Windows fseek() uses a long value as offset which is always 32-bit + int rc = _fseeki64(file_handle, (long long)offset, SEEK_SET); +#else + // offset is of type long which is 64-bit in 64-bit GCC + int rc = fseek(file_handle, (long)offset, SEEK_SET); +#endif + if (rc) + { + warnif("write, setting file offset", filename, rc); + fclose(file_handle); + return 0; + } + } + + // element_count is of size_t which is 64-bit on all 64-bit systems + U64 bytes_written = fwrite(buf, 1, nbytes, file_handle); + if (bytes_written == 0) + { + warnif("write to file", filename, -1); + } + fclose(file_handle); + return bytes_written; +} + // Make this a define rather than using magic numbers multiple times in the code #define LLFILE_COPY_BUFFER_SIZE 16384 @@ -523,7 +608,7 @@ S64 LLFile::size(const std::string& filename, int suppress_error) int rc = ::stat(filename.c_str(), &filestatus); if (rc == 0) { - if (S_ISREG(filestatus.st_mode) + if (S_ISREG(filestatus.st_mode)) { return filestatus.st_size; } @@ -542,10 +627,10 @@ unsigned short LLFile::getattr(const std::string& filename, bool dontFollowSymLi #if LL_WINDOWS // _wstat64() is a bit heavyweight on Windows, use a more lightweight API // to just get the attributes - int rc = -1; std::wstring utf16filename = utf8path_to_wstring(filename); - unsigned short st_mode = get_fileattr(utf16filename, dontFollowSymLink); - if (st_mode) + unsigned short st_mode; + int rc = get_fileattr(utf16filename, st_mode, dontFollowSymLink); + if (rc == 0) { return st_mode; } diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h index 61ec8a5e4e0..a856f34be6b 100644 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -45,8 +45,8 @@ typedef FILE LLFILE; // We use _stat64 here to support 64-bit st_size and time_t values typedef struct _stat64 llstat; #else -typedef struct stat llstat; #include +typedef struct stat llstat; #endif #ifndef S_ISREG @@ -74,8 +74,11 @@ typedef struct stat llstat; class LL_COMMON_API LLFile { public: +//---------------------------------------------------------------------------------------- +// Static member functions +//---------------------------------------------------------------------------------------- /// open a file with the specified access mode - static LLFILE* fopen(const std::string& filename, const char* accessmode); /* Flawfinder: ignore */ + static LLFILE* fopen(const std::string& filename, const char* accessmode); ///< 'accessmode' follows the rules of the Posix fopen() mode parameter /// "r" open the file for reading only and positions the stream at the beginning /// "r+" open the file for reading and writing and positions the stream at the beginning @@ -91,14 +94,13 @@ class LL_COMMON_API LLFile /// This means that it is always a good idea to append "b" specifically for binary file access to /// avoid corruption of the binary consistency of the data stream when reading or writing /// Other characters in 'accessmode' will usually cause an error as fopen will verify this parameter - /// @returns a valid LLFILE* pointer on success or NULL on failure + /// @returns a valid LLFILE* pointer on success that can be passed to the fread() and fwrite() functions + /// and some other f functions in the Standard C library that accept a FILE* as parameter + /// or NULL on failure + /// Close a file handle opened with fopen() above static int close(LLFILE * file); - /// retrieve the content of a file into a string - static std::string getContents(const std::string& filename); - ///< @returns the content of the file or an empty string on failure - /// create a directory static int mkdir(const std::string& filename, int perms = 0700); ///< perms is a permissions mask like 0777 or 0700. In most cases it will be @@ -127,11 +129,22 @@ class LL_COMMON_API LLFile /// does not make such guarantees. /// @returns 0 on success and -1 on failure. - - /// copy the contents of file from 'from' to 'to' filename + /// copy the contents of the file from 'from' to 'to' filename static bool copy(const std::string& from, const std::string& to); ///< @returns true on success and false on failure. + /// retrieve the content of a file into a string + static std::string getContents(const std::string& filename); + ///< @returns the content of the file or an empty string on failure + + /// read nBytes from the file into the buffer, starting at offset in the file + static U64 read(const std::string& filename, void* buf, U64 offset, U64 nbytes); + ///< @returns bytes read on success, 0 on failure + + /// write nBytes from the buffer into the file, starting at offset in the file + static U64 write(const std::string& filename, const void* buf, U64 offset, U64 nbytes); + ///< @returns bytes written on success, 0 on failure + /// return the file stat structure for filename static int stat(const std::string& filename, llstat* file_status, int suppress_error = ENOENT); ///< for compatibility with existing uses of LL_File::stat() we use ENOENT as default in the @@ -145,7 +158,7 @@ class LL_COMMON_API LLFile /// dontFollowSymLinks set to true returns the attributes of the symlink if it is one, rather than resolving it /// we pass by default ENOENT in the optional 'suppress_error' parameter to not spam the log with /// warnings when the file or directory does not exist - /// @returns 0 on failure and a st_mode value with either S_IFDIR or S_IFREG set otherwise + /// @returns 0 on failure and a st_mode value with either S_IFDIR, S_IFREG or S_IFLNK set, /// together with the three access bits which under Windows only the write bit is relevant. /// get the size of a file in bytes