From 3ca3ea75c333078013914e174564340f894573e2 Mon Sep 17 00:00:00 2001 From: Frederick Martian Date: Sat, 25 Oct 2025 17:08:35 +0200 Subject: 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/newview/llappviewer.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 569fd30b21..f6e3139cc4 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2268,7 +2268,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 @@ -3900,7 +3900,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) @@ -3992,7 +3992,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)) { @@ -4004,11 +4004,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) @@ -4033,7 +4033,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); } #if LL_DARWIN @@ -4060,7 +4060,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() -- cgit v1.3 From f07762a46830005b6ff4218c1f070ce27a9ecebe Mon Sep 17 00:00:00 2001 From: Frederick Martian Date: Wed, 12 Nov 2025 19:19:59 +0100 Subject: Refactoring of LLFile class to support additional methods - LLFile with its own class method interface to access files for read and write - Remove rudimentary LLUniqueFile class as LLFile supports now all of that and more - Implement most of the filename based functions using std::filesystem functions - Replace LLFile::rmdir() with LLFile::remove() since this function now supports deleting files and directories on all platforms. --- indra/llcommon/llerror.cpp | 10 +- indra/llcommon/llfile.cpp | 1159 +++++++++++++++++++++---------- indra/llcommon/llfile.h | 396 +++++++---- indra/llcrashlogger/llcrashlock.cpp | 7 +- indra/llfilesystem/lldir.cpp | 51 +- indra/llfilesystem/lldir_win32.cpp | 13 +- indra/llfilesystem/llfilesystem.cpp | 19 +- indra/llfilesystem/tests/lldir_test.cpp | 4 +- indra/llrender/llshadermgr.cpp | 17 +- indra/llxml/tests/llcontrol_test.cpp | 2 +- indra/newview/llappviewer.cpp | 10 +- indra/newview/llfloaterpreference.cpp | 12 +- indra/newview/llfloateruipreview.cpp | 11 +- indra/newview/llpreviewnotecard.cpp | 3 +- indra/newview/llpreviewscript.cpp | 3 +- indra/newview/lltexturecache.cpp | 2 +- indra/newview/llviewerdisplay.cpp | 5 +- indra/newview/llviewermedia.cpp | 5 +- indra/newview/llvocache.cpp | 2 +- indra/newview/llvoicevivox.cpp | 3 +- indra/test/llmessageconfig_tut.cpp | 2 +- indra/test/message_tut.cpp | 2 +- 22 files changed, 1136 insertions(+), 602 deletions(-) mode change 100644 => 100755 indra/llcommon/llerror.cpp mode change 100644 => 100755 indra/llcommon/llfile.cpp mode change 100644 => 100755 indra/llcommon/llfile.h mode change 100644 => 100755 indra/llcrashlogger/llcrashlock.cpp mode change 100644 => 100755 indra/llfilesystem/lldir.cpp mode change 100644 => 100755 indra/llfilesystem/lldir_win32.cpp mode change 100644 => 100755 indra/llfilesystem/llfilesystem.cpp mode change 100644 => 100755 indra/llfilesystem/tests/lldir_test.cpp mode change 100644 => 100755 indra/llrender/llshadermgr.cpp mode change 100644 => 100755 indra/llxml/tests/llcontrol_test.cpp mode change 100644 => 100755 indra/newview/llappviewer.cpp mode change 100644 => 100755 indra/newview/llfloaterpreference.cpp mode change 100644 => 100755 indra/newview/llfloateruipreview.cpp mode change 100644 => 100755 indra/newview/llpreviewnotecard.cpp mode change 100644 => 100755 indra/newview/llpreviewscript.cpp mode change 100644 => 100755 indra/newview/lltexturecache.cpp mode change 100644 => 100755 indra/newview/llviewerdisplay.cpp mode change 100644 => 100755 indra/newview/llviewermedia.cpp mode change 100644 => 100755 indra/newview/llvocache.cpp mode change 100644 => 100755 indra/newview/llvoicevivox.cpp mode change 100644 => 100755 indra/test/llmessageconfig_tut.cpp mode change 100644 => 100755 indra/test/message_tut.cpp (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp old mode 100644 new mode 100755 index b14464382b..bfa8bca224 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -435,13 +435,9 @@ namespace std::string file = user_dir + "/logcontrol-dev.xml"; - llstat stat_info; - if (LLFile::stat(file, &stat_info)) { - // NB: stat returns non-zero if it can't read the file, for example - // if it doesn't exist. LLFile has no better abstraction for - // testing for file existence. - - file = app_dir + "/logcontrol.xml"; + if (!LLFile::isfile(file)) + { + file = app_dir + "/logcontrol.xml"; } return * new LogControlFile(file); // NB: This instance is never freed diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp old mode 100644 new mode 100755 index 9fff614efb..6ccf01dd78 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -34,17 +34,13 @@ #include "stringize.h" #if LL_WINDOWS -#include "llwin32headers.h" -#include +#include #else #include +#include #endif -using namespace std; - -static std::string empty; - -// Many of the methods below use OS-level functions that mess with errno. Wrap +// Some of the methods below use OS-level functions that mess with errno. Wrap // variants of strerror() to report errors. #if LL_WINDOWS @@ -79,6 +75,7 @@ static errentry const errtable[] { ERROR_CURRENT_DIRECTORY, EACCES }, // 16 { ERROR_NOT_SAME_DEVICE, EXDEV }, // 17 { ERROR_NO_MORE_FILES, ENOENT }, // 18 + { ERROR_SHARING_VIOLATION, EACCES }, // 32 { ERROR_LOCK_VIOLATION, EACCES }, // 33 { ERROR_BAD_NETPATH, ENOENT }, // 53 { ERROR_NETWORK_ACCESS_DENIED, EACCES }, // 65 @@ -109,22 +106,25 @@ static errentry const errtable[] { ERROR_NOT_ENOUGH_QUOTA, ENOMEM } // 1816 }; -static int set_errno_from_oserror(unsigned long oserr) +static int get_errno_from_oserror(int oserr) { if (!oserr) return 0; // Check the table for the Windows OS error code - for (const struct errentry &entry : errtable) + for (const struct errentry& entry : errtable) { if (oserr == entry.oserr) { - _set_errno(entry.errcode); - return -1; + return entry.errcode; } } + return EINVAL; +} - _set_errno(EINVAL); +static int set_errno_from_oserror(unsigned long oserr) +{ + _set_errno(get_errno_from_oserror(oserr)); return -1; } @@ -136,72 +136,8 @@ std::string strerr(int errn) return buffer; } -inline bool is_slash(wchar_t const c) -{ - return c == L'\\' || c == L'/'; -} - -static std::wstring utf8path_to_wstring(const std::string& utf8path) -{ - if (utf8path.size() >= MAX_PATH) - { - // By prepending "\\?\" to a path, Windows widechar file APIs will not fail on long path names - std::wstring utf16path = L"\\\\?\\" + ll_convert(utf8path); - // We need to make sure that the path does not contain forward slashes as above - // prefix does bypass the path normalization that replaces slashes with backslashes - // before passing the path to kernel mode APIs - std::replace(utf16path.begin(), utf16path.end(), L'/', L'\\'); - return utf16path; - } - return ll_convert(utf8path); -} -// 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; // 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, - FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, OPEN_EXISTING, flags, nullptr); - if (file_handle != INVALID_HANDLE_VALUE) - { - FILE_ATTRIBUTE_TAG_INFO attribute_info; - if (GetFileInformationByHandleEx(file_handle, FileAttributeTagInfo, &attribute_info, sizeof(attribute_info))) - { - // A volume path alone (only drive letter) is not recognized as directory while it technically is - bool is_directory = (attribute_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) || - (iswalpha(utf16path[0]) && utf16path[1] == ':' && - (!utf16path[2] || (is_slash(utf16path[2]) && !utf16path[3]))); - 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 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 0; - } - } - // Retrieve last error and set errno before calling CloseHandle() - int rc = set_errno_from_oserror(GetLastError()); - - if (file_handle != INVALID_HANDLE_VALUE) - { - CloseHandle(file_handle); - } - return rc; -} - #else + // On Posix we want to call strerror_r(), but alarmingly, there are two // different variants. The one that returns int always populates the passed // buffer (except in case of error), whereas the other one always returns a @@ -248,8 +184,49 @@ std::string strerr(int errn) return message_from(errn, buffer, sizeof(buffer), strerror_r(errn, buffer, sizeof(buffer))); } + #endif // ! LL_WINDOWS +#if LL_WINDOWS && 0 // turn on to debug file-locking problems +#define PROCESS_LOCKING_CHECK 1 +static void find_locking_process(const std::string& filename) +{ + // Only do any of this stuff (before LL_ENDL) if it will be logged. + LL_DEBUGS("LLFile") << ""; + // wrong way + std::string TEMP = LLFile::tmpdir(); + if (TEMP.empty()) + { + 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 + static int warnif(const std::string& desc, const std::string& filename, int rc, int accept = 0) { if (rc < 0) @@ -262,437 +239,905 @@ static int warnif(const std::string& desc, const std::string& filename, int rc, // EEXIST. Don't warn if caller explicitly says this errno is okay. if (errn != accept) { - LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename - << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL; + LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL; } -#if 0 && LL_WINDOWS // turn on to debug file-locking problems +#if PROCESS_LOCKING_CHECK // 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 + 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; + find_locking_process(filename); } -#endif // LL_WINDOWS hack to identify processes holding file open +#endif } return rc; } -// static -int LLFile::mkdir(const std::string& dirname, int perms) +static int warnif(const std::string& desc, const std::string& filename, std::error_code& ec, int accept = 0) { - // We often use mkdir() to ensure the existence of a directory that might - // already exist. There is no known case in which we want to call out as - // an error the requested directory already existing. + if (ec) + { + // get Posix errno from the std::error_code so we can compare it to the accept parameter to see + // when a caller wants us to not generate a warning for a particular error code #if LL_WINDOWS - // permissions are ignored on Windows - int rc = 0; - std::wstring utf16dirname = utf8path_to_wstring(dirname); - if (!CreateDirectoryW(utf16dirname.c_str(), nullptr)) - { - // Only treat other errors than an already existing file as a real error - unsigned long oserr = GetLastError(); - if (oserr != ERROR_ALREADY_EXISTS) + int errn = get_errno_from_oserror(ec.value()); +#else + int errn = ec.value(); +#endif + // For certain operations, a particular errno value might be acceptable + // Don't warn if caller explicitly says this errno is okay. + if (errn != accept) + { + LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename << "' (errno " << errn << "): " << ec.message() << LL_ENDL; + } +#if PROCESS_LOCKING_CHECK + // Try to detect locked files by other processes + if (ec.value() == ERROR_SHARING_VIOLATION || ec.value() == ERROR_LOCK_VIOLATION) { - rc = set_errno_from_oserror(oserr); + find_locking_process(filename); } +#endif + return -1; } + return 0; +} + +#if LL_WINDOWS + +inline int set_ec_from_system_error(std::error_code& ec, DWORD error) +{ + ec.assign(error, std::system_category()); + return -1; +} + +static int set_ec_from_system_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, GetLastError()); +} + +inline int set_ec_to_parameter_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, ERROR_INVALID_PARAMETER); +} + +inline int set_ec_to_outofmemory_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, ERROR_NOT_ENOUGH_MEMORY); +} + +inline DWORD decode_access_mode(std::ios_base::openmode omode) +{ + switch (omode & (LLFile::in | LLFile::out)) + { + case LLFile::in: + return GENERIC_READ; + case LLFile::out: + return GENERIC_WRITE; + case LLFile::in | LLFile::out: + return GENERIC_READ | GENERIC_WRITE; + } + if (omode & LLFile::app) + { + return GENERIC_WRITE; + } + return 0; +} + +inline DWORD decode_open_create_flags(std::ios_base::openmode omode) +{ + if (omode & LLFile::noreplace) + { + return CREATE_NEW; // create if it does not exist, otherwise fail + } + if (omode & LLFile::trunc) + { + if (!(omode & LLFile::out)) + { + return TRUNCATE_EXISTING; // open and truncatte if it exists, otherwise fail + } + return CREATE_ALWAYS; // open and truncate if it exists, otherwise create it + } + if (!(omode & LLFile::out)) + { + return OPEN_EXISTING; // open if exists, otherwise fail + } + // LLFile::app or (LLFile::out and (!LLFile::trunc or !LLFile::noreplace)) + return OPEN_ALWAYS; // open if it exists, otherwise create it +} + +inline DWORD decode_share_mode(int omode) +{ + if (omode & LLFile::exclusive) + { + return 0; // allow no other access + } + if (omode & LLFile::shared) + { + return FILE_SHARE_READ; // allow read access + } + return FILE_SHARE_READ | FILE_SHARE_WRITE; // allow read and write access to others +} + +inline DWORD decode_attributes(std::ios_base::openmode omode, int perm) +{ + return (perm & S_IWRITE) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_READONLY; +} + +// Under Windows the values for the std::ios_base::seekdir constants match the according FILE_BEGIN +// and other constants but we do a programmatic translation for now to be sure +static DWORD seek_mode_from_dir(std::ios_base::seekdir seekdir) +{ + switch (seekdir) + { + case LLFile::beg: + return FILE_BEGIN; + case LLFile::cur: + return FILE_CURRENT; + case LLFile::end: + return FILE_END; + } + return FILE_BEGIN; +} + #else - int rc = ::mkdir(dirname.c_str(), (mode_t)perms); - if (rc < 0 && errno == EEXIST) + +inline int set_ec_from_system_error(std::error_code& ec, int error) +{ + ec.assign(error, std::system_category()); + return -1; +} + +static int set_ec_from_system_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, errno); +} + +inline int set_ec_to_parameter_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, EINVAL); +} + +inline int set_ec_to_outofmemory_error(std::error_code& ec) +{ + return set_ec_from_system_error(ec, ENOMEM); +} + +inline int decode_access_mode(std::ios_base::openmode omode) +{ + switch (omode & (LLFile::in | LLFile::out)) { - // this is not the error you want, move along - return 0; + case LLFile::out: + return O_WRONLY; + case LLFile::in | LLFile::out: + return O_RDWR; + } + return O_RDONLY; +} + +inline int decode_open_mode(std::ios_base::openmode omode) +{ + int flags = O_CREAT | decode_access_mode(omode); + if (omode & LLFile::app) + { + flags |= O_APPEND; + } + if (omode & LLFile::trunc) + { + flags |= O_TRUNC; + } + if (omode & LLFile::binary) + { + // Not a thing under *nix + } + if (omode & LLFile::noreplace) + { + flags |= O_EXCL; + } + return flags; +} + +inline int decode_lock_mode(std::ios_base::openmode omode) +{ + int lmode = omode & LLFile::noblock ? LOCK_NB : 0; + if (omode & LLFile::lock_mask) + { + if (omode & LLFile::exclusive) + { + return lmode | LOCK_EX; + } + return lmode | LOCK_SH; + } + return lmode | LOCK_UN; +} + +// Under Linux and Mac the values for the std::ios_base::seekdir constants match the according SEEK_SET +// and other constants but we do a programmatic translation for now to be sure +inline int seek_mode_from_dir(std::ios_base::seekdir seekdir) +{ + switch (seekdir) + { + case LLFile::beg: + return SEEK_SET; + case LLFile::cur: + return SEEK_CUR; + case LLFile::end: + return SEEK_END; + } + return SEEK_SET; +} + +#endif + +inline int clear_error(std::error_code& ec) +{ + ec.clear(); + return 0; +} + +inline bool are_open_mode_flags_invalid(std::ios_base::openmode omode) +{ + // at least one of input or output needs to be specified + if (!(omode & (LLFile::in | LLFile::out))) + { + return true; + } + // output must be possible for any of the extra options + if (!(omode & LLFile::out) && (omode & (LLFile::trunc | LLFile::app | LLFile::noreplace))) + { + return true; + } + // invalid combination, mutually exclusive + if ((omode & LLFile::app) && (omode & (LLFile::trunc | LLFile::noreplace))) + { + return true; + } + return false; +} + +//---------------------------------------------------------------------------------------- +// class member functions +//---------------------------------------------------------------------------------------- +int LLFile::open(const std::string& filename, std::ios_base::openmode omode, std::error_code& ec, int perm) +{ + close(ec); + if (are_open_mode_flags_invalid(omode)) + { + return set_ec_to_parameter_error(ec); + } +#if LL_WINDOWS + DWORD access = decode_access_mode(omode), + share = decode_share_mode(omode), + create = decode_open_create_flags(omode), + attributes = decode_attributes(omode, perm); + + std::wstring file_path = utf8StringToWstring(filename); + mHandle = CreateFileW(file_path.c_str(), access, share, nullptr, create, attributes, nullptr); +#else + int oflags = decode_open_mode(omode); + int lmode = LLFile::noblock | (omode & LLFile::lock_mask); + mHandle = ::open(filename.c_str(), oflags, perm); + if (mHandle != InvalidHandle && omode & LLFile::lock_mask && + lock(omode | LLFile::noblock, ec) != 0) + { + close(); + return -1; } #endif - // anything else might be a problem - return warnif("mkdir", dirname, rc); + if (mHandle == InvalidHandle) + { + return set_ec_from_system_error(ec); + } + + if (omode & LLFile::ate && seek(0, LLFile::end, ec) != 0) + { + close(); + return -1; + } + mOpen = omode; + return clear_error(ec); } -// static -int LLFile::rmdir(const std::string& dirname, int suppress_error) +S64 LLFile::size(std::error_code& ec) { #if LL_WINDOWS - std::wstring utf16dirname = utf8path_to_wstring(dirname); - if (RemoveDirectoryW(utf16dirname.c_str())) + LARGE_INTEGER value = { 0 }; + if (GetFileSizeEx(mHandle, &value)) { - return 0; + clear_error(ec); + return value.QuadPart; } - int rc = set_errno_from_oserror(GetLastError()); #else - int rc = ::rmdir(dirname.c_str()); + struct stat statval; + if (fstat(mHandle, &statval) == 0) + { + clear_error(ec); + return statval.st_size; + } #endif - return warnif("rmdir", dirname, rc, suppress_error); + return set_ec_from_system_error(ec); } -// static -LLFILE* LLFile::fopen(const std::string& filename, const char* mode) +S64 LLFile::tell(std::error_code& ec) { #if LL_WINDOWS - std::wstring utf16filename = utf8path_to_wstring(filename); - std::wstring utf16mode = ll_convert(std::string(mode)); - return _wfopen(utf16filename.c_str(), utf16mode.c_str()); + LARGE_INTEGER value = { 0 }; + if (SetFilePointerEx(mHandle, value, &value, FILE_CURRENT)) + { + clear_error(ec); + return value.QuadPart; + } #else - return ::fopen(filename.c_str(),mode); + off_t offset = lseek(mHandle, 0, SEEK_CUR); + if (offset != -1) + { + clear_error(ec); + return offset; + } #endif + return set_ec_from_system_error(ec); } -// static -int LLFile::close(LLFILE * file) +int LLFile::seek(S64 pos, std::error_code& ec) { - int ret_value = 0; - if (file) + return seek(pos, LLFile::beg, ec); +} + +int LLFile::seek(S64 offset, std::ios_base::seekdir dir, std::error_code& ec) +{ + S64 newOffset = 0; +#if LL_WINDOWS + DWORD seekdir = seek_mode_from_dir(dir); + LARGE_INTEGER value; + value.QuadPart = offset; + if (SetFilePointerEx(mHandle, value, (PLARGE_INTEGER)&newOffset, seekdir)) +#else + newOffset = lseek(mHandle, offset, seek_mode_from_dir(dir)); + if (newOffset != -1) +#endif { - ret_value = fclose(file); + return clear_error(ec); } - return ret_value; + return set_ec_from_system_error(ec); } -// static -std::string LLFile::getContents(const std::string& filename) +#if LL_WINDOWS +inline DWORD next_buffer_size(S64 nbytes) +{ + return nbytes > 0x80000000 ? 0x80000000 : (DWORD)nbytes; +} +#endif + +S64 LLFile::read(void* buffer, S64 nbytes, std::error_code& ec) +{ + if (nbytes == 0) + { + // Nothing to do + clear_error(ec); + return 0; + } + else if (!buffer || nbytes < 0) + { + return set_ec_to_parameter_error(ec); + } +#if LL_WINDOWS + S64 totalBytes = 0; + char *ptr = (char*)buffer; + DWORD bytesRead, bytesToRead = next_buffer_size(nbytes); + + // Read in chunks to support >4GB which the S64 nbytes value makes possible + while (ReadFile(mHandle, ptr, bytesToRead, &bytesRead, nullptr)) + { + totalBytes += bytesRead; + if (nbytes <= totalBytes || // requested amount read + bytesRead < bytesToRead) // ReadFile encountered eof + { + clear_error(ec); + return totalBytes; + } + ptr += bytesRead; + bytesToRead = next_buffer_size(nbytes - totalBytes); + } +#else + ssize_t bytesRead = ::read(mHandle, buffer, nbytes); + if (bytesRead != -1) + { + clear_error(ec); + return bytesRead; + } +#endif + return set_ec_from_system_error(ec); +} + +S64 LLFile::write(const void* buffer, S64 nbytes, std::error_code& ec) { - LLFILE* fp = LLFile::fopen(filename, "rb"); - if (fp) + if (nbytes == 0) + { + // Nothing to do here + clear_error(ec); + return 0; + } + else if (!buffer || nbytes < 0) + { + return set_ec_to_parameter_error(ec); + } +#if LL_WINDOWS + // If this was opened in append mode, we emulate it on Windows + if (mOpen & LLFile::app && seek(0, LLFile::end, ec) != 0) { - fseek(fp, 0, SEEK_END); - U32 length = ftell(fp); - fseek(fp, 0, SEEK_SET); + return -1; + } - std::vector buffer(length); - size_t nread = fread(buffer.data(), 1, length, fp); - fclose(fp); + S64 totalBytes = 0; + char* ptr = (char*)buffer; + DWORD bytesWritten, bytesToWrite = next_buffer_size(nbytes); - return std::string(buffer.data(), nread); + // Write in chunks to support >4GB which the S64 nbytes value makes possible + while (WriteFile(mHandle, ptr, bytesToWrite, &bytesWritten, nullptr)) + { + totalBytes += bytesWritten; + if (nbytes <= totalBytes) + { + clear_error(ec); + return totalBytes; + } + ptr += bytesWritten; + bytesToWrite = next_buffer_size(nbytes - totalBytes); } +#else + ssize_t bytesWritten = ::write(mHandle, buffer, nbytes); + if (bytesWritten != -1) + { + clear_error(ec); + return bytesWritten; + } +#endif + return set_ec_from_system_error(ec); +} - return LLStringUtil::null; +S64 LLFile::printf(const char* fmt, ...) +{ + va_list args1; + va_start(args1, fmt); + va_list args2; + va_copy(args2, args1); + int length = vsnprintf(NULL, 0, fmt, args1); + va_end(args1); + if (length < 0) + { + va_end(args2); + return -1; + } + void* buffer = malloc(length + 1); + if (!buffer) + { + va_end(args2); + return -1; + } + length = vsnprintf((char*)buffer, length + 1, fmt, args2); + va_end(args2); + std::error_code ec; + S64 written = write(buffer, length, ec); + free(buffer); + return written; } -// static -int LLFile::remove(const std::string& filename, int suppress_error) +int LLFile::lock(int mode, std::error_code& ec) { #if LL_WINDOWS - // Posix remove() works on both files and directories although on Windows - // remove() and its wide char variant _wremove() only removes files just - // 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 - std::wstring utf16filename = utf8path_to_wstring(filename); - unsigned short st_mode; - int rc = get_fileattr(utf16filename, st_mode); - if (rc == 0) + if (!(mode & LLFile::lock_mask)) { - if (S_ISDIR(st_mode)) + if (UnlockFile(mHandle, 0, 0, MAXDWORD, MAXDWORD)) { - if (RemoveDirectoryW(utf16filename.c_str())) - { - return 0; - } + return clear_error(ec); } - else if (S_ISREG(st_mode)) + } + else + { + OVERLAPPED overlapped = { 0 }; + DWORD flags = (mode & LLFile::noblock) ? LOCKFILE_FAIL_IMMEDIATELY : 0; + if (mode & LLFile::exclusive) { - if (DeleteFileW(utf16filename.c_str())) - { - return 0; - } + flags |= LOCKFILE_EXCLUSIVE_LOCK; } - else + // We lock the maximum range, since flock only supports locking the entire file too + if (LockFileEx(mHandle, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) { - SetLastError(ERROR_INVALID_PARAMETER); + return clear_error(ec); } - // Deleting the file or directory failed - rc = set_errno_from_oserror(GetLastError()); } #else - int rc = ::remove(filename.c_str()); + if (flock(mHandle, decode_lock_mode(mode)) == 0) + { + return clear_error(ec); + } #endif - return warnif("remove", filename, rc, suppress_error); + return set_ec_from_system_error(ec); +} + +int LLFile::close(std::error_code& ec) +{ + if (mHandle != InvalidHandle) + { + llfile_handle_t handle = InvalidHandle; + std::swap(handle, mHandle); +#if LL_WINDOWS + if (!CloseHandle(handle)) +#else + if (::close(handle)) +#endif + { + return set_ec_from_system_error(ec); + } + } + return clear_error(ec); +} + +int LLFile::close() +{ + std::error_code ec; + return close(ec); } +//---------------------------------------------------------------------------------------- +// static member functions +//---------------------------------------------------------------------------------------- + // static -int LLFile::rename(const std::string& filename, const std::string& newname, int suppress_error) +LLFILE* LLFile::fopen(const std::string& filename, const char* mode, int lmode) { + LLFILE* file; #if LL_WINDOWS - // Posix rename() will gladly overwrite a file at newname if it exists, the Windows - // rename(), respectively _wrename(), will bark on that. Instead call directly the Windows - // 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); - if (MoveFileExW(utf16filename.c_str(), utf16newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) + int shflag = _SH_DENYNO; + switch (lmode) { - return 0; + case LLFile::exclusive: + shflag = _SH_DENYRW; + break; + case LLFile::shared: + shflag = _SH_DENYWR; + break; } - int rc = set_errno_from_oserror(GetLastError()); + std::wstring file_path = utf8StringToWstring(filename); + std::wstring utf16mode = ll_convert(std::string(mode)); + file = _wfsopen(file_path.c_str(), utf16mode.c_str(), shflag); #else - int rc = ::rename(filename.c_str(),newname.c_str()); + file = ::fopen(filename.c_str(), mode); + if (file && (lmode & (LLFile::lock_mask))) + { + // Rather fail on a sharing conflict than block + if (flock(fileno(file), decode_lock_mode(lmode | LLFile::noblock))) + { + LLFile::close(file); + file = nullptr; + } + } #endif - return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, suppress_error); + return file; } // static -U64 LLFile::read(const std::string& filename, void* buf, U64 offset, U64 nbytes) +int LLFile::close(LLFILE* file) { - LLFILE* file_handle = LLFile::fopen(filename, "rb"); - if (file_handle == nullptr) + int ret_value = 0; + if (file) { - warnif("read, opening file", filename, -1); - return 0; + // Read the current errno and restore it if it was not 0 + int errn = errno; + ret_value = ::fclose(file); + if (errn) + { + errno = errn; + } } + return ret_value; +} - if (offset > 0) +// static +std::string LLFile::getContents(const std::string& filename) +{ + std::error_code ec; + return getContents(filename, ec); +} + +// static +std::string LLFile::getContents(const std::string& filename, std::error_code& ec) +{ + std::string buffer; + LLFile file(filename, LLFile::in | LLFile::binary, ec); + if (file) { -#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) + S64 length = file.size(ec); + if (!ec && length > 0) { - warnif("read, setting file offset", filename, rc); - fclose(file_handle); - return 0; + buffer = std::string(length, 0); + file.read(&buffer[0], length, ec); + if (ec) + { + buffer.clear(); + } } } + return buffer; +} - // 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 +int LLFile::mkdir(const std::string& dirname) +{ + std::error_code ec; + std::filesystem::path file_path = utf8StringToPath(dirname); + // We often use mkdir() to ensure the existence of a directory that might + // already exist. There is no known case in which we want to call out as + // an error the requested directory already existing. + std::filesystem::create_directory(file_path, ec); + // The return value is only true if the directory was actually created. + // But if it already existed, ec still indicates success. + return warnif("mkdir", dirname, ec); +} + +// static +int LLFile::remove(const std::string& filename, int suppress_error) +{ + std::error_code ec; + std::filesystem::path file_path = utf8StringToPath(filename); + std::filesystem::remove(file_path, ec); + return warnif("remove", filename, ec, suppress_error); +} + +// static +int LLFile::rename(const std::string& filename, const std::string& newname, int suppress_error) +{ + std::error_code ec; + std::filesystem::path file_path = utf8StringToPath(filename); + std::filesystem::path new_path = utf8StringToPath(newname); + std::filesystem::rename(file_path, new_path, ec); + return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, ec, suppress_error); +} + +// static +S64 LLFile::read(const std::string& filename, void* buf, S64 offset, S64 nbytes) +{ + std::error_code ec; + return read(filename, buf, offset, nbytes, ec); } // static -U64 LLFile::write(const std::string& filename, const void* buf, U64 offset, U64 nbytes) +S64 LLFile::read(const std::string& filename, void* buf, S64 offset, S64 nbytes, std::error_code& ec) { - LLFILE* file_handle = LLFile::fopen(filename, "wb"); - if (file_handle == nullptr) + // if number of bytes is 0 or less there is nothing to do here + if (nbytes <= 0) { - warnif("write, opening file", filename, -1); + clear_error(ec); return 0; } - if (offset > 0) + std::ios_base::openmode omode = LLFile::in | LLFile::binary; + + LLFile file(filename, omode, ec); + if (file) { -#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) + S64 bytes_read = 0; + if (offset > 0) { - warnif("write, setting file offset", filename, rc); - fclose(file_handle); - return 0; + file.seek(offset, ec); + } + if (!ec) + { + bytes_read = file.read(buf, nbytes, ec); + if (!ec) + { + return bytes_read; + } } } - - // 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; + return warnif("read from file failed", filename, ec); } -// Make this a define rather than using magic numbers multiple times in the code -#define LLFILE_COPY_BUFFER_SIZE 16384 +// static +S64 LLFile::write(const std::string& filename, const void* buf, S64 offset, S64 nbytes) +{ + std::error_code ec; + return write(filename, buf, offset, nbytes, ec); +} // static -bool LLFile::copy(const std::string& from, const std::string& to) +S64 LLFile::write(const std::string& filename, const void* buf, S64 offset, S64 nbytes, std::error_code& ec) { - bool copied = false; - LLFILE* in = LLFile::fopen(from, "rb"); - if (in) + // if number of bytes is 0 or less there is nothing to do here + if (nbytes <= 0) + { + clear_error(ec); + return 0; + } + + std::ios_base::openmode omode = LLFile::out | LLFile::binary; + if (offset < 0) + { + omode |= LLFile::app; + } + + LLFile file(filename, omode, ec); + if (file) { - LLFILE* out = LLFile::fopen(to, "wb"); - if (out) + S64 bytes_written = 0; + if (offset > 0) { - char buf[LLFILE_COPY_BUFFER_SIZE]; - size_t readbytes; - bool write_ok = true; - while (write_ok && (readbytes = fread(buf, 1, LLFILE_COPY_BUFFER_SIZE, in))) - { - if (fwrite(buf, 1, readbytes, out) != readbytes) - { - LL_WARNS("LLFile") << "Short write" << LL_ENDL; - write_ok = false; - } - } - if ( write_ok ) + file.seek(offset, ec); + } + if (!ec) + { + bytes_written = file.write(buf, nbytes, ec); + if (!ec) { - copied = true; + return bytes_written; } - fclose(out); } - fclose(in); + } + return warnif("write to file failed", filename, ec); +} + +// static +bool LLFile::copy(const std::string& from, const std::string& to) +{ + std::error_code ec; + std::filesystem::path from_path = utf8StringToPath(from); + std::filesystem::path to_path = utf8StringToPath(to); + bool copied = std::filesystem::copy_file(from_path, to_path, ec); + if (!copied) + { + LL_WARNS("LLFile") << "copy failed" << LL_ENDL; } return copied; } // static -int LLFile::stat(const std::string& filename, llstat* filestatus, int suppress_error) +int LLFile::stat(const std::string& filename, llstat* filestatus, const char *fname, int suppress_warning) { #if LL_WINDOWS - std::wstring utf16filename = utf8path_to_wstring(filename); - int rc = _wstat64(utf16filename.c_str(), filestatus); + std::wstring file_path = utf8StringToWstring(filename); + int rc = _wstat64(file_path.c_str(), filestatus); #else int rc = ::stat(filename.c_str(), filestatus); #endif - return warnif("stat", filename, rc, suppress_error); + return warnif(fname ? fname : "stat", filename, rc, suppress_warning); } // static -S64 LLFile::size(const std::string& filename, int suppress_error) +std::time_t LLFile::getCreationTime(const std::string& filename, int suppress_warning) { -#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) + // As if C++20 there is no functionality in std::filesystem to retrieve this information + llstat filestat; + int rc = stat(filename, &filestat, "getCreationTime", suppress_warning); + if (rc == 0) { - CloseHandle(file_handle); - } +#if LL_DARWIN + return filestat.st_birthtime; #else - llstat filestatus; - int rc = ::stat(filename.c_str(), &filestatus); + // Linux stat() doesn't have a creation/birth time (st_ctime really is the last status + // change or inode attributes change) unless we would use statx() instead. But that is + // a major effort, which would require Linux specific changes to LLFile::stat() above + // and possibly adaptions for other platforms that we leave for a later exercise if it + // is ever desired. + return filestat.st_ctime; +#endif + } + return 0; +} + +// static +std::time_t LLFile::getModificationTime(const std::string& filename, int suppress_warning) +{ + // tried to use std::filesystem::last_write_time() but the whole std::chrono infrastructure is as of + // C++20 still not fully implemented on all platforms. Specifically MacOS C++20 seems lacking here, + // and Windows requires a roundabout through std::chrono::utc_clock to then get a + // std::chrono::system_clock that can return a more useful time_t. + // So we take the easy way out in a similar way as with getCreationTime(). + llstat filestat; + int rc = stat(filename, &filestat, "getModificationTime", suppress_warning); if (rc == 0) { - if (S_ISREG(filestatus.st_mode)) - { - return filestatus.st_size; - } - // We got something else than a file - rc = -1; - errno = EACCES; + return filestat.st_mtime; } -#endif - warnif("size", filename, rc, suppress_error); return 0; } // static -unsigned short LLFile::getattr(const std::string& filename, bool dontFollowSymLink, int suppress_error) +S64 LLFile::size(const std::string& filename, int suppress_warning) { -#if LL_WINDOWS - // _wstat64() is a bit heavyweight on Windows, use a more lightweight API - // to just get the attributes - std::wstring utf16filename = utf8path_to_wstring(filename); - unsigned short st_mode; - int rc = get_fileattr(utf16filename, st_mode, dontFollowSymLink); - if (rc == 0) + std::error_code ec; + std::filesystem::path file_path = utf8StringToPath(filename); + std::intmax_t size = (std::intmax_t)std::filesystem::file_size(file_path, ec); + return warnif("size", filename, ec, suppress_warning) ? -1 : size; +} + +// static +std::filesystem::file_status LLFile::getStatus(const std::string& filename, bool dontFollowSymLink, int suppress_warning) +{ + std::error_code ec; + std::filesystem::path file_path = utf8StringToPath(filename); + std::filesystem::file_status status; + if (dontFollowSymLink) { - return st_mode; + status = std::filesystem::status(file_path, ec); } -#else - llstat filestatus; - int rc = dontFollowSymLink ? ::lstat(filename.c_str(), &filestatus) : ::stat(filename.c_str(), &filestatus); - if (rc == 0) + else { - return filestatus.st_mode; + status = std::filesystem::symlink_status(file_path, ec); } -#endif - warnif("getattr", filename, rc, suppress_error); - return 0; + warnif("getattr", filename, ec, suppress_warning); + return status; +} + +// static +bool LLFile::exists(const std::string& filename) +{ + std::filesystem::file_status status = getStatus(filename); + return std::filesystem::exists(status); } // static bool LLFile::isdir(const std::string& filename) { - return S_ISDIR(getattr(filename)); + std::filesystem::file_status status = getStatus(filename); + return std::filesystem::is_directory(status); } // static bool LLFile::isfile(const std::string& filename) { - return S_ISREG(getattr(filename)); + std::filesystem::file_status status = getStatus(filename); + return std::filesystem::is_regular_file(status); } // static bool LLFile::islink(const std::string& filename) { - return S_ISLNK(getattr(filename, true)); + std::filesystem::file_status status = getStatus(filename, true); + return std::filesystem::is_symlink(status); } // static -const char *LLFile::tmpdir() +const std::string& LLFile::tmpdir() { - static std::string utf8path; - - if (utf8path.empty()) + static std::string temppath; + if (temppath.empty()) { - char sep; -#if LL_WINDOWS - sep = '\\'; + temppath = std::filesystem::temp_directory_path().string(); + } + return temppath; +} - std::vector utf16path(MAX_PATH + 1); - GetTempPathW(static_cast(utf16path.size()), &utf16path[0]); - utf8path = ll_convert_wide_to_string(&utf16path[0]); +// static +std::filesystem::path LLFile::utf8StringToPath(const std::string& pathname) +{ +#if LL_WINDOWS + return ll_convert(pathname); #else - sep = '/'; - - utf8path = LLStringUtil::getenv("TMPDIR", "/tmp/"); + return pathname; #endif - if (utf8path[utf8path.size() - 1] != sep) - { - utf8path += sep; - } - } - return utf8path.c_str(); } #if LL_WINDOWS +// static +std::wstring LLFile::utf8StringToWstring(const std::string& pathname) +{ + std::wstring utf16string(ll_convert(pathname)); + if (utf16string.size() >= MAX_PATH) + { + // By going through std::filesystem::path we get a lot of path sanitation done for us that + // is needed when passing a path with a kernel object space prefix to Windows API functions + // since this prefix disables the kernel32 path normalization + std::filesystem::path utf16path(utf16string); + + // By prepending "\\?\" to a path, Windows widechar file APIs will not fail on long path names + utf16string.assign(L"\\\\?\\").append(utf16path); + + /* remove trailing spaces and dots (yes, Windows really does that) */ + return utf16string.substr(0, utf16string.find_last_not_of(L" \t.")); + } + return utf16string; +} + /************** input file stream ********************************/ llifstream::llifstream() {} @@ -735,11 +1180,11 @@ std::streamsize llifstream_size(llifstream& ifstr) { if(!ifstr.is_open()) return 0; std::streampos pos_old = ifstr.tellg(); - ifstr.seekg(0, ios_base::beg); + ifstr.seekg(0, std::ios_base::beg); std::streampos pos_beg = ifstr.tellg(); - ifstr.seekg(0, ios_base::end); + ifstr.seekg(0, std::ios_base::end); std::streampos pos_end = ifstr.tellg(); - ifstr.seekg(pos_old, ios_base::beg); + ifstr.seekg(pos_old, std::ios_base::beg); return pos_end - pos_beg; } @@ -747,11 +1192,11 @@ std::streamsize llofstream_size(llofstream& ofstr) { if(!ofstr.is_open()) return 0; std::streampos pos_old = ofstr.tellp(); - ofstr.seekp(0, ios_base::beg); + ofstr.seekp(0, std::ios_base::beg); std::streampos pos_beg = ofstr.tellp(); - ofstr.seekp(0, ios_base::end); + ofstr.seekp(0, std::ios_base::end); std::streampos pos_end = ofstr.tellp(); - ofstr.seekp(pos_old, ios_base::beg); + ofstr.seekp(pos_old, std::ios_base::beg); return pos_end - pos_beg; } diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h old mode 100644 new mode 100755 index a856f34be6..2bfc4d1ffb --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -35,57 +35,218 @@ * Attempts to mostly mirror the POSIX style IO functions. */ -typedef FILE LLFILE; - #include +#include #include #if LL_WINDOWS +#include // The Windows version of stat function and stat data structure are called _stat64 // We use _stat64 here to support 64-bit st_size and time_t values -typedef struct _stat64 llstat; +typedef struct _stat64 llstat; #else #include typedef struct stat llstat; #endif -#ifndef S_ISREG -# define S_ISREG(x) (((x) & S_IFMT) == S_IFREG) -#endif - -#ifndef S_ISDIR -# define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR) -#endif - -// Windows C runtime library does not define this and does not support symlink detection in the -// stat functions but we do in our getattr() function -#ifndef S_IFLNK -#define S_IFLNK 0xA000 /* symlink */ -#endif - -#ifndef S_ISLNK -#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK) -#endif +typedef FILE LLFILE; #include "llstring.h" // safe char* -> std::string conversion -/// LLFile is a class of static functions operating on paths /// All the functions with a path string input take UTF8 path/filenames class LL_COMMON_API LLFile { public: -//---------------------------------------------------------------------------------------- -// Static member functions -//---------------------------------------------------------------------------------------- + //-------------------------------------------------------------------------------------- + // constants + //-------------------------------------------------------------------------------------- + // These can be passed to the omode parameter of LLFile::open() and its constructor + /* + This is similar to the openmode flags for std:fstream but not exactly the same + std::fstream open() does not allow to open a file for writing without either + forcing the file to be truncated on open or all write operations being always + appended to the end of the file. + But to allow implementing the LLAPRFile::writeEx() functionality we need to be + able to write at a random position to the file without truncating it on open. + + any other combinations than listed here are not allowed and will cause an error + + bin in out trunc app norep File exists File does not exist + ------------------------------------------------------------------------------- + - + - - - - Open at begin Failure to open + + + - - - - " " + - - + - - - " Create new + + - + - - - " " + - + + - - - " " + + + + - - - " " + ------------------------------------------------------------------------------- + - - + + - - Destroy contents Create new + + - + + - - " " + - + + + - - " " + + + + + - - " " + ------------------------------------------------------------------------------- + - - + x - + Failure to open Create new + + - + x - + " " + - + + x - + " " + + + + x - + " " + ------------------------------------------------------------------------------- + - - + - + - Write to end Create new + + - + - + - " " + - + + - + - " " + + + + - + - " " + ------------------------------------------------------------------------------- + */ + static const std::ios_base::openmode app = 1 << 1; // append to end + static const std::ios_base::openmode ate = 1 << 2; // initialize to end + static const std::ios_base::openmode binary = 1 << 3; // binary mode + static const std::ios_base::openmode in = 1 << 4; // for reading + static const std::ios_base::openmode out = 1 << 5; // for writing + static const std::ios_base::openmode trunc = 1 << 6; // truncate on open + static const std::ios_base::openmode noreplace = 1 << 7; // no replace if it exists + + // Additional optional omode flags to open() and lmode to fopen() or mode flags to lock() + // to indicate which sort of lock if any to attempt to get + static const std::ios_base::openmode exclusive = 1 << 16; + static const std::ios_base::openmode shared = 1 << 17; + // When used either in omode for LLFile::open() or in lmode for LLFile::fopen() there is a + // difference between platforms. + // On Windows this lock is mandatory as it is part of the API to open a file and other processes + // can not avoid it. If a file was opened denying other processes read and/or write access, + // trying to open the same file with that access will fail. + // On Mac and Linux it is only an advisory lock. This means that the other application + // needs to also attempt to at least acquire a shared lock on the file in order to notice that + // the file is actually already locked. It can therefore not be used to prevent random other + // applications from accessing the file, but it works for other viewer processes when they use + // either the LLFile::open() or LLFile::fopen() functions with the appropriate lock flags to + // open a file. + + // Additional mode flag to indicate to rather fail instead of blocking when trying + // to acquire a lock with LLFile::lock() + static const std::ios_base::openmode noblock = 1 << 18; + + static const std::ios_base::openmode lock_mask = exclusive | shared; + + // These can be passed to the dir parameter of LLFile::seek() + static const std::ios_base::seekdir beg = std::ios_base::beg; + static const std::ios_base::seekdir cur = std::ios_base::cur; + static const std::ios_base::seekdir end = std::ios_base::end; + + //-------------------------------------------------------------------------------------- + // constructor/deconstructor + //-------------------------------------------------------------------------------------- + LLFile() : mHandle(InvalidHandle) {} + + // no copy + LLFile(const LLFile&) = delete; + + // move construction + LLFile(LLFile&& other) noexcept + { + mHandle = other.mHandle; + other.mHandle = InvalidHandle; + } + + // open a file + LLFile(const std::string& filename, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666) : + mHandle(InvalidHandle) + { + open(filename, omode, ec, perm); + } + + // Make it a RAII class + ~LLFile() { close(); } + + //-------------------------------------------------------------------------------------- + // operators + //-------------------------------------------------------------------------------------- + // copy assignment deleted + LLFile& operator=(const LLFile&) = delete; + + // move assignment + LLFile& operator=(LLFile&& other) noexcept + { + close(); + std::swap(mHandle, other.mHandle); + return *this; + } + + // detect whether the wrapped file descriptor/handle is open or not + explicit operator bool() const { return (mHandle != InvalidHandle); } + bool operator!() { return (mHandle == InvalidHandle); } + + //-------------------------------------------------------------------------------------- + // class member methods + //-------------------------------------------------------------------------------------- + + /// All of these functions take as one of their parameters a std::error_code object which can be used to + /// determine in more detail what error occurred if required + + /// Open a file with the specific open mode flags + int open(const std::string& filename, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666); + ///< @returns 0 on success, -1 on failure + + /// Determine the size of the opened file + S64 size(std::error_code& ec); + ///< @returns the number of bytes in the file or -1 on failure + + /// Return the current file pointer into the file + S64 tell(std::error_code& ec); + ///< @returns the absolute offset of the file pointer in bytes relative to the start of the file or -1 on failure + + /// Move the file pointer to the specified absolute position relative to the start of the file + int seek(S64 pos, std::error_code& ec); + ///< @returns 0 on success, -1 on failure + + /// Move the file pointer to the specified position relative to dir + int seek(S64 offset, std::ios_base::seekdir dir, std::error_code& ec); + ///< @returns 0 on success, -1 on failure + + /// Read a number of bytes into the buffer starting at the current file pointer + S64 read(void* buffer, S64 nbytes, std::error_code& ec); + ///< If the file ends before the requested amount of bytes could be read, the function succeeds and + /// returns the bytes up to the end of the file. The return value indicates the number of actually + /// read bytes and can be therefore smaller than the requested amount. + /// @returns the number of bytes read from the file or -1 on failure + + /// Write a number of bytes to the file starting at the current file pointer + S64 write(const void* buffer, S64 nbytes, std::error_code& ec); + /// @returns the number of bytes written to the file or -1 on failure + + /// Write into the file starting at the current file pointer using printf style format + S64 printf(const char* fmt, ...); + /// @returns the number of bytes written to the file or -1 on failure + + /// Attempt to acquire or release a lock on the file + int lock(int mode, std::error_code& ec); + ///< mode can be one of LLFile::exclusive or LLFile::shared to acquire the according lock + /// or 0 to give up an earlier acquired lock. Adding LLFile::noblock together with one of + /// the lock requests will cause the function to fail if the lock can not be acquired, + /// otherwise the function will block until the lock can be acquired. + /// @returns 0 on success, -1 on failure + + /// close the file explicitely + int close(std::error_code& ec); + ///< @returns 0 on success, -1 on failure + + /// Convinience function to close the file without parameters + int close(); + ///< @returns 0 on success, -1 on failure + + //---------------------------------------------------------------------------------------- + // static member functions + // + // All filename parameters are UTF8 file paths + // + //---------------------------------------------------------------------------------------- /// open a file with the specified access mode - static LLFILE* fopen(const std::string& filename, const char* accessmode); + static LLFILE* fopen(const std::string& filename, const char* accessmode, int lmode = 0); ///< '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 /// "w" open the file for reading and writing and truncate it to zero length /// "w+" open or create the file for reading and writing and truncate to zero length if it existed - /// "a" open the file for reading and writing and position the stream at the end of the file - /// "a+" open or create the file for reading and writing and position the stream at the end of the file + /// "a" open the file for reading and writing and and before every write position the stream at the end of the file + /// "a+" open or create the file for reading and writing and before every write position the stream at the end of the file /// /// in addition to these values, "b" can be appended to indicate binary stream access, but on Linux and Mac /// this is strictly for compatibility and has no effect. On Windows this makes the file functions not @@ -93,35 +254,37 @@ public: /// "b" or "t" is defined, Windows uses the value set by _fmode which by default is _O_TEXT. /// 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 + /// Other characters in 'accessmode', while possible on some platforms (Windows), will usually + /// cause an error as fopen will verify this parameter + /// + /// lmode is optional and allows to lock the file for other processes either as a shared lock or an + /// exclusive lock. If the requested lock conflicts with an already existing lock, the open fails. + /// Pass either LLFIle::exclusive or LLFile::shared to this parameter if you want to prevent other + /// processes from reading (exclusive lock) or writing (shared lock) to the file. It will always use + /// LLFile::noblock, meaning the open will immediately fail if it conflicts with an existing lock on the + /// file. + /// /// @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); - - /// 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 - /// overridden by the user's umask. It is ignored on Windows. - /// mkdir() considers "directory already exists" to be not an error. /// @returns 0 on success and -1 on failure. - //// remove a directory - static int rmdir(const std::string& filename, int suppress_error = 0); - ///< pass ENOENT in the optional 'suppress_error' parameter - /// if you don't want a warning in the log when the directory does not exist + /// create a directory + static int mkdir(const std::string& filename); + ///< mkdir() considers "directory already exists" to be not an error. /// @returns 0 on success and -1 on failure. /// remove a file or directory - static int remove(const std::string& filename, int suppress_error = 0); - ///< pass ENOENT in the optional 'suppress_error' parameter - /// if you don't want a warning in the log when the directory does not exist + static int remove(const std::string& filename, int suppress_warning = 0); + ///< pass ENOENT in the optional 'suppress_warning' parameter if you don't want + /// a warning in the log when the directory does not exist /// @returns 0 on success and -1 on failure. /// rename a file - static int rename(const std::string& filename, const std::string& newname, int suppress_error = 0); + static int rename(const std::string& filename, const std::string& newname, int suppress_warning = 0); ///< it will silently overwrite newname if it exists without returning an error /// Posix guarantees that if newname already exists, then there will be no moment /// in which for other processes newname does not exist. There is no such guarantee @@ -135,116 +298,103 @@ public: /// retrieve the content of a file into a string static std::string getContents(const std::string& filename); + static std::string getContents(const std::string& filename, std::error_code& ec); ///< @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 + static S64 read(const std::string& filename, void* buf, S64 offset, S64 nbytes); + static S64 read(const std::string& filename, void* buf, S64 offset, S64 nbytes, std::error_code& ec); + ///< @returns bytes read on success, or -1 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 + static S64 write(const std::string& filename, const void* buf, S64 offset, S64 nbytes); + static S64 write(const std::string& filename, const void* buf, S64 offset, S64 nbytes, std::error_code& ec); + ///< A negative offset will append the data to the end of the file + /// @returns bytes written on success, or -1 on failure /// return the file stat structure for filename - static int stat(const std::string& filename, llstat* file_status, int suppress_error = ENOENT); + static int stat(const std::string& filename, llstat* file_status, const char *fname = nullptr, int suppress_warning = ENOENT); ///< for compatibility with existing uses of LL_File::stat() we use ENOENT as default in the - /// optional 'suppress_error' parameter to avoid spamming the log with warnings when the API + /// optional 'suppress_warning' parameter to avoid spamming the log with warnings when the API /// is used to detect if a file exists /// @returns 0 on success and -1 on failure. + /// get the creation data and time of a file + static std::time_t getCreationTime(const std::string& filename, int suppress_warning = 0); + ///< @returns the creation time of the file or 0 on error + + /// get the last modification data and time of a file + static std::time_t getModificationTime(const std::string& filename, int suppress_warning = 0); + ///< @returns the modification time of the file or 0 on error + /// get the file or directory attributes for filename - static unsigned short getattr(const std::string& filename, bool dontFollowSymLink = false, int suppress_error = ENOENT); - ///< a more lightweight function on Windows to stat, that just returns the file attribute flags - /// 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 + static std::filesystem::file_status getStatus(const std::string& filename, bool dontFollowSymLink = false, int suppress_warning = ENOENT); + ///< 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_warning' 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, S_IFREG or S_IFLNK set, - /// together with the three access bits which under Windows only the write bit is relevant. + /// @returns a std::filesystem::file_status value that can be passed to the according std::filesystem::exists() + /// and other APIs accepting a file_status. /// 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. + static S64 size(const std::string& filename, int suppress_warning = ENOENT); + ///< we pass by default ENOENT in the optional 'suppress_warning' parameter to not spam + /// the log with warnings when the file does not exist + /// @returns the file size on success or -1 on failure. + + /// check if filename is an existing file or directory + static bool exists(const std::string& filename); + ///< @returns true if the path is for an existing file or directory /// check if filename is an existing directory - static bool isdir(const std::string& filename); + static bool isdir(const std::string& filename); ///< @returns true if the path is for an existing directory /// check if filename is an existing file - static bool isfile(const std::string& filename); + static bool isfile(const std::string& filename); ///< @returns true if the path is for an existing file /// check if filename is a symlink - static bool islink(const std::string& filename); + static bool islink(const std::string& filename); ///< @returns true if the path is pointing at a symlink /// return a path to the temporary directory on the system - static const char * tmpdir(); -}; + static const std::string& tmpdir(); -/// RAII class -class LLUniqueFile -{ -public: - // empty - LLUniqueFile(): mFileHandle(nullptr) {} - // wrap (e.g.) result of LLFile::fopen() - LLUniqueFile(LLFILE* f): mFileHandle(f) {} - // no copy - LLUniqueFile(const LLUniqueFile&) = delete; - // move construction - LLUniqueFile(LLUniqueFile&& other) noexcept - { - mFileHandle = other.mFileHandle; - other.mFileHandle = nullptr; - } - // The point of LLUniqueFile is to close on destruction. - ~LLUniqueFile() - { - close(); - } - - // simple assignment - LLUniqueFile& operator=(LLFILE* f) - { - close(); - mFileHandle = f; - return *this; - } - // copy assignment deleted - LLUniqueFile& operator=(const LLUniqueFile&) = delete; - // move assignment - LLUniqueFile& operator=(LLUniqueFile&& other) noexcept - { - close(); - std::swap(mFileHandle, other.mFileHandle); - return *this; - } - - // explicit close operation - void close() - { - if (mFileHandle) - { - // in case close() throws, set mFileHandle null FIRST - LLFILE* h{nullptr}; - std::swap(h, mFileHandle); - LLFile::close(h); - } - } - - // detect whether the wrapped LLFILE is open or not - explicit operator bool() const { return bool(mFileHandle); } - bool operator!() { return ! mFileHandle; } - - // LLUniqueFile should be usable for any operation that accepts LLFILE* - // (or FILE* for that matter) - operator LLFILE*() const { return mFileHandle; } + /// converts a string containing a path in utf8 encoding into an explicit filesystem path + static std::filesystem::path utf8StringToPath(const std::string& pathname); + ///< @returns the path as a std::filesystem::path private: - LLFILE* mFileHandle; +#if LL_WINDOWS + typedef HANDLE llfile_handle_t; + const llfile_handle_t InvalidHandle = INVALID_HANDLE_VALUE; +#else + typedef int llfile_handle_t; + const llfile_handle_t InvalidHandle = -1; +#endif + + //---------------------------------------------------------------------------------------- + // static member functions + //---------------------------------------------------------------------------------------- +#if LL_WINDOWS + /// convert a string containing a path in utf8 encoding into a Windows format std::wstring + static std::wstring utf8StringToWstring(const std::string& pathname); + ///< this will prepend the path with the Windows kernel object space prefix when the path is + /// equal or longer than MAX_PATH characters and do some sanitation on the path. + /// This allows the underlaying Windows APIs to process long path names. Do not pass such a path + /// to std::filesystem functions. These functions are not guaranteed to handle such paths properly. + /// It's only useful to pass the resulting string buffer to Microsoft Windows widechar APIs or + /// the Microsoft C runtime widechar file functions. + /// + /// Example: + /// + /// std::wstring file_path = utf8StringToWstring(filename); + /// HANDLE CreateFileW(file_path.c_str(), ......); + /// + /// @returns the path as a std::wstring path +#endif + llfile_handle_t mHandle; + std::ios_base::openmode mOpen; // Used to emulate std::ios_base::app under Windows }; #if LL_WINDOWS diff --git a/indra/llcrashlogger/llcrashlock.cpp b/indra/llcrashlogger/llcrashlock.cpp old mode 100644 new mode 100755 index bc34f6798f..731b53b7e9 --- a/indra/llcrashlogger/llcrashlock.cpp +++ b/indra/llcrashlogger/llcrashlock.cpp @@ -188,12 +188,7 @@ LLSD LLCrashLock::getProcessList() //static bool LLCrashLock::fileExists(std::string filename) { -#ifdef LL_WINDOWS // or BOOST_WINDOWS_API - boost::filesystem::path file_path(ll_convert(filename)); -#else - boost::filesystem::path file_path(filename); -#endif - return boost::filesystem::exists(file_path); + return LLFile::exists(filename); } void LLCrashLock::cleanupProcess(std::string proc_dir) diff --git a/indra/llfilesystem/lldir.cpp b/indra/llfilesystem/lldir.cpp old mode 100644 new mode 100755 index 190539cea5..4c51cc12ab --- a/indra/llfilesystem/lldir.cpp +++ b/indra/llfilesystem/lldir.cpp @@ -94,29 +94,19 @@ LLDir::~LLDir() std::vector LLDir::getFilesInDir(const std::string &dirname) { //Returns a vector of fullpath filenames. - -#ifdef LL_WINDOWS // or BOOST_WINDOWS_API - boost::filesystem::path p(ll_convert(dirname)); -#else - boost::filesystem::path p(dirname); -#endif - + std::filesystem::path p = LLFile::utf8StringToPath(dirname); std::vector v; - - boost::system::error_code ec; - if (exists(p, ec) && !ec.failed()) + std::error_code ec; + if (std::filesystem::is_directory(p, ec) && !ec) { - if (is_directory(p, ec) && !ec.failed()) + std::filesystem::directory_iterator end_iter; + for (std::filesystem::directory_iterator dir_itr(p); + dir_itr != end_iter; + ++dir_itr) { - boost::filesystem::directory_iterator end_iter; - for (boost::filesystem::directory_iterator dir_itr(p); - dir_itr != end_iter; - ++dir_itr) + if (std::filesystem::is_regular_file(dir_itr->status())) { - if (boost::filesystem::is_regular_file(dir_itr->status())) - { - v.push_back(dir_itr->path().filename().string()); - } + v.push_back(dir_itr->path().string()); } } } @@ -186,28 +176,23 @@ U32 LLDir::deleteDirAndContents(const std::string& dir_name) //Removes the directory and its contents. Returns number of files deleted. U32 num_deleted = 0; + std::filesystem::path dir_path = LLFile::utf8StringToPath(dir_name); try { -#ifdef LL_WINDOWS // or BOOST_WINDOWS_API - boost::filesystem::path dir_path(ll_convert(dir_name)); -#else - boost::filesystem::path dir_path(dir_name); -#endif - - if (boost::filesystem::exists(dir_path)) + if (std::filesystem::is_directory(dir_path)) { - if (!boost::filesystem::is_empty(dir_path)) + if (!std::filesystem::is_empty(dir_path)) { // Directory has content - num_deleted = (U32)boost::filesystem::remove_all(dir_path); + num_deleted = (U32)std::filesystem::remove_all(dir_path); } else { // Directory is empty - boost::filesystem::remove(dir_path); + std::filesystem::remove(dir_path); } } } - catch (boost::filesystem::filesystem_error &er) + catch (std::filesystem::filesystem_error &er) { LL_WARNS() << "Failed to delete " << dir_name << " with error " << er.code().message() << LL_ENDL; } @@ -1105,15 +1090,15 @@ void dir_exists_or_crash(const std::string &dir_name) #if LL_WINDOWS // *FIX: lame - it doesn't do the same thing on windows. not so // important since we don't deploy simulator to windows boxes. - LLFile::mkdir(dir_name, 0700); + LLFile::mkdir(dir_name); #else - struct stat dir_stat; + llstat dir_stat; if(0 != LLFile::stat(dir_name, &dir_stat)) { S32 stat_rv = errno; if(ENOENT == stat_rv) { - if(0 != LLFile::mkdir(dir_name, 0700)) // octal + if(0 != LLFile::mkdir(dir_name)) { LL_ERRS() << "Unable to create directory: " << dir_name << LL_ENDL; } diff --git a/indra/llfilesystem/lldir_win32.cpp b/indra/llfilesystem/lldir_win32.cpp old mode 100644 new mode 100755 index 58c080c982..2b478e5dce --- a/indra/llfilesystem/lldir_win32.cpp +++ b/indra/llfilesystem/lldir_win32.cpp @@ -376,18 +376,7 @@ std::string LLDir_Win32::getCurPath() bool LLDir_Win32::fileExists(const std::string &filename) const { - llstat stat_data; - // Check the age of the file - // Now, we see if the files we've gathered are recent... - int res = LLFile::stat(filename, &stat_data); - if (!res) - { - return true; - } - else - { - return false; - } + return LLFile::exists(filename); } diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp old mode 100644 new mode 100755 index 541266af4f..4238dd64e9 --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -77,13 +77,8 @@ bool LLFileSystem::getExists(const LLUUID& file_id, const LLAssetType::EType fil LL_PROFILE_ZONE_SCOPED; const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type); - llifstream file(filename, std::ios::binary); - if (file.is_open()) - { - file.seekg(0, std::ios::end); - return file.tellg() > 0; - } - return false; + // not only test for existence but for the file to be not empty + return LLFile::size(filename) > 0; } // static @@ -120,15 +115,7 @@ S32 LLFileSystem::getFileSize(const LLUUID& file_id, const LLAssetType::EType fi { const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type); - S32 file_size = 0; - llifstream file(filename, std::ios::binary); - if (file.is_open()) - { - file.seekg(0, std::ios::end); - file_size = (S32)file.tellg(); - } - - return file_size; + return (S32)LLFile::size(filename); } bool LLFileSystem::read(U8* buffer, S32 bytes) diff --git a/indra/llfilesystem/tests/lldir_test.cpp b/indra/llfilesystem/tests/lldir_test.cpp old mode 100644 new mode 100755 index 13db6fad80..17e4ffecf1 --- a/indra/llfilesystem/tests/lldir_test.cpp +++ b/indra/llfilesystem/tests/lldir_test.cpp @@ -558,8 +558,8 @@ namespace tut LLFile::remove(dir1files[i]); LLFile::remove(dir2files[i]); } - LLFile::rmdir(dir1); - LLFile::rmdir(dir2); + LLFile::remove(dir1); + LLFile::remove(dir2); } template<> template<> diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp old mode 100644 new mode 100755 index 2c35a6acae..49df9cd88c --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -1059,7 +1059,7 @@ void LLShaderMgr::clearShaderCache() LL_INFOS("ShaderMgr") << "Removing shader cache at " << shader_cache << LL_ENDL; const std::string mask = "*"; gDirUtilp->deleteFilesInDir(shader_cache, mask); - LLFile::rmdir(shader_cache); + LLFile::remove(shader_cache); mShaderBinaryCache.clear(); } @@ -1131,11 +1131,11 @@ bool LLShaderMgr::loadCachedProgramBinary(LLGLSLShader* shader) { std::vector in_data; in_data.resize(shader_info.mBinaryLength); - - LLUniqueFile filep = LLFile::fopen(in_path, "rb"); + std::error_code ec; + LLFile filep = LLFile(in_path, LLFile::in | LLFile::binary, ec); if (filep) { - size_t result = fread(in_data.data(), sizeof(U8), in_data.size(), filep); + size_t result = filep.read(in_data.data(), in_data.size(), ec); filep.close(); if (result == in_data.size()) @@ -1180,11 +1180,12 @@ bool LLShaderMgr::saveCachedProgramBinary(LLGLSLShader* shader) if (error == GL_NO_ERROR) { std::string out_path = gDirUtilp->add(mShaderCacheDir, shader->mShaderHash.asString() + ".shaderbin"); - LLUniqueFile outfile = LLFile::fopen(out_path, "wb"); - if (outfile) + std::error_code ec; + LLFile filep = LLFile(out_path, LLFile::out | LLFile::binary, ec); + if (filep) { - fwrite(program_binary.data(), sizeof(U8), program_binary.size(), outfile); - outfile.close(); + filep.write(program_binary.data(), program_binary.size(), ec); + filep.close(); binary_info.mLastUsedTime = (F32)LLTimer::getTotalSeconds(); diff --git a/indra/llxml/tests/llcontrol_test.cpp b/indra/llxml/tests/llcontrol_test.cpp old mode 100644 new mode 100755 index 4192e029c5..e15117fc29 --- a/indra/llxml/tests/llcontrol_test.cpp +++ b/indra/llxml/tests/llcontrol_test.cpp @@ -69,7 +69,7 @@ namespace tut LLFile::remove(filename); } LLFile::remove(mTestConfigFile); - LLFile::rmdir(mTestConfigDir); + LLFile::remove(mTestConfigDir); } void writeSettingsFile(const LLSD& config) { diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp old mode 100644 new mode 100755 index f6e3139cc4..962a91bb73 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3031,13 +3031,11 @@ void LLAppViewer::initStrings() } else { - llstat st; - int rc = LLFile::stat(strings_path_full, &st); - if (rc != 0) + if (!LLFile::exists(strings_path_full)) { - crash_reason = "The file '" + strings_path_full + "' failed to get status. Error code: " + std::to_string(rc); + crash_reason = "The file '" + strings_path_full + "' doesn't seem to exist"; } - else if (S_ISDIR(st.st_mode)) + else if (LLFile::isdir(strings_path_full)) { crash_reason = "The filename '" + strings_path_full + "' is a directory name"; } @@ -4293,7 +4291,7 @@ void LLAppViewer::migrateCacheDirectory() LLFile::remove(ds_store); } #endif - if (LLFile::rmdir(old_cache_dir) != 0) + if (LLFile::remove(old_cache_dir) != 0) { LL_WARNS() << "could not delete old cache directory " << old_cache_dir << LL_ENDL; } diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp old mode 100644 new mode 100755 index c5c1e01538..66066a45b2 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -1500,7 +1500,7 @@ bool LLFloaterPreference::moveTranscriptsAndLog() //Couldn't move the log and created a new directory so remove the new directory if(madeDirectory) { - LLFile::rmdir(chatLogPath); + LLFile::remove(chatLogPath); } return false; } @@ -1526,7 +1526,7 @@ bool LLFloaterPreference::moveTranscriptsAndLog() if(madeDirectory) { - LLFile::rmdir(chatLogPath); + LLFile::remove(chatLogPath); } return false; @@ -2031,17 +2031,15 @@ void LLFloaterPreference::changed() { if (LLConversationLog::instance().getIsLoggingEnabled()) { - getChild("clear_log")->setEnabled(LLConversationLog::instance().getConversations().size() > 0); + getChild("clear_log")->setEnabled(LLConversationLog::instance().getConversations().size() > 0); } else { // onClearLog clears list, then notifies changed() and only then clears file, // so check presence of conversations before checking file, file will cleared later. - llstat st; bool has_logs = LLConversationLog::instance().getConversations().size() > 0 - && LLFile::stat(LLConversationLog::instance().getFileName(), &st) == 0 - && S_ISREG(st.st_mode) - && st.st_size > 0; + && LLFile::isfile(LLConversationLog::instance().getFileName()) + && LLFile::size(LLConversationLog::instance().getFileName()) > 0; getChild("clear_log")->setEnabled(has_logs); } diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp old mode 100644 new mode 100755 index c3bc24c6b9..db98a70573 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -892,8 +892,7 @@ void LLFloaterUIPreview::displayFloater(bool click, S32 ID) // Add localization to title so user knows whether it's localized or defaulted to en 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::isfile(full_path.c_str())) // if the file does not exist { floater_lang = getLocStr(ID); } @@ -966,9 +965,8 @@ void LLFloaterUIPreview::onClickEditFloater() } file_path = getLocalizedDirectory() + file_name; - // 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 + // does it exist? (some localized versions may not have it there are no diffs, and then we try to open an nonexistent file + if(!LLFile::isfile(file_path.c_str())) // 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 @@ -1124,8 +1122,7 @@ void LLFloaterUIPreview::onClickToggleDiffHighlighting() error = true; } - 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::isfile(path_in_textfield.c_str()) && !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/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp old mode 100644 new mode 100755 index 9a991727b2..4fe661b055 --- a/indra/newview/llpreviewnotecard.cpp +++ b/indra/newview/llpreviewnotecard.cpp @@ -575,8 +575,7 @@ void LLPreviewNotecard::syncExternal() { // Sync with external editor. std::string tmp_file = getTmpFileName(); - llstat s; - if (LLFile::stat(tmp_file, &s) == 0) // file exists + if (LLFile::isfile(tmp_file)) // file exists { if (mLiveFile) mLiveFile->ignoreNextUpdate(); writeToFile(tmp_file); diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp old mode 100644 new mode 100755 index c2aa4925bd..2c436198e3 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -694,8 +694,7 @@ void LLScriptEdCore::sync() if (mLiveFile) { std::string tmp_file = mLiveFile->filename(); - llstat s; - if (LLFile::stat(tmp_file, &s) == 0) // file exists + if (LLFile::isfile(tmp_file)) // file exists { mLiveFile->ignoreNextUpdate(); writeToFile(tmp_file); diff --git a/indra/newview/lltexturecache.cpp b/indra/newview/lltexturecache.cpp old mode 100644 new mode 100755 index e59cf70177..ef7542fe2e --- a/indra/newview/lltexturecache.cpp +++ b/indra/newview/lltexturecache.cpp @@ -1614,7 +1614,7 @@ void LLTextureCache::purgeAllTextures(bool purge_directories) gDirUtilp->deleteFilesInDir(mTexturesDirName, mask); // headers, fast cache if (purge_directories) { - LLFile::rmdir(mTexturesDirName); + LLFile::remove(mTexturesDirName); } } mHeaderIDMap.clear(); diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp old mode 100644 new mode 100755 index 35ac7919ac..7534d778a5 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -1140,15 +1140,12 @@ std::string getProfileStatsFilename() // same second), may produce (e.g.) sec==61, but avoids collisions and // preserves chronological filename sort order. std::string name; - std::error_code ec; do { // base + missing 2-digit seconds, append ".json" // post-increment sec in case we have to try again name = stringize(base, std::setw(2), std::setfill('0'), sec++, ".json"); - } while (std::filesystem::exists(fsyspath(name), ec)); - // Ignoring ec means we might potentially return a name that does already - // exist -- but if we can't check its existence, what more can we do? + } while (LLFile::exists(fsyspath(name))); return name; } diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp old mode 100644 new mode 100755 index a77b9f6103..5cb05460bc --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -1827,12 +1827,11 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ user_data_path_cache += gDirUtilp->getDirDelimiter(); // See if the plugin executable exists - llstat s; - if(LLFile::stat(launcher_name, &s)) + if (!LLFile::isfile(launcher_name)) { LL_WARNS_ONCE("Media") << "Couldn't find launcher at " << launcher_name << LL_ENDL; } - else if(LLFile::stat(plugin_name, &s)) + else if (!LLFile::isfile(plugin_name)) { #if !LL_LINUX LL_WARNS_ONCE("Media") << "Couldn't find plugin at " << plugin_name << LL_ENDL; diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp old mode 100644 new mode 100755 index 9537ad745b..cdc41baa88 --- a/indra/newview/llvocache.cpp +++ b/indra/newview/llvocache.cpp @@ -1250,7 +1250,7 @@ void LLVOCache::removeCache(ELLPath location, bool started) std::string cache_dir = gDirUtilp->getExpandedFilename(location, object_cache_dirname); LL_INFOS() << "Removing cache at " << cache_dir << LL_ENDL; gDirUtilp->deleteFilesInDir(cache_dir, mask); //delete all files - LLFile::rmdir(cache_dir); + LLFile::remove(cache_dir); clearCacheInMemory(); mInitialized = false; diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp old mode 100644 new mode 100755 index d132cbfa36..e58a6577f1 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -943,8 +943,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() gDirUtilp->append(exe_path, "SLVoice"); #endif // See if the vivox executable exists - llstat s; - if (!LLFile::stat(exe_path, &s)) + if (LLFile::isfile(exe_path)) { // vivox executable exists. Build the command line and launch the daemon. LLProcess::Params params; diff --git a/indra/test/llmessageconfig_tut.cpp b/indra/test/llmessageconfig_tut.cpp old mode 100644 new mode 100755 index 93443467a2..b8942e99b3 --- a/indra/test/llmessageconfig_tut.cpp +++ b/indra/test/llmessageconfig_tut.cpp @@ -62,7 +62,7 @@ namespace tut int rmfile = LLFile::remove((mTestConfigDir + "/message.xml")); ensure_equals("rmfile value", rmfile, 0); // rm temp dir - int rmdir = LLFile::rmdir(mTestConfigDir); + int rmdir = LLFile::remove(mTestConfigDir); ensure_equals("rmdir value", rmdir, 0); } diff --git a/indra/test/message_tut.cpp b/indra/test/message_tut.cpp old mode 100644 new mode 100755 index 11cd710ef6..6fb2b92803 --- a/indra/test/message_tut.cpp +++ b/indra/test/message_tut.cpp @@ -113,7 +113,7 @@ namespace tut ensure_equals("rmfile value", rmfile, 0); // rm temp dir - int rmdir = LLFile::rmdir(mTestConfigDir); + int rmdir = LLFile::remove(mTestConfigDir); ensure_equals("rmdir value", rmdir, 0); } -- cgit v1.3 From c92b0b74cbd963cd79d1cb7754256b801f1479b1 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 11 Dec 2025 01:42:52 +0200 Subject: Revert #4899 "Add more functionality to LLFile and cleanup LLAPRFile" Interferes with linux work, will be moved to a different branch and applied separately. --- indra/llaudio/llaudiodecodemgr.cpp | 2 +- indra/llcommon/llapp.cpp | 4 +- indra/llcommon/llapr.cpp | 220 ++++++ indra/llcommon/llapr.h | 19 + indra/llcommon/llerror.cpp | 10 +- indra/llcommon/llfile.cpp | 1136 ++++++++----------------------- indra/llcommon/llfile.h | 488 ++++--------- indra/llcrashlogger/llcrashlock.cpp | 7 +- indra/llfilesystem/lldir.cpp | 56 +- indra/llfilesystem/lldir_win32.cpp | 13 +- indra/llfilesystem/llfilesystem.cpp | 29 +- indra/llfilesystem/llfilesystem.h | 4 +- indra/llfilesystem/tests/lldir_test.cpp | 4 +- indra/llmessage/llassetstorage.cpp | 2 +- indra/llrender/llshadermgr.cpp | 22 +- indra/llxml/tests/llcontrol_test.cpp | 2 +- indra/newview/llappviewer.cpp | 28 +- indra/newview/llfloaterpreference.cpp | 12 +- indra/newview/llfloateruipreview.cpp | 37 +- indra/newview/llpreviewnotecard.cpp | 3 +- indra/newview/llpreviewscript.cpp | 3 +- indra/newview/lltexturecache.cpp | 89 ++- indra/newview/llviewerassetstorage.cpp | 2 +- indra/newview/llviewerassetupload.cpp | 39 +- indra/newview/llviewerdisplay.cpp | 5 +- indra/newview/llviewermedia.cpp | 5 +- indra/newview/llvocache.cpp | 6 +- indra/newview/llvoicevivox.cpp | 3 +- indra/test/CMakeLists.txt | 13 +- indra/test/llfile_tut.cpp | 253 ------- indra/test/llmessageconfig_tut.cpp | 2 +- indra/test/message_tut.cpp | 2 +- 32 files changed, 910 insertions(+), 1610 deletions(-) mode change 100755 => 100644 indra/llcommon/llerror.cpp mode change 100755 => 100644 indra/llcommon/llfile.cpp mode change 100755 => 100644 indra/llcommon/llfile.h mode change 100755 => 100644 indra/llcrashlogger/llcrashlock.cpp mode change 100755 => 100644 indra/llfilesystem/lldir.cpp mode change 100755 => 100644 indra/llfilesystem/lldir_win32.cpp mode change 100755 => 100644 indra/llfilesystem/llfilesystem.cpp mode change 100755 => 100644 indra/llfilesystem/tests/lldir_test.cpp mode change 100755 => 100644 indra/llrender/llshadermgr.cpp mode change 100755 => 100644 indra/llxml/tests/llcontrol_test.cpp mode change 100755 => 100644 indra/newview/llappviewer.cpp mode change 100755 => 100644 indra/newview/llfloaterpreference.cpp mode change 100755 => 100644 indra/newview/llfloateruipreview.cpp mode change 100755 => 100644 indra/newview/llpreviewnotecard.cpp mode change 100755 => 100644 indra/newview/llpreviewscript.cpp mode change 100755 => 100644 indra/newview/lltexturecache.cpp mode change 100755 => 100644 indra/newview/llviewerdisplay.cpp mode change 100755 => 100644 indra/newview/llviewermedia.cpp mode change 100755 => 100644 indra/newview/llvocache.cpp mode change 100755 => 100644 indra/newview/llvoicevivox.cpp mode change 100755 => 100644 indra/test/CMakeLists.txt delete mode 100755 indra/test/llfile_tut.cpp mode change 100755 => 100644 indra/test/llmessageconfig_tut.cpp mode change 100755 => 100644 indra/test/message_tut.cpp (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index 232b429130..d8a6fffea6 100755 --- a/indra/llaudio/llaudiodecodemgr.cpp +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -199,7 +199,7 @@ bool LLVorbisDecodeState::initDecode() LL_DEBUGS("AudioEngine") << "Initing decode from vfile: " << mUUID << LL_ENDL; mInFilep = new LLFileSystem(mUUID, LLAssetType::AT_SOUND); - if (!mInFilep || mInFilep->getSize() <= 0) + if (!mInFilep || !mInFilep->getSize()) { LL_WARNS("AudioEngine") << "unable to open vorbis source vfile for reading" << LL_ENDL; delete mInFilep; diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 5829d2be49..c532620daa 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -37,7 +37,7 @@ #endif #include "llcommon.h" - +#include "llapr.h" #include "llerrorcontrol.h" #include "llframetimer.h" #include "lllivefile.h" @@ -53,8 +53,6 @@ // // Signal handling #ifndef LL_WINDOWS -#include "apr_signal.h" - # include # include // for fork() void setup_signals(); diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index 22bed48542..eeff2694a7 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -526,6 +526,226 @@ S32 LLAPRFile::seek(apr_file_t* file_handle, apr_seek_where_t where, S32 offset) } } +//static +S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) +{ + LL_PROFILE_ZONE_SCOPED; + //***************************************** + LLAPRFilePoolScope scope(pool); + apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), APR_READ|APR_BINARY); + //***************************************** + if (!file_handle) + { + return 0; + } + + llassert(offset >= 0); + + if (offset > 0) + offset = LLAPRFile::seek(file_handle, APR_SET, offset); + + apr_size_t bytes_read; + if (offset < 0) + { + bytes_read = 0; + } + else + { + bytes_read = nbytes ; + apr_status_t s = apr_file_read(file_handle, buf, &bytes_read); + if (s != APR_SUCCESS) + { + LL_WARNS("APR") << " Attempting to read filename: " << filename << LL_ENDL; + ll_apr_warn_status(s); + bytes_read = 0; + } + else + { + llassert_always(bytes_read <= 0x7fffffff); + } + } + + //***************************************** + close(file_handle) ; + //***************************************** + return (S32)bytes_read; +} + +//static +S32 LLAPRFile::writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) +{ + LL_PROFILE_ZONE_SCOPED; + apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY; + if (offset < 0) + { + flags |= APR_APPEND; + offset = 0; + } + + //***************************************** + LLAPRFilePoolScope scope(pool); + apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), flags); + //***************************************** + if (!file_handle) + { + return 0; + } + + if (offset > 0) + { + offset = LLAPRFile::seek(file_handle, APR_SET, offset); + } + + apr_size_t bytes_written; + if (offset < 0) + { + bytes_written = 0; + } + else + { + bytes_written = nbytes ; + apr_status_t s = apr_file_write(file_handle, buf, &bytes_written); + if (s != APR_SUCCESS) + { + LL_WARNS("APR") << "Attempting to write filename: " << filename << LL_ENDL; + if (APR_STATUS_IS_ENOSPC(s)) + { + LLApp::notifyOutOfDiskSpace(); + } + ll_apr_warn_status(s); + bytes_written = 0; + } + else + { + llassert_always(bytes_written <= 0x7fffffff); + } + } + + //***************************************** + LLAPRFile::close(file_handle); + //***************************************** + + 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 72458c6302..11e474b5dd 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -35,6 +35,12 @@ #include "llwin32headers.h" #include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_signal.h" + +#include "llstring.h" + +#include "mutex.h" struct apr_dso_handle_t; /** @@ -178,7 +184,20 @@ private: static apr_file_t* open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags); 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/llerror.cpp b/indra/llcommon/llerror.cpp old mode 100755 new mode 100644 index bfa8bca224..b14464382b --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -435,9 +435,13 @@ namespace std::string file = user_dir + "/logcontrol-dev.xml"; - if (!LLFile::isfile(file)) - { - file = app_dir + "/logcontrol.xml"; + llstat stat_info; + if (LLFile::stat(file, &stat_info)) { + // NB: stat returns non-zero if it can't read the file, for example + // if it doesn't exist. LLFile has no better abstraction for + // testing for file existence. + + file = app_dir + "/logcontrol.xml"; } return * new LogControlFile(file); // NB: This instance is never freed diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp old mode 100755 new mode 100644 index a1d41cdf73..a539e4fe28 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -29,17 +29,22 @@ #include "linden_common.h" #include "llfile.h" +#include "llstring.h" #include "llerror.h" #include "stringize.h" #if LL_WINDOWS -#include +#include "llwin32headers.h" +#include #else #include -#include #endif -// Some of the methods below use OS-level functions that mess with errno. Wrap +using namespace std; + +static std::string empty; + +// Many of the methods below use OS-level functions that mess with errno. Wrap // variants of strerror() to report errors. #if LL_WINDOWS @@ -74,7 +79,6 @@ static errentry const errtable[] { ERROR_CURRENT_DIRECTORY, EACCES }, // 16 { ERROR_NOT_SAME_DEVICE, EXDEV }, // 17 { ERROR_NO_MORE_FILES, ENOENT }, // 18 - { ERROR_SHARING_VIOLATION, EACCES }, // 32 { ERROR_LOCK_VIOLATION, EACCES }, // 33 { ERROR_BAD_NETPATH, ENOENT }, // 53 { ERROR_NETWORK_ACCESS_DENIED, EACCES }, // 65 @@ -105,25 +109,22 @@ static errentry const errtable[] { ERROR_NOT_ENOUGH_QUOTA, ENOMEM } // 1816 }; -static int get_errno_from_oserror(int oserr) +static int set_errno_from_oserror(unsigned long oserr) { if (!oserr) return 0; // Check the table for the Windows OS error code - for (const struct errentry& entry : errtable) + for (const struct errentry &entry : errtable) { if (oserr == entry.oserr) { - return entry.errcode; + _set_errno(entry.errcode); + return -1; } } - return EINVAL; -} -static int set_errno_from_oserror(unsigned long oserr) -{ - _set_errno(get_errno_from_oserror(oserr)); + _set_errno(EINVAL); return -1; } @@ -135,8 +136,69 @@ std::string strerr(int errn) return buffer; } -#else +inline bool is_slash(wchar_t const c) +{ + return c == L'\\' || c == L'/'; +} +static std::wstring utf8path_to_wstring(const std::string& utf8path) +{ + if (utf8path.size() >= MAX_PATH) + { + // By prepending "\\?\" to a path, Windows widechar file APIs will not fail on long path names + std::wstring utf16path = L"\\\\?\\" + ll_convert(utf8path); + // We need to make sure that the path does not contain forward slashes as above + // prefix does bypass the path normalization that replaces slashes with backslashes + // before passing the path to kernel mode APIs + std::replace(utf16path.begin(), utf16path.end(), L'/', L'\\'); + return utf16path; + } + return ll_convert(utf8path); +} + +static unsigned short get_fileattr(const std::wstring& utf16path, bool dontFollowSymLink = false) +{ + unsigned long flags = FILE_FLAG_BACKUP_SEMANTICS; + if (dontFollowSymLink) + { + flags |= FILE_FLAG_OPEN_REPARSE_POINT; + } + HANDLE file_handle = CreateFileW(utf16path.c_str(), FILE_READ_ATTRIBUTES, + FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, flags, nullptr); + if (file_handle != INVALID_HANDLE_VALUE) + { + FILE_ATTRIBUTE_TAG_INFO attribute_info; + if (GetFileInformationByHandleEx(file_handle, FileAttributeTagInfo, &attribute_info, sizeof(attribute_info))) + { + // A volume path alone (only drive letter) is not recognized as directory while it technically is + 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 |= (attribute_info.FileAttributes & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD | S_IWRITE; + // we do not try to guess 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; + } + } + // Retrieve last error and set errno before calling CloseHandle() + set_errno_from_oserror(GetLastError()); + + if (file_handle != INVALID_HANDLE_VALUE) + { + CloseHandle(file_handle); + } + return 0; +} + +#else // On Posix we want to call strerror_r(), but alarmingly, there are two // different variants. The one that returns int always populates the passed // buffer (except in case of error), whereas the other one always returns a @@ -183,50 +245,9 @@ std::string strerr(int errn) return message_from(errn, buffer, sizeof(buffer), strerror_r(errn, buffer, sizeof(buffer))); } - #endif // ! LL_WINDOWS -#if LL_WINDOWS && 0 // turn on to debug file-locking problems -#define PROCESS_LOCKING_CHECK 1 -static void find_locking_process(const std::string& filename) -{ - // Only do any of this stuff (before LL_ENDL) if it will be logged. - LL_DEBUGS("LLFile") << ""; - // wrong way - std::string TEMP = LLFile::tmpdir(); - if (TEMP.empty()) - { - 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 - -static int warnif(const std::string& desc, const std::string& filename, int rc, int suppress_warning = 0) +static int warnif(const std::string& desc, const std::string& filename, int rc, int accept = 0) { if (rc < 0) { @@ -235,930 +256,319 @@ static int warnif(const std::string& desc, const std::string& filename, int rc, // For certain operations, a particular errno value might be // acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit - // EEXIST. Don't log a warning if caller explicitly says this errno is okay. - if (errn != suppress_warning) + // EEXIST. Don't warn if caller explicitly says this errno is okay. + if (errn != accept) { - LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL; + LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename + << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL; } -#if PROCESS_LOCKING_CHECK +#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 + if (errn == EACCES) // *not* EPERM { - find_locking_process(filename); + // 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 +#endif // LL_WINDOWS hack to identify processes holding file open } return rc; } -static int warnif(const std::string& desc, const std::string& filename, const std::error_code& ec, int suppress_warning = 0) +// static +int LLFile::mkdir(const std::string& dirname, int perms) { - if (ec) - { - // get Posix errno from the std::error_code so we can compare it to the suppress_warning parameter - // to see when a caller wants us to not generate a warning for a particular error code + // We often use mkdir() to ensure the existence of a directory that might + // already exist. There is no known case in which we want to call out as + // an error the requested directory already existing. #if LL_WINDOWS - int errn = get_errno_from_oserror(ec.value()); -#else - int errn = ec.value(); -#endif - // For certain operations, a particular errno value might be acceptable - // Don't warn if caller explicitly says this errno is okay. - if (errn != suppress_warning) - { - LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename << "' (errno " << errn << "): " << ec.message() << LL_ENDL; - } -#if PROCESS_LOCKING_CHECK - // Try to detect locked files by other processes - if (ec.value() == ERROR_SHARING_VIOLATION || ec.value() == ERROR_LOCK_VIOLATION) + // permissions are ignored on Windows + int rc = 0; + std::wstring utf16dirname = utf8path_to_wstring(dirname); + if (!CreateDirectoryW(utf16dirname.c_str(), nullptr)) + { + // Only treat other errors than an already existing file as a real error + unsigned long oserr = GetLastError(); + if (oserr != ERROR_ALREADY_EXISTS) { - find_locking_process(filename); + rc = set_errno_from_oserror(oserr); } -#endif - return -1; - } - return 0; -} - -#if LL_WINDOWS - -inline int set_ec_from_system_error(std::error_code& ec, DWORD error) -{ - ec.assign(error, std::system_category()); - return -1; -} - -static int set_ec_from_system_error(std::error_code& ec) -{ - return set_ec_from_system_error(ec, GetLastError()); -} - -inline int set_ec_to_parameter_error(std::error_code& ec) -{ - return set_ec_from_system_error(ec, ERROR_INVALID_PARAMETER); -} - -inline int set_ec_to_outofmemory_error(std::error_code& ec) -{ - return set_ec_from_system_error(ec, ERROR_NOT_ENOUGH_MEMORY); -} - -inline DWORD decode_access_mode(std::ios_base::openmode omode) -{ - switch (omode & (LLFile::in | LLFile::out)) - { - case LLFile::in: - return GENERIC_READ; - case LLFile::out: - return GENERIC_WRITE; - case LLFile::in | LLFile::out: - return GENERIC_READ | GENERIC_WRITE; - } - if (omode & LLFile::app) - { - return GENERIC_WRITE; - } - return 0; -} - -inline DWORD decode_open_create_flags(std::ios_base::openmode omode) -{ - if (omode & LLFile::noreplace) - { - return CREATE_NEW; // create if it does not exist, otherwise fail - } - if (omode & LLFile::trunc) - { - if (!(omode & LLFile::out)) - { - return TRUNCATE_EXISTING; // open and truncate if it exists, otherwise fail - } - return CREATE_ALWAYS; // open and truncate if it exists, otherwise create it - } - if (!(omode & LLFile::out)) - { - return OPEN_EXISTING; // open if it exists, otherwise fail - } - // LLFile::app or (LLFile::out and (!LLFile::trunc or !LLFile::noreplace)) - return OPEN_ALWAYS; // open if it exists, otherwise create it -} - -inline DWORD decode_share_mode(int omode) -{ - if (omode & LLFile::exclusive) - { - return 0; // allow no other access - } - if (omode & LLFile::shared) - { - return FILE_SHARE_READ; // allow read access } - return FILE_SHARE_READ | FILE_SHARE_WRITE; // allow read and write access to others -} - -inline DWORD decode_attributes(std::ios_base::openmode omode, int perm) -{ - return (perm & S_IWRITE) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_READONLY; -} - -// Under Windows the values for the std::ios_base::seekdir constants match the according FILE_BEGIN -// and other constants but we do a programmatic translation for now to be sure -static DWORD seek_mode_from_dir(std::ios_base::seekdir seekdir) -{ - switch (seekdir) - { - case LLFile::beg: - return FILE_BEGIN; - case LLFile::cur: - return FILE_CURRENT; - case LLFile::end: - return FILE_END; - } - return FILE_BEGIN; -} - #else - -inline int set_ec_from_system_error(std::error_code& ec, int error) -{ - ec.assign(error, std::system_category()); - return -1; -} - -static int set_ec_from_system_error(std::error_code& ec) -{ - return set_ec_from_system_error(ec, errno); -} - -inline int set_ec_to_parameter_error(std::error_code& ec) -{ - return set_ec_from_system_error(ec, EINVAL); -} - -inline int set_ec_to_outofmemory_error(std::error_code& ec) -{ - return set_ec_from_system_error(ec, ENOMEM); -} - -inline int decode_access_mode(std::ios_base::openmode omode) -{ - switch (omode & (LLFile::in | LLFile::out)) + int rc = ::mkdir(dirname.c_str(), (mode_t)perms); + if (rc < 0 && errno == EEXIST) { - case LLFile::out: - return O_WRONLY; - case LLFile::in | LLFile::out: - return O_RDWR; - } - return O_RDONLY; -} - -inline int decode_open_mode(std::ios_base::openmode omode) -{ - int flags = O_CREAT | decode_access_mode(omode); - if (omode & LLFile::app) - { - flags |= O_APPEND; - } - if (omode & LLFile::trunc) - { - flags |= O_TRUNC; - } - if (omode & LLFile::binary) - { - // Not a thing under *nix - } - if (omode & LLFile::noreplace) - { - flags |= O_EXCL; - } - return flags; -} - -inline int decode_lock_mode(std::ios_base::openmode omode) -{ - int lmode = omode & LLFile::noblock ? LOCK_NB : 0; - if (omode & LLFile::lock_mask) - { - if (omode & LLFile::exclusive) - { - return lmode | LOCK_EX; - } - return lmode | LOCK_SH; - } - return lmode | LOCK_UN; -} - -// Under Linux and Mac the values for the std::ios_base::seekdir constants match the according SEEK_SET -// and other constants but we do a programmatic translation for now to be sure -inline int seek_mode_from_dir(std::ios_base::seekdir seekdir) -{ - switch (seekdir) - { - case LLFile::beg: - return SEEK_SET; - case LLFile::cur: - return SEEK_CUR; - case LLFile::end: - return SEEK_END; + // this is not the error you want, move along + return 0; } - return SEEK_SET; -} - #endif - -inline int clear_error(std::error_code& ec) -{ - ec.clear(); - return 0; -} - -inline bool are_open_mode_flags_invalid(std::ios_base::openmode omode) -{ - // at least one of input or output needs to be specified - if (!(omode & (LLFile::in | LLFile::out))) - { - return true; - } - // output must be possible for any of the extra options - if (!(omode & LLFile::out) && (omode & (LLFile::trunc | LLFile::app | LLFile::noreplace))) - { - return true; - } - // invalid combination, mutually exclusive - if ((omode & LLFile::app) && (omode & (LLFile::trunc | LLFile::noreplace))) - { - return true; - } - return false; + // anything else might be a problem + return warnif("mkdir", dirname, rc); } -//---------------------------------------------------------------------------------------- -// class member functions -//---------------------------------------------------------------------------------------- -int LLFile::open(const std::string& filename, std::ios_base::openmode omode, std::error_code& ec, int perm) +// static +int LLFile::rmdir(const std::string& dirname, int suppress_error) { - close(ec); - if (are_open_mode_flags_invalid(omode)) - { - return set_ec_to_parameter_error(ec); - } #if LL_WINDOWS - DWORD access = decode_access_mode(omode), - share = decode_share_mode(omode), - create = decode_open_create_flags(omode), - attributes = decode_attributes(omode, perm); - - std::wstring file_path = utf8StringToWstring(filename); - mHandle = CreateFileW(file_path.c_str(), access, share, nullptr, create, attributes, nullptr); - // The dwShareMode = share parameter takes care of locking the file for other processes if indicated, - // no need to do anything else for file locking here + std::wstring utf16dirname = utf8path_to_wstring(dirname); + int rc = _wrmdir(utf16dirname.c_str()); #else - int oflags = decode_open_mode(omode); - int lmode = omode & LLFile::lock_mask; - mHandle = ::open(filename.c_str(), oflags, perm); - if (mHandle != InvalidHandle && lmode && lock(lmode | LLFile::noblock, ec) != 0) - { - close(); - return -1; - } + int rc = ::rmdir(dirname.c_str()); #endif - if (mHandle == InvalidHandle) - { - return set_ec_from_system_error(ec); - } - - if (omode & LLFile::ate && seek(0, LLFile::end, ec) != 0) - { - close(); - return -1; - } - mOpen = omode; - return clear_error(ec); + return warnif("rmdir", dirname, rc, suppress_error); } -S64 LLFile::size(std::error_code& ec) +// static +LLFILE* LLFile::fopen(const std::string& filename, const char* mode) { #if LL_WINDOWS - LARGE_INTEGER value = { 0 }; - if (GetFileSizeEx(mHandle, &value)) - { - clear_error(ec); - return value.QuadPart; - } + std::wstring utf16filename = utf8path_to_wstring(filename); + std::wstring utf16mode = ll_convert(std::string(mode)); + return _wfopen(utf16filename.c_str(), utf16mode.c_str()); #else - struct stat statval; - if (fstat(mHandle, &statval) == 0) - { - clear_error(ec); - return statval.st_size; - } + return ::fopen(filename.c_str(),mode); #endif - return set_ec_from_system_error(ec); } -S64 LLFile::tell(std::error_code& ec) +// static +int LLFile::close(LLFILE * file) { -#if LL_WINDOWS - LARGE_INTEGER value = { 0 }; - if (SetFilePointerEx(mHandle, value, &value, FILE_CURRENT)) - { - clear_error(ec); - return value.QuadPart; - } -#else - off_t offset = lseek(mHandle, 0, SEEK_CUR); - if (offset != -1) + int ret_value = 0; + if (file) { - clear_error(ec); - return offset; + ret_value = fclose(file); } -#endif - return set_ec_from_system_error(ec); -} - -int LLFile::seek(S64 pos, std::error_code& ec) -{ - return seek(pos, LLFile::beg, ec); + return ret_value; } -int LLFile::seek(S64 offset, std::ios_base::seekdir dir, std::error_code& ec) +// static +std::string LLFile::getContents(const std::string& filename) { - S64 newOffset = 0; -#if LL_WINDOWS - DWORD seekdir = seek_mode_from_dir(dir); - LARGE_INTEGER value; - value.QuadPart = offset; - if (SetFilePointerEx(mHandle, value, (PLARGE_INTEGER)&newOffset, seekdir)) -#else - newOffset = lseek(mHandle, offset, seek_mode_from_dir(dir)); - if (newOffset != -1) -#endif + LLFILE* fp = LLFile::fopen(filename, "rb"); + if (fp) { - return clear_error(ec); - } - return set_ec_from_system_error(ec); -} + fseek(fp, 0, SEEK_END); + U32 length = ftell(fp); + fseek(fp, 0, SEEK_SET); -#if LL_WINDOWS -inline DWORD next_buffer_size(S64 nbytes) -{ - return nbytes > 0x80000000 ? 0x80000000 : (DWORD)nbytes; -} -#endif + std::vector buffer(length); + size_t nread = fread(buffer.data(), 1, length, fp); + fclose(fp); -S64 LLFile::read(void* buffer, S64 nbytes, std::error_code& ec) -{ - if (nbytes == 0) - { - // Nothing to do - return clear_error(ec); + return std::string(buffer.data(), nread); } - else if (!buffer || nbytes < 0) - { - return set_ec_to_parameter_error(ec); - } -#if LL_WINDOWS - S64 totalBytes = 0; - char *ptr = (char*)buffer; - DWORD bytesRead, bytesToRead = next_buffer_size(nbytes); - // Read in chunks to support >4GB which the S64 nbytes value makes possible - while (ReadFile(mHandle, ptr, bytesToRead, &bytesRead, nullptr)) - { - totalBytes += bytesRead; - if (nbytes <= totalBytes || // requested amount read - bytesRead < bytesToRead) // ReadFile encountered eof - { - clear_error(ec); - return totalBytes; - } - ptr += bytesRead; - bytesToRead = next_buffer_size(nbytes - totalBytes); - } -#else - ssize_t bytesRead = ::read(mHandle, buffer, nbytes); - if (bytesRead != -1) - { - clear_error(ec); - return bytesRead; - } -#endif - return set_ec_from_system_error(ec); + return LLStringUtil::null; } -S64 LLFile::write(const void* buffer, S64 nbytes, std::error_code& ec) +// static +int LLFile::remove(const std::string& filename, int suppress_error) { - if (nbytes == 0) - { - // Nothing to do here - return clear_error(ec); - } - else if (!buffer || nbytes < 0) - { - return set_ec_to_parameter_error(ec); - } #if LL_WINDOWS - // If this was opened in append mode, we emulate it on Windows - if (mOpen & LLFile::app && seek(0, LLFile::end, ec) != 0) + // Posix remove() works on both files and directories although on Windows + // remove() and its wide char variant _wremove() only removes files just + // 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)) { - return -1; + rc = _wrmdir(utf16filename.c_str()); } - - S64 totalBytes = 0; - char* ptr = (char*)buffer; - DWORD bytesWritten, bytesToWrite = next_buffer_size(nbytes); - - // Write in chunks to support >4GB which the S64 nbytes value makes possible - while (WriteFile(mHandle, ptr, bytesToWrite, &bytesWritten, nullptr)) + else if (S_ISREG(st_mode)) { - totalBytes += bytesWritten; - if (nbytes <= totalBytes) - { - clear_error(ec); - return totalBytes; - } - ptr += bytesWritten; - bytesToWrite = next_buffer_size(nbytes - totalBytes); + rc = _wunlink(utf16filename.c_str()); } -#else - ssize_t bytesWritten = ::write(mHandle, buffer, nbytes); - if (bytesWritten != -1) + else if (st_mode) { - clear_error(ec); - return bytesWritten; - } -#endif - return set_ec_from_system_error(ec); -} - -S64 LLFile::printf(const char* fmt, ...) -{ - va_list args1; - va_start(args1, fmt); - va_list args2; - va_copy(args2, args1); - int length = vsnprintf(nullptr, 0, fmt, args1); - va_end(args1); - if (length < 0) - { - va_end(args2); - return -1; - } - void* buffer = malloc(length + 1); - if (!buffer) - { - va_end(args2); - return -1; - } - length = vsnprintf((char*)buffer, length + 1, fmt, args2); - va_end(args2); - std::error_code ec; - S64 written = write(buffer, length, ec); - free(buffer); - return written; -} - -int LLFile::lock(int mode, std::error_code& ec) -{ -#if LL_WINDOWS - if (!(mode & LLFile::lock_mask)) - { - if (UnlockFile(mHandle, 0, 0, MAXDWORD, MAXDWORD)) - { - return clear_error(ec); - } + // 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 { - OVERLAPPED overlapped = { 0 }; - DWORD flags = (mode & LLFile::noblock) ? LOCKFILE_FAIL_IMMEDIATELY : 0; - if (mode & LLFile::exclusive) - { - flags |= LOCKFILE_EXCLUSIVE_LOCK; - } - // We lock the maximum range, since flock only supports locking the entire file too - if (LockFileEx(mHandle, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) - { - return clear_error(ec); - } - } -#else - if (flock(mHandle, decode_lock_mode(mode)) == 0) - { - return clear_error(ec); + // get_fileattr() failed and already set errno, preserve it for correct error reporting } -#endif - return set_ec_from_system_error(ec); -} - -int LLFile::close(std::error_code& ec) -{ - if (mHandle != InvalidHandle) - { - llfile_handle_t handle = InvalidHandle; - std::swap(handle, mHandle); -#if LL_WINDOWS - if (!CloseHandle(handle)) #else - if (::close(handle)) + int rc = ::remove(filename.c_str()); #endif - { - return set_ec_from_system_error(ec); - } - } - return clear_error(ec); -} - -int LLFile::close() -{ - std::error_code ec; - return close(ec); + return warnif("remove", filename, rc, suppress_error); } -//---------------------------------------------------------------------------------------- -// static member functions -//---------------------------------------------------------------------------------------- - // static -LLFILE* LLFile::fopen(const std::string& filename, const char* mode, int lmode) +int LLFile::rename(const std::string& filename, const std::string& newname, int suppress_error) { - LLFILE* file; #if LL_WINDOWS - int shflag = _SH_DENYNO; - switch (lmode) + // Posix rename() will gladly overwrite a file at newname if it exists, the Windows + // rename(), respectively _wrename(), will bark on that. Instead call directly the Windows + // 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)) { - case LLFile::exclusive: - shflag = _SH_DENYRW; - break; - case LLFile::shared: - shflag = _SH_DENYWR; - break; + rc = set_errno_from_oserror(GetLastError()); } - std::wstring file_path = utf8StringToWstring(filename); - std::wstring utf16mode = ll_convert(std::string(mode)); - file = _wfsopen(file_path.c_str(), utf16mode.c_str(), shflag); #else - file = ::fopen(filename.c_str(), mode); - if (file && (lmode & (LLFile::lock_mask))) - { - // Rather fail on a sharing conflict than block - if (flock(fileno(file), decode_lock_mode(lmode | LLFile::noblock))) - { - ::fclose(file); - file = nullptr; - } - } + int rc = ::rename(filename.c_str(),newname.c_str()); #endif - return file; + return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, suppress_error); } -// static -int LLFile::close(LLFILE* file) -{ - int ret_value = 0; - if (file) - { - ret_value = ::fclose(file); - } - return ret_value; -} - -// static -std::string LLFile::getContents(const std::string& filename) -{ - std::error_code ec; - return getContents(filename, ec); -} +// Make this a define rather than using magic numbers multiple times in the code +#define LLFILE_COPY_BUFFER_SIZE 16384 // static -std::string LLFile::getContents(const std::string& filename, std::error_code& ec) +bool LLFile::copy(const std::string& from, const std::string& to) { - std::string buffer; - LLFile file(filename, LLFile::in | LLFile::binary, ec); - if (file) + bool copied = false; + LLFILE* in = LLFile::fopen(from, "rb"); + if (in) { - S64 length = file.size(ec); - if (!ec && length > 0) + LLFILE* out = LLFile::fopen(to, "wb"); + if (out) { - buffer = std::string(length, 0); - file.read(&buffer[0], length, ec); - if (ec) + char buf[LLFILE_COPY_BUFFER_SIZE]; + size_t readbytes; + bool write_ok = true; + while (write_ok && (readbytes = fread(buf, 1, LLFILE_COPY_BUFFER_SIZE, in))) { - buffer.clear(); - } - } - } - return buffer; -} - -// static -int LLFile::mkdir(const std::string& dirname) -{ - std::error_code ec; - std::filesystem::path file_path = utf8StringToPath(dirname); - // We often use mkdir() to ensure the existence of a directory that might - // already exist. There is no known case in which we want to call out as - // an error the requested directory already existing. - std::filesystem::create_directory(file_path, ec); - // The return value is only true if the directory was actually created. - // But if it already existed, ec still indicates success. - return warnif("mkdir", dirname, ec); -} - -// static -int LLFile::remove(const std::string& filename, int suppress_warning) -{ - std::error_code ec; - std::filesystem::path file_path = utf8StringToPath(filename); - std::filesystem::remove(file_path, ec); - return warnif("remove", filename, ec, suppress_warning); -} - -// static -int LLFile::rename(const std::string& filename, const std::string& newname, int suppress_warning) -{ - std::error_code ec; - std::filesystem::path file_path = utf8StringToPath(filename); - std::filesystem::path new_path = utf8StringToPath(newname); - std::filesystem::rename(file_path, new_path, ec); - return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, ec, suppress_warning); -} - -// static -S64 LLFile::read(const std::string& filename, void* buf, S64 offset, S64 nbytes) -{ - std::error_code ec; - return read(filename, buf, offset, nbytes, ec); -} - -// static -S64 LLFile::read(const std::string& filename, void* buf, S64 offset, S64 nbytes, std::error_code& ec) -{ - // if number of bytes is 0 or less there is nothing to do here - if (nbytes <= 0) - { - clear_error(ec); - return 0; - } - - if (!buf || offset < 0) - { - set_ec_to_parameter_error(ec); - } - else - { - std::ios_base::openmode omode = LLFile::in | LLFile::binary; - - LLFile file(filename, omode, ec); - if (!ec && (bool)file) - { - if (offset > 0) - { - file.seek(offset, ec); - } - // else (offset == 0) file was just opened and should already be at 0. - if (!ec) - { - S64 bytes_read = file.read(buf, nbytes, ec); - if (!ec) + if (fwrite(buf, 1, readbytes, out) != readbytes) { - return bytes_read; + LL_WARNS("LLFile") << "Short write" << LL_ENDL; + write_ok = false; } } - } - } - return warnif("read from file failed", filename, ec); -} - -// static -S64 LLFile::write(const std::string& filename, const void* buf, S64 offset, S64 nbytes) -{ - std::error_code ec; - return write(filename, buf, offset, nbytes, ec); -} - -// static -S64 LLFile::write(const std::string& filename, const void* buf, S64 offset, S64 nbytes, std::error_code& ec) -{ - // if number of bytes is 0 or less there is nothing to do here - if (nbytes <= 0) - { - clear_error(ec); - return 0; - } - - if (!buf) - { - set_ec_to_parameter_error(ec); - } - else - { - std::ios_base::openmode omode = LLFile::out | LLFile::binary; - if (offset < 0) - { - omode |= LLFile::app; - } - - LLFile file(filename, omode, ec); - if (!ec && (bool)file) - { - if (offset > 0) + if ( write_ok ) { - file.seek(offset, ec); - } - // else (offset == 0) we are not appending, file was just opened and should already be at 0. - if (!ec) - { - S64 bytes_written = file.write(buf, nbytes, ec); - if (!ec) - { - return bytes_written; - } + copied = true; } + fclose(out); } - } - return warnif("write to file failed", filename, ec); -} - -// static -bool LLFile::copy(const std::string& source, const std::string& target) -{ - std::error_code ec; - return copy(source, target, std::filesystem::copy_options::overwrite_existing, ec); -} - -// static -bool LLFile::copy(const std::string& source, const std::string& target, std::filesystem::copy_options options, std::error_code& ec) -{ - std::filesystem::path source_path = utf8StringToPath(source); - std::filesystem::path target_path = utf8StringToPath(target); - bool copied = std::filesystem::copy_file(source_path, target_path, options, ec); - if (!copied) - { - warnif(STRINGIZE("copy failed, to '" << target << "' from"), source, ec); + fclose(in); } return copied; } // static -int LLFile::stat(const std::string& filename, llstat* filestatus, const char *fname, int suppress_warning) +int LLFile::stat(const std::string& filename, llstat* filestatus, int suppress_error) { #if LL_WINDOWS - std::wstring file_path = utf8StringToWstring(filename); - int rc = _wstat64(file_path.c_str(), filestatus); + std::wstring utf16filename = utf8path_to_wstring(filename); + int rc = _wstat64(utf16filename.c_str(), filestatus); #else int rc = ::stat(filename.c_str(), filestatus); #endif - return warnif(fname ? fname : "stat", filename, rc, suppress_warning); + return warnif("stat", filename, rc, suppress_error); } // static -std::time_t LLFile::getCreationTime(const std::string& filename, int suppress_warning) +unsigned short LLFile::getattr(const std::string& filename, bool dontFollowSymLink, int suppress_error) { - // As of C++20 there is no functionality in std::filesystem to retrieve this information - llstat filestat; - int rc = stat(filename, &filestat, "getCreationTime", suppress_warning); - if (rc == 0) +#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) { -#if LL_DARWIN - return filestat.st_birthtime; -#else - // Linux stat() doesn't have a creation/birth time (st_ctime really is the last status - // change or inode attributes change) unless we would use statx() instead. But that is - // a major effort, which would require Linux specific changes to LLFile::stat() above - // and possibly adaptions for other platforms that we leave for a later exercise if it - // is ever desired. - return filestat.st_ctime; -#endif + return st_mode; } - return 0; -} - -// static -std::time_t LLFile::getModificationTime(const std::string& filename, int suppress_warning) -{ - // tried to use std::filesystem::last_write_time() but the whole std::chrono infrastructure is as of - // C++20 still not fully implemented on all platforms. Specifically MacOS C++20 seems lacking here, - // and Windows requires a roundabout through std::chrono::utc_clock to then get a - // std::chrono::system_clock that can return a more useful time_t. - // So we take the easy way out in a similar way as with getCreationTime(). - llstat filestat; - int rc = stat(filename, &filestat, "getModificationTime", suppress_warning); +#else + llstat filestatus; + int rc = dontFollowSymLink ? ::lstat(filename.c_str(), &filestatus) : ::stat(filename.c_str(), &filestatus); if (rc == 0) { - return filestat.st_mtime; + return filestatus.st_mode; } +#endif + warnif("getattr", filename, rc, suppress_error); return 0; } -// static -S64 LLFile::size(const std::string& filename, int suppress_warning) -{ - std::error_code ec; - std::filesystem::path file_path = utf8StringToPath(filename); - std::intmax_t size = static_cast(std::filesystem::file_size(file_path, ec)); - if (ec) - { - return warnif("size", filename, ec, suppress_warning); - } - return size; -} - -// static -std::filesystem::file_status LLFile::getStatus(const std::string& filename, bool dontFollowSymLink, int suppress_warning) -{ - std::error_code ec; - std::filesystem::path file_path = utf8StringToPath(filename); - std::filesystem::file_status status; - if (dontFollowSymLink) - { - status = std::filesystem::symlink_status(file_path, ec); - } - else - { - status = std::filesystem::status(file_path, ec); - } - warnif("getStatus()", filename, ec, suppress_warning); - return status; -} - -// static -bool LLFile::exists(const std::string& filename) -{ - std::filesystem::file_status status = getStatus(filename); - return std::filesystem::exists(status); -} - // static bool LLFile::isdir(const std::string& filename) { - std::filesystem::file_status status = getStatus(filename); - return std::filesystem::is_directory(status); + return S_ISDIR(getattr(filename)); } // static bool LLFile::isfile(const std::string& filename) { - std::filesystem::file_status status = getStatus(filename); - return std::filesystem::is_regular_file(status); + return S_ISREG(getattr(filename)); } // static bool LLFile::islink(const std::string& filename) { - std::filesystem::file_status status = getStatus(filename, true); - return std::filesystem::is_symlink(status); -} - -// static -const std::string& LLFile::tmpdir() -{ - static std::string temppath; - if (temppath.empty()) - { - temppath = std::filesystem::temp_directory_path().string(); - } - return temppath; + return S_ISLNK(getattr(filename, true)); } // static -std::filesystem::path LLFile::utf8StringToPath(const std::string& pathname) +const char *LLFile::tmpdir() { -#if LL_WINDOWS - return ll_convert(pathname); -#else - return pathname; -#endif -} + static std::string utf8path; -#if LL_WINDOWS - -// static -std::wstring LLFile::utf8StringToWstring(const std::string& pathname) -{ - std::wstring utf16string(ll_convert(pathname)); - if (utf16string.size() >= MAX_PATH) + if (utf8path.empty()) { - // By going through std::filesystem::path we get a lot of path sanitation done for us that - // is needed when passing a path with a kernel object space prefix to Windows API functions - // since this prefix disables the kernel32 path normalization - std::filesystem::path utf16path(utf16string); + char sep; +#if LL_WINDOWS + sep = '\\'; - // By prepending "\\?\" to a path, Windows widechar file APIs will not fail on long path names - utf16string.assign(L"\\\\?\\").append(utf16path); + std::vector utf16path(MAX_PATH + 1); + GetTempPathW(static_cast(utf16path.size()), &utf16path[0]); + utf8path = ll_convert_wide_to_string(&utf16path[0]); +#else + sep = '/'; - /* remove trailing spaces and dots (yes, Windows really does that) */ - size_t last_valid = utf16string.find_last_not_of(L" \t."); - if (last_valid == std::wstring::npos) + utf8path = LLStringUtil::getenv("TMPDIR", "/tmp/"); +#endif + if (utf8path[utf8path.size() - 1] != sep) { - return std::wstring(); + utf8path += sep; } - return utf16string.substr(0, last_valid + 1); } - return utf16string; + return utf8path.c_str(); } +#if LL_WINDOWS + /************** input file stream ********************************/ llifstream::llifstream() {} @@ -1176,8 +586,10 @@ void llifstream::open(const std::string& _Filename, ios_base::openmode _Mode) _Mode | ios_base::in); } + /************** output file stream ********************************/ + llofstream::llofstream() {} // explicit @@ -1193,4 +605,30 @@ void llofstream::open(const std::string& _Filename, ios_base::openmode _Mode) _Mode | ios_base::out); } +/************** helper functions ********************************/ + +std::streamsize llifstream_size(llifstream& ifstr) +{ + if(!ifstr.is_open()) return 0; + std::streampos pos_old = ifstr.tellg(); + ifstr.seekg(0, ios_base::beg); + std::streampos pos_beg = ifstr.tellg(); + ifstr.seekg(0, ios_base::end); + std::streampos pos_end = ifstr.tellg(); + ifstr.seekg(pos_old, ios_base::beg); + return pos_end - pos_beg; +} + +std::streamsize llofstream_size(llofstream& ofstr) +{ + if(!ofstr.is_open()) return 0; + std::streampos pos_old = ofstr.tellp(); + ofstr.seekp(0, ios_base::beg); + std::streampos pos_beg = ofstr.tellp(); + ofstr.seekp(0, ios_base::end); + std::streampos pos_end = ofstr.tellp(); + ofstr.seekp(pos_old, ios_base::beg); + return pos_end - pos_beg; +} + #endif // LL_WINDOWS diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h old mode 100755 new mode 100644 index 2c74c97d0b..04a2946ac4 --- a/indra/llcommon/llfile.h +++ b/indra/llcommon/llfile.h @@ -35,359 +35,118 @@ * Attempts to mostly mirror the POSIX style IO functions. */ +typedef FILE LLFILE; + #include -#include #include #if LL_WINDOWS -#include // The Windows version of stat function and stat data structure are called _stat64 // We use _stat64 here to support 64-bit st_size and time_t values -typedef struct _stat64 llstat; +typedef struct _stat64 llstat; #else +typedef struct stat llstat; #include -typedef struct stat llstat; #endif -typedef FILE LLFILE; +#ifndef S_ISREG +# define S_ISREG(x) (((x) & S_IFMT) == S_IFREG) +#endif + +#ifndef S_ISDIR +# define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR) +#endif + +// Windows C runtime library does not define this and does not support symlink detection in the +// stat functions but we do in our getattr() function +#ifndef S_IFLNK +#define S_IFLNK 0xA000 /* symlink */ +#endif + +#ifndef S_ISLNK +#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK) +#endif #include "llstring.h" // safe char* -> std::string conversion -/// This class provides a selection of functions to operate on files through names and -/// a class implementation to represent a file for reading and writing to it +/// LLFile is a class of static functions operating on paths /// All the functions with a path string input take UTF8 path/filenames -/// -/// @nosubgrouping -/// class LL_COMMON_API LLFile { public: - // ================================================================================ - /// @name Constants - /// - ///@{ - /** These can be passed to the omode parameter of LLFile::open() and its constructor - - This is similar to the openmode flags for std:fstream but not exactly the same - std::fstream open() does not allow to open a file for writing without either - forcing the file to be truncated on open or all write operations being always - appended to the end of the file or failing to open when the file does not exist. - But to allow implementing the LLAPRFile::writeEx() functionality we need to be - able to write at a random position to the file without truncating it on open. - - any other combinations than listed here are not allowed and will cause an error - - bin in out trunc app noreplace File exists File doesn't exist - ---------------------------------------------------------------------------------- - - + - - - - Open at begin Failure to open - + + - - - - " " - - - + - - - " Create new - + - + - - - " " - - + + - - - " " - + + + - - - " " - ---------------------------------------------------------------------------------- - - - + + - - Destroy contents Create new - + - + + - - " " - - + + + - - " " - + + + + - - " " - ---------------------------------------------------------------------------------- - - - + x - + Failure to open Create new - + - + x - + " " - - + + x - + " " - + + + x - + " " - ---------------------------------------------------------------------------------- - - - + - + - Write to end Create new - + - + - + - " " - - + + - + - " " - + + + - + - " " - ---------------------------------------------------------------------------------- - */ - static const std::ios_base::openmode app = 1 << 1; // append to end - static const std::ios_base::openmode ate = 1 << 2; // initialize to end - static const std::ios_base::openmode binary = 1 << 3; // binary mode - static const std::ios_base::openmode in = 1 << 4; // for reading - static const std::ios_base::openmode out = 1 << 5; // for writing - static const std::ios_base::openmode trunc = 1 << 6; // truncate on open - static const std::ios_base::openmode noreplace = 1 << 7; // no replace if it exists - - /// Additional optional flags to omode in open() and lmode in fopen() or lock() - /// to indicate which sort of lock if any to attempt to get - /// - /// NOTE: there is a fundamental difference between platforms. - /// On Windows this lock is mandatory as it is part of the API to open a file handle and other - /// processes can not avoid it. If a file was opened denying other processes read and/or write - /// access, trying to open the same file in another process with that access will fail. - /// On Mac and Linux it is only an advisory lock implemented through the flock() system call. - /// This means that any other application needs to also attempt to at least acquire a shared - /// lock on the file in order to notice that the file is actually already locked. It can - /// therefore not be used to prevent random other applications from accessing the file, but it - /// works for other viewer processes when they use either the LLFile::open() or LLFile::fopen() - /// functions with the appropriate lock flags to open a file. - static const std::ios_base::openmode exclusive = 1 << 16; - static const std::ios_base::openmode shared = 1 << 17; - - /// Additional lmode flag to indicate to rather fail instead of blocking when trying - /// to acquire a lock with LLFile::lock() - static const std::ios_base::openmode noblock = 1 << 18; - - /// The mask value for the lock mask bits - static const std::ios_base::openmode lock_mask = exclusive | shared; - - /// One of these can be passed to the dir parameter of LLFile::seek() - static const std::ios_base::seekdir beg = std::ios_base::beg; - static const std::ios_base::seekdir cur = std::ios_base::cur; - static const std::ios_base::seekdir end = std::ios_base::end; - ///@} - - // ================================================================================ - /// @name constructor/deconstructor - /// - ///@{ - /// default constructor - LLFile() : mHandle(InvalidHandle) {} - - /// no copy constructor - LLFile(const LLFile&) = delete; - - /// move constructor - LLFile(LLFile&& other) noexcept - { - mHandle = other.mHandle; - other.mHandle = InvalidHandle; - } - - /// constructor opening the file - LLFile(const std::string& filename, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666) : - mHandle(InvalidHandle) - { - open(filename, omode, ec, perm); - } - - /// destructor always attempts to close the file - ~LLFile() { close(); } - ///@} - - // ================================================================================ - /// @name operators - /// - ///@{ - /// copy assignment deleted - LLFile& operator=(const LLFile&) = delete; - - /// move assignment - LLFile& operator=(LLFile&& other) noexcept - { - close(); - std::swap(mHandle, other.mHandle); - return *this; - } - - // detect whether the wrapped file descriptor/handle is open or not - explicit operator bool() const { return (mHandle != InvalidHandle); } - bool operator!() { return (mHandle == InvalidHandle); } - ///@} - - /// ================================================================================ - /// @name class member methods - /// - /// These methods provide read and write support as well as additional functionality to query the size of - /// the file, change the position of the current file pointer or query it. - /// - /// Most of these functions take as one of their parameters a std::error_code object which can be used to - /// determine in more detail what error occurred if required - ///@{ - - /// Open a file with the specific open mode flags - int open(const std::string& filename, std::ios_base::openmode omode, std::error_code& ec, int perm = 0666); - ///< @returns 0 on success, -1 on failure - - /// Determine the size of the opened file - S64 size(std::error_code& ec); - ///< @returns the number of bytes in the file or -1 on failure - - /// Query the position of the current file pointer in the file - S64 tell(std::error_code& ec); - ///< @returns the absolute offset of the file pointer in bytes relative to the start of the file or -1 on failure - - /// Move the file pointer to the specified absolute position relative to the start of the file - int seek(S64 pos, std::error_code& ec); - ///< @returns 0 on success, -1 on failure - - /// Move the file pointer to the specified position relative to dir - int seek(S64 offset, std::ios_base::seekdir dir, std::error_code& ec); - ///< @returns 0 on success, -1 on failure - - /// Read the specified number of bytes into the buffer starting at the current file pointer - S64 read(void* buffer, S64 nbytes, std::error_code& ec); - ///< If the file ends before the requested amount of bytes could be read, the function succeeds and - /// returns the bytes up to the end of the file. The return value indicates the number of actually - /// read bytes and can be therefore smaller than the requested amount. - /// @returns the number of bytes read from the file or -1 on failure - - /// Write the specified number of bytes to the file starting at the current file pointer - S64 write(const void* buffer, S64 nbytes, std::error_code& ec); - ///< @returns the number of bytes written to the file or -1 on failure - - /// Write into the file starting at the current file pointer using printf style format and - /// additional optional parameters as specified in the fmt string - S64 printf(const char* fmt, ...); - ///< @returns the number of bytes written to the file or -1 on failure - - /// Attempt to acquire or release a lock on the file - int lock(int lmode, std::error_code& ec); - ///< lmode can be one of LLFile::exclusive or LLFile::shared to acquire the according lock - /// or 0 to give up an earlier acquired lock. Adding LLFile::noblock together with one of - /// the lock requests will cause the function to fail if the lock can not be acquired, - /// otherwise the function will block until the lock can be acquired. - /// @returns 0 on success, -1 on failure - - /// close the file explicitly - int close(std::error_code& ec); - ///< @returns 0 on success, -1 on failure - - /// Convenience function to close the file without additional parameters - int close(); - ///< @returns 0 on success, -1 on failure - ///@} - - /// ================================================================================ - /// @name static member functions - /// - /// These functions are static and operate with UTF8 filenames as one of their parameters. - /// - ///@{ /// open a file with the specified access mode - static LLFILE* fopen(const std::string& filename, const char* accessmode, int lmode = 0); + static LLFILE* fopen(const std::string& filename, const char* accessmode); /* Flawfinder: ignore */ ///< '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 - /// "w" open the file for reading and writing and truncate it to zero length - /// "w+" open or create the file for reading and writing and truncate to zero length if it existed - /// "a" open the file for reading and writing and before every write position the stream at the end of the file - /// "a+" open or create the file for reading and writing and before every write position the stream at the end of the file - /// - /// in addition to these values, "b" can be appended to indicate binary stream access, but on Linux and Mac - /// this is strictly for compatibility and has no effect. On Windows this makes the file functions not - /// try to translate line endings. Windows also allows to append "t" to indicate text mode. If neither - /// "b" or "t" is defined, Windows uses the value set by _fmode which by default is _O_TEXT. - /// 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', while possible on some platforms (Windows), will usually - /// cause an error on other platforms as fopen will verify this 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 + /// "w" open the file for reading and writing and truncate it to zero length + /// "w+" open or create the file for reading and writing and truncate to zero length if it existed + /// "a" open the file for reading and writing and position the stream at the end of the file + /// "a+" open or create the file for reading and writing and position the stream at the end of the file /// - /// lmode is optional and allows to lock the file for other processes either as a shared lock or an - /// exclusive lock. If the requested lock conflicts with an already existing lock, the open fails. - /// Pass either LLFIle::exclusive or LLFile::shared to this parameter if you want to prevent other - /// processes from reading (exclusive lock) or writing (shared lock) to the file. It will always use - /// LLFile::noblock, meaning the open will immediately fail if it conflicts with an existing lock on the - /// file. - /// - /// @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 + /// in addition to these values, "b" can be appended to indicate binary stream access, but on Linux and Mac + /// this is strictly for compatibility and has no effect. On Windows this makes the file functions not + /// try to translate line endings. Windows also allows to append "t" to indicate text mode. If neither + /// "b" or "t" is defined, Windows uses the value set by _fmode which by default is _O_TEXT. + /// 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 - /// Close a file handle opened with fopen() above static int close(LLFILE * file); - ///< @returns 0 on success and -1 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 /// create a directory - static int mkdir(const std::string& filename); - ///< mkdir() considers "directory already exists" to be not an error. + 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 + /// overridden by the user's umask. It is ignored on Windows. + /// mkdir() considers "directory already exists" to be not an error. + /// @returns 0 on success and -1 on failure. + + //// remove a directory + static int rmdir(const std::string& filename, int suppress_error = 0); + ///< pass ENOENT in the optional 'suppress_error' parameter + /// if you don't want a warning in the log when the directory does not exist /// @returns 0 on success and -1 on failure. /// remove a file or directory - static int remove(const std::string& filename, int suppress_warning = 0); - ///< pass an errno value (e.g., ENOENT) in the optional 'suppress_warning' parameter if you want to - /// suppress a warning in the log when the failure matches that errno (e.g., suppress warning if - /// the file or directory does not exist) + static int remove(const std::string& filename, int suppress_error = 0); + ///< pass ENOENT in the optional 'suppress_error' parameter + /// if you don't want a warning in the log when the directory does not exist /// @returns 0 on success and -1 on failure. /// rename a file - static int rename(const std::string& filename, const std::string& newname, int suppress_warning = 0); + static int rename(const std::string& filename, const std::string& newname, int suppress_error = 0); ///< it will silently overwrite newname if it exists without returning an error /// Posix guarantees that if newname already exists, then there will be no moment /// in which for other processes newname does not exist. There is no such guarantee - /// under Windows at this time. It may do it in the same way but the used Windows - /// APIs do not make such guarantees. + /// under Windows at this time. It may do it in the same way but the used Windows API + /// does not make such guarantees. /// @returns 0 on success and -1 on failure. - /// copy the contents of the file from 'source' to 'target' - static bool copy(const std::string& source, const std::string& target); - ///< Copies the contents of the file 'source' to the file 'target', overwriting 'target' if it already - /// existed. - /// This is a convenience function that implements the previous behavior of silently overwriting an - /// already existing target file. Consider using the function below if you desire a different - /// behavior when the target file already exists - /// @returns true on success and false on failure. - - /// copy the contents of the file from 'from' to 'to' - static bool copy(const std::string& source, const std::string& target, std::filesystem::copy_options options, std::error_code& ec); - ///< Copies the contents of the file 'source' to the file 'target'. The options parameter allows to - /// specify what should happen if the "target" file already exists: - /// std::filesystem::copy_options::none - return an error in ec and fail - /// std::filesystem::copy_options::skip_existing - skip the operation and do not overwrite file - /// std::filesystem::copy_options::overwrite_existing - overwrite the file - /// std::filesystem::copy_options::update_existing - overwrite the file only if it is older than the file being copied - /// @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); - static std::string getContents(const std::string& filename, std::error_code& ec); - ///< @returns the entire content of the file as std::string or an empty string on failure - - /// read nBytes from the file into the buffer, starting at offset in the file - static S64 read(const std::string& filename, void* buf, S64 offset, S64 nbytes); - static S64 read(const std::string& filename, void* buf, S64 offset, S64 nbytes, std::error_code& ec); - ///< @returns bytes read on success, or -1 on failure - /// write nBytes from the buffer into the file, starting at offset in the file - static S64 write(const std::string& filename, const void* buf, S64 offset, S64 nbytes); - static S64 write(const std::string& filename, const void* buf, S64 offset, S64 nbytes, std::error_code& ec); - ///< If a negative offset is provided, the file is opened in append mode and the - /// write will be appended to the end of the file. - /// @returns bytes written on success, or -1 on failure + /// copy the contents of 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. /// return the file stat structure for filename - static int stat(const std::string& filename, llstat* file_status, const char *operation = nullptr, int suppress_warning = ENOENT); + 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 - /// optional 'suppress_warning' parameter to avoid spamming the log with warnings when the API + /// optional 'suppress_error' parameter to avoid spamming the log with warnings when the API /// is used to detect if a file exists /// @returns 0 on success and -1 on failure. - /// get the creation data and time of a file - static std::time_t getCreationTime(const std::string& filename, int suppress_warning = 0); - ///< Different systems have different support for this. Under Windows this is supposedly - /// the actual time the file was created, on the Mac this is the actual birth date of - /// the file which is in fact the creation time. The according ctime entry in the stat - /// structure under Linux (and any other *nix really) is however contrary to what one - /// might expect based on the c in ctime not the creation time but the time the last - /// change to the inode entry was made. Changing access rights to a file will update - /// this value too. In order to have a true creation time under Linux, we would have - /// to use the statx() call which is available since kernel 4.19, but that will require - /// considerable changes to the implementation of above stat() function. - /// @returns the creation time (last status change under Linux) of the file or 0 on error - - /// get the last modification data and time of a file - static std::time_t getModificationTime(const std::string& filename, int suppress_warning = 0); - ///< @returns the modification time of the file or 0 on error - - /// get the std::filesystem::file_status for filename - static std::filesystem::file_status getStatus(const std::string& filename, bool dontFollowSymLink = false, int suppress_warning = ENOENT); - ///< dontFollowSymLinks set to true returns the std::filesystem::file_status of the symlink if it - /// is one, rather than resolving it. We pass by default ENOENT in the optional 'suppress_warning' - /// parameter to not spam the log with warnings when the file or directory does not exist - /// @returns a std::filesystem::file_status value that can be passed to the appropriate std::filesystem::exists() - /// and other APIs accepting a file_status. - - /// get the size of a file in bytes - static S64 size(const std::string& filename, int suppress_warning = ENOENT); - ///< we pass by default ENOENT in the optional 'suppress_warning' parameter to not spam - /// the log with warnings when the file does not exist - /// @returns the file size on success or -1 on failure. - - /// check if filename is an existing file or directory - static bool exists(const std::string& filename); - ///< @returns true if the path is for an existing file or directory + /// get the file or directory attributes for filename + static unsigned short getattr(const std::string& filename, bool dontFollowSymLink = false, int suppress_error = ENOENT); + ///< a more lightweight function on Windows to stat, that just returns the file attribute flags + /// 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 + /// together with the three access bits which under Windows only the write bit is relevant. /// check if filename is an existing directory static bool isdir(const std::string& filename); @@ -402,44 +161,70 @@ public: ///< @returns true if the path is pointing at a symlink /// return a path to the temporary directory on the system - static const std::string& tmpdir(); + static const char * tmpdir(); +}; - /// converts a string containing a path in utf8 encoding into an explicit filesystem path - static std::filesystem::path utf8StringToPath(const std::string& pathname); - ///< @returns the path as a std::filesystem::path - ///@} +/// RAII class +class LLUniqueFile +{ +public: + // empty + LLUniqueFile(): mFileHandle(nullptr) {} + // wrap (e.g.) result of LLFile::fopen() + LLUniqueFile(LLFILE* f): mFileHandle(f) {} + // no copy + LLUniqueFile(const LLUniqueFile&) = delete; + // move construction + LLUniqueFile(LLUniqueFile&& other) noexcept + { + mFileHandle = other.mFileHandle; + other.mFileHandle = nullptr; + } + // The point of LLUniqueFile is to close on destruction. + ~LLUniqueFile() + { + close(); + } -private: -#if LL_WINDOWS - typedef HANDLE llfile_handle_t; - const llfile_handle_t InvalidHandle = INVALID_HANDLE_VALUE; -#else - typedef int llfile_handle_t; - const llfile_handle_t InvalidHandle = -1; -#endif + // simple assignment + LLUniqueFile& operator=(LLFILE* f) + { + close(); + mFileHandle = f; + return *this; + } + // copy assignment deleted + LLUniqueFile& operator=(const LLUniqueFile&) = delete; + // move assignment + LLUniqueFile& operator=(LLUniqueFile&& other) noexcept + { + close(); + std::swap(mFileHandle, other.mFileHandle); + return *this; + } - /// ================================================================================ - /// @name private static member functions - /// -#if LL_WINDOWS - /// convert a string containing a path in utf8 encoding into a Windows format std::wstring - static std::wstring utf8StringToWstring(const std::string& pathname); - ///< this will prepend the path with the Windows kernel object space prefix when the path is - /// equal or longer than MAX_PATH characters and do some sanitation on the path. - /// This allows the underlaying Windows APIs to process long path names. Do not pass such a path - /// to std::filesystem functions. These functions are not guaranteed to handle such paths properly. - /// It's only useful to pass the resulting string buffer to Microsoft Windows widechar APIs or - /// the Microsoft C runtime widechar file functions. - /// - /// Example: - /// - /// std::wstring file_path = utf8StringToWstring(filename); - /// HANDLE CreateFileW(file_path.c_str(), ......); - /// - /// @returns the path as a std::wstring path -#endif - llfile_handle_t mHandle; // The file handle/descriptor - std::ios_base::openmode mOpen; // Used to emulate std::ios_base::app under Windows + // explicit close operation + void close() + { + if (mFileHandle) + { + // in case close() throws, set mFileHandle null FIRST + LLFILE* h{nullptr}; + std::swap(h, mFileHandle); + LLFile::close(h); + } + } + + // detect whether the wrapped LLFILE is open or not + explicit operator bool() const { return bool(mFileHandle); } + bool operator!() { return ! mFileHandle; } + + // LLUniqueFile should be usable for any operation that accepts LLFILE* + // (or FILE* for that matter) + operator LLFILE*() const { return mFileHandle; } + +private: + LLFILE* mFileHandle; }; #if LL_WINDOWS @@ -530,6 +315,17 @@ class LL_COMMON_API llofstream : public std::ofstream ios_base::openmode _Mode = ios_base::out|ios_base::trunc); }; + +/** + * @brief filesize helpers. + * + * The file size helpers are not considered particularly efficient, + * and should only be used for config files and the like -- not in a + * loop. + */ +std::streamsize LL_COMMON_API llifstream_size(llifstream& fstr); +std::streamsize LL_COMMON_API llofstream_size(llofstream& fstr); + #else // ! LL_WINDOWS // on non-windows, llifstream and llofstream are just mapped directly to the std:: equivalents diff --git a/indra/llcrashlogger/llcrashlock.cpp b/indra/llcrashlogger/llcrashlock.cpp old mode 100755 new mode 100644 index 731b53b7e9..bc34f6798f --- a/indra/llcrashlogger/llcrashlock.cpp +++ b/indra/llcrashlogger/llcrashlock.cpp @@ -188,7 +188,12 @@ LLSD LLCrashLock::getProcessList() //static bool LLCrashLock::fileExists(std::string filename) { - return LLFile::exists(filename); +#ifdef LL_WINDOWS // or BOOST_WINDOWS_API + boost::filesystem::path file_path(ll_convert(filename)); +#else + boost::filesystem::path file_path(filename); +#endif + return boost::filesystem::exists(file_path); } void LLCrashLock::cleanupProcess(std::string proc_dir) diff --git a/indra/llfilesystem/lldir.cpp b/indra/llfilesystem/lldir.cpp old mode 100755 new mode 100644 index eb3c2d9909..190539cea5 --- a/indra/llfilesystem/lldir.cpp +++ b/indra/llfilesystem/lldir.cpp @@ -30,6 +30,8 @@ #include #include #include +#else +#include #endif #include "lldir.h" @@ -41,6 +43,7 @@ #include "lldiriterator.h" #include "stringize.h" #include "llstring.h" +#include #include #include @@ -90,20 +93,30 @@ LLDir::~LLDir() std::vector LLDir::getFilesInDir(const std::string &dirname) { - // Returns a vector of filenames in the directory. - std::filesystem::path p = LLFile::utf8StringToPath(dirname); + //Returns a vector of fullpath filenames. + +#ifdef LL_WINDOWS // or BOOST_WINDOWS_API + boost::filesystem::path p(ll_convert(dirname)); +#else + boost::filesystem::path p(dirname); +#endif + std::vector v; - std::error_code ec; - if (std::filesystem::is_directory(p, ec)) + + boost::system::error_code ec; + if (exists(p, ec) && !ec.failed()) { - std::filesystem::directory_iterator end_iter; - for (std::filesystem::directory_iterator dir_itr(p); - dir_itr != end_iter; - ++dir_itr) + if (is_directory(p, ec) && !ec.failed()) { - if (std::filesystem::is_regular_file(dir_itr->status())) + boost::filesystem::directory_iterator end_iter; + for (boost::filesystem::directory_iterator dir_itr(p); + dir_itr != end_iter; + ++dir_itr) { - v.push_back(dir_itr->path().filename().string()); + if (boost::filesystem::is_regular_file(dir_itr->status())) + { + v.push_back(dir_itr->path().filename().string()); + } } } } @@ -173,23 +186,28 @@ U32 LLDir::deleteDirAndContents(const std::string& dir_name) //Removes the directory and its contents. Returns number of files deleted. U32 num_deleted = 0; - std::filesystem::path dir_path = LLFile::utf8StringToPath(dir_name); try { - if (std::filesystem::is_directory(dir_path)) +#ifdef LL_WINDOWS // or BOOST_WINDOWS_API + boost::filesystem::path dir_path(ll_convert(dir_name)); +#else + boost::filesystem::path dir_path(dir_name); +#endif + + if (boost::filesystem::exists(dir_path)) { - if (!std::filesystem::is_empty(dir_path)) + if (!boost::filesystem::is_empty(dir_path)) { // Directory has content - num_deleted = (U32)std::filesystem::remove_all(dir_path); + num_deleted = (U32)boost::filesystem::remove_all(dir_path); } else { // Directory is empty - std::filesystem::remove(dir_path); + boost::filesystem::remove(dir_path); } } } - catch (std::filesystem::filesystem_error &er) + catch (boost::filesystem::filesystem_error &er) { LL_WARNS() << "Failed to delete " << dir_name << " with error " << er.code().message() << LL_ENDL; } @@ -1087,15 +1105,15 @@ void dir_exists_or_crash(const std::string &dir_name) #if LL_WINDOWS // *FIX: lame - it doesn't do the same thing on windows. not so // important since we don't deploy simulator to windows boxes. - LLFile::mkdir(dir_name); + LLFile::mkdir(dir_name, 0700); #else - llstat dir_stat; + struct stat dir_stat; if(0 != LLFile::stat(dir_name, &dir_stat)) { S32 stat_rv = errno; if(ENOENT == stat_rv) { - if(0 != LLFile::mkdir(dir_name)) + if(0 != LLFile::mkdir(dir_name, 0700)) // octal { LL_ERRS() << "Unable to create directory: " << dir_name << LL_ENDL; } diff --git a/indra/llfilesystem/lldir_win32.cpp b/indra/llfilesystem/lldir_win32.cpp old mode 100755 new mode 100644 index 2b478e5dce..58c080c982 --- a/indra/llfilesystem/lldir_win32.cpp +++ b/indra/llfilesystem/lldir_win32.cpp @@ -376,7 +376,18 @@ std::string LLDir_Win32::getCurPath() bool LLDir_Win32::fileExists(const std::string &filename) const { - return LLFile::exists(filename); + llstat stat_data; + // Check the age of the file + // Now, we see if the files we've gathered are recent... + int res = LLFile::stat(filename, &stat_data); + if (!res) + { + return true; + } + else + { + return false; + } } diff --git a/indra/llfilesystem/llfilesystem.cpp b/indra/llfilesystem/llfilesystem.cpp old mode 100755 new mode 100644 index 0c220fe7cf..541266af4f --- a/indra/llfilesystem/llfilesystem.cpp +++ b/indra/llfilesystem/llfilesystem.cpp @@ -77,17 +77,21 @@ bool LLFileSystem::getExists(const LLUUID& file_id, const LLAssetType::EType fil LL_PROFILE_ZONE_SCOPED; const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type); - // not only test for existence but for the file to be not empty - S64 size = LLFile::size(filename); - return size > 0; + llifstream file(filename, std::ios::binary); + if (file.is_open()) + { + file.seekg(0, std::ios::end); + return file.tellg() > 0; + } + return false; } // static -bool LLFileSystem::removeFile(const LLUUID& file_id, const LLAssetType::EType file_type, int suppress_warning /*= 0*/) +bool LLFileSystem::removeFile(const LLUUID& file_id, const LLAssetType::EType file_type, int suppress_error /*= 0*/) { const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type); - LLFile::remove(filename.c_str(), suppress_warning); + LLFile::remove(filename.c_str(), suppress_error); return true; } @@ -112,10 +116,19 @@ bool LLFileSystem::renameFile(const LLUUID& old_file_id, const LLAssetType::ETyp } // static -S64 LLFileSystem::getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type) +S32 LLFileSystem::getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type) { const std::string filename = LLDiskCache::metaDataToFilepath(file_id, file_type); - return LLFile::size(filename); + + S32 file_size = 0; + llifstream file(filename, std::ios::binary); + if (file.is_open()) + { + file.seekg(0, std::ios::end); + file_size = (S32)file.tellg(); + } + + return file_size; } bool LLFileSystem::read(U8* buffer, S32 bytes) @@ -256,7 +269,7 @@ S32 LLFileSystem::tell() const S32 LLFileSystem::getSize() const { - return (S32)LLFileSystem::getFileSize(mFileID, mFileType); + return LLFileSystem::getFileSize(mFileID, mFileType); } S32 LLFileSystem::getMaxSize() const diff --git a/indra/llfilesystem/llfilesystem.h b/indra/llfilesystem/llfilesystem.h index 7188683e7f..10649b6920 100644 --- a/indra/llfilesystem/llfilesystem.h +++ b/indra/llfilesystem/llfilesystem.h @@ -61,10 +61,10 @@ class LLFileSystem void updateFileAccessTime(const std::string& file_path); static bool getExists(const LLUUID& file_id, const LLAssetType::EType file_type); - static bool removeFile(const LLUUID& file_id, const LLAssetType::EType file_type, int suppress_warning = 0); + static bool removeFile(const LLUUID& file_id, const LLAssetType::EType file_type, int suppress_error = 0); 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 S64 getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type); + static S32 getFileSize(const LLUUID& file_id, const LLAssetType::EType file_type); public: static const S32 READ; diff --git a/indra/llfilesystem/tests/lldir_test.cpp b/indra/llfilesystem/tests/lldir_test.cpp old mode 100755 new mode 100644 index 17e4ffecf1..13db6fad80 --- a/indra/llfilesystem/tests/lldir_test.cpp +++ b/indra/llfilesystem/tests/lldir_test.cpp @@ -558,8 +558,8 @@ namespace tut LLFile::remove(dir1files[i]); LLFile::remove(dir2files[i]); } - LLFile::remove(dir1); - LLFile::remove(dir2); + LLFile::rmdir(dir1); + LLFile::rmdir(dir2); } template<> template<> diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index 34eeacb273..10fd56a68e 100644 --- a/indra/llmessage/llassetstorage.cpp +++ b/indra/llmessage/llassetstorage.cpp @@ -467,7 +467,7 @@ bool LLAssetStorage::findInCacheAndInvokeCallback(const LLUUID& uuid, LLAssetTyp else { LL_WARNS("AssetStorage") << "Asset vfile " << uuid << ":" << type - << " found in static cache with bad size " << size << ", ignoring" << LL_ENDL; + << " found in static cache with bad size " << file.getSize() << ", ignoring" << LL_ENDL; } } return false; diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp old mode 100755 new mode 100644 index 0522307661..2c35a6acae --- a/indra/llrender/llshadermgr.cpp +++ b/indra/llrender/llshadermgr.cpp @@ -1014,6 +1014,7 @@ void LLShaderMgr::initShaderCache(bool enabled, const LLUUID& old_cache_version, mShaderCacheDir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "shader_cache"); LLFile::mkdir(mShaderCacheDir); + { std::string meta_out_path = gDirUtilp->add(mShaderCacheDir, "shaderdata.llsd"); if (gDirUtilp->fileExists(meta_out_path)) @@ -1058,7 +1059,7 @@ void LLShaderMgr::clearShaderCache() LL_INFOS("ShaderMgr") << "Removing shader cache at " << shader_cache << LL_ENDL; const std::string mask = "*"; gDirUtilp->deleteFilesInDir(shader_cache, mask); - LLFile::remove(shader_cache); + LLFile::rmdir(shader_cache); mShaderBinaryCache.clear(); } @@ -1077,7 +1078,7 @@ void LLShaderMgr::persistShaderCacheMetadata() // Settings and shader cache get saved at different time, thus making // RenderShaderCacheVersion unreliable when running multiple viewer // instances, or for cases where viewer crashes before saving settings. - // Duplicate version to the cache itself. + // Dupplicate version to the cache itself. out["version"] = mShaderCacheVersion; out["shaders"] = LLSD::emptyMap(); LLSD &shaders = out["shaders"]; @@ -1130,11 +1131,11 @@ bool LLShaderMgr::loadCachedProgramBinary(LLGLSLShader* shader) { std::vector in_data; in_data.resize(shader_info.mBinaryLength); - std::error_code ec; - LLFile filep = LLFile(in_path, LLFile::in | LLFile::binary, ec); - if (!ec && (bool)filep) + + LLUniqueFile filep = LLFile::fopen(in_path, "rb"); + if (filep) { - size_t result = filep.read(in_data.data(), in_data.size(), ec); + size_t result = fread(in_data.data(), sizeof(U8), in_data.size(), filep); filep.close(); if (result == in_data.size()) @@ -1179,12 +1180,11 @@ bool LLShaderMgr::saveCachedProgramBinary(LLGLSLShader* shader) if (error == GL_NO_ERROR) { std::string out_path = gDirUtilp->add(mShaderCacheDir, shader->mShaderHash.asString() + ".shaderbin"); - std::error_code ec; - LLFile filep = LLFile(out_path, LLFile::out | LLFile::binary, ec); - if (filep) + LLUniqueFile outfile = LLFile::fopen(out_path, "wb"); + if (outfile) { - filep.write(program_binary.data(), program_binary.size(), ec); - filep.close(); + fwrite(program_binary.data(), sizeof(U8), program_binary.size(), outfile); + outfile.close(); binary_info.mLastUsedTime = (F32)LLTimer::getTotalSeconds(); diff --git a/indra/llxml/tests/llcontrol_test.cpp b/indra/llxml/tests/llcontrol_test.cpp old mode 100755 new mode 100644 index e15117fc29..4192e029c5 --- a/indra/llxml/tests/llcontrol_test.cpp +++ b/indra/llxml/tests/llcontrol_test.cpp @@ -69,7 +69,7 @@ namespace tut LLFile::remove(filename); } LLFile::remove(mTestConfigFile); - LLFile::remove(mTestConfigDir); + LLFile::rmdir(mTestConfigDir); } void writeSettingsFile(const LLSD& config) { diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp old mode 100755 new mode 100644 index 962a91bb73..569fd30b21 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2268,7 +2268,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 (!LLFile::isfile(error_marker_file)) + if (!LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) { // If marker doesn't exist, create a marker with llerror code for next launch // otherwise don't override existing file @@ -3031,11 +3031,13 @@ void LLAppViewer::initStrings() } else { - if (!LLFile::exists(strings_path_full)) + llstat st; + int rc = LLFile::stat(strings_path_full, &st); + if (rc != 0) { - crash_reason = "The file '" + strings_path_full + "' doesn't seem to exist"; + crash_reason = "The file '" + strings_path_full + "' failed to get status. Error code: " + std::to_string(rc); } - else if (LLFile::isdir(strings_path_full)) + else if (S_ISDIR(st.st_mode)) { crash_reason = "The filename '" + strings_path_full + "' is a directory name"; } @@ -3898,7 +3900,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 (LLFile::isfile(mMarkerFileName)) + if (LLAPRFile::isExist(mMarkerFileName, NULL, LL_APR_RB)) { // File exists... // first, read it to see if it was created by the same version (we need this later) @@ -3990,7 +3992,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(LLFile::isfile(logout_marker_file)) + if(LLAPRFile::isExist(logout_marker_file, NULL, LL_APR_RB)) { if (markerIsSameVersion(logout_marker_file)) { @@ -4002,11 +4004,11 @@ void LLAppViewer::processMarkerFiles() { LL_INFOS("MarkerFile") << "Logout crash marker '"<< logout_marker_file << "' found, but versions did not match" << LL_ENDL; } - LLFile::remove(logout_marker_file); + LLAPRFile::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(LLFile::isfile(error_marker_file)) + if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) { S32 marker_code = getMarkerErrorCode(error_marker_file); if (marker_code >= 0) @@ -4031,7 +4033,7 @@ void LLAppViewer::processMarkerFiles() { LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL; } - LLFile::remove(error_marker_file); + LLAPRFile::remove(error_marker_file); } #if LL_DARWIN @@ -4058,7 +4060,7 @@ void LLAppViewer::removeMarkerFiles() if (mMarkerFile.getFileHandle()) { mMarkerFile.close() ; - LLFile::remove( mMarkerFileName ); + LLAPRFile::remove( mMarkerFileName ); LL_DEBUGS("MarkerFile") << "removed exec marker '"<getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); - return LLFile::isfile(error_marker_file); + return LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB); } void LLAppViewer::outOfMemorySoftQuit() diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp old mode 100755 new mode 100644 index 66066a45b2..c5c1e01538 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -1500,7 +1500,7 @@ bool LLFloaterPreference::moveTranscriptsAndLog() //Couldn't move the log and created a new directory so remove the new directory if(madeDirectory) { - LLFile::remove(chatLogPath); + LLFile::rmdir(chatLogPath); } return false; } @@ -1526,7 +1526,7 @@ bool LLFloaterPreference::moveTranscriptsAndLog() if(madeDirectory) { - LLFile::remove(chatLogPath); + LLFile::rmdir(chatLogPath); } return false; @@ -2031,15 +2031,17 @@ void LLFloaterPreference::changed() { if (LLConversationLog::instance().getIsLoggingEnabled()) { - getChild("clear_log")->setEnabled(LLConversationLog::instance().getConversations().size() > 0); + getChild("clear_log")->setEnabled(LLConversationLog::instance().getConversations().size() > 0); } else { // onClearLog clears list, then notifies changed() and only then clears file, // so check presence of conversations before checking file, file will cleared later. + llstat st; bool has_logs = LLConversationLog::instance().getConversations().size() > 0 - && LLFile::isfile(LLConversationLog::instance().getFileName()) - && LLFile::size(LLConversationLog::instance().getFileName()) > 0; + && LLFile::stat(LLConversationLog::instance().getFileName(), &st) == 0 + && S_ISREG(st.st_mode) + && st.st_size > 0; getChild("clear_log")->setEnabled(has_logs); } diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp old mode 100755 new mode 100644 index 0bf0946c42..c3bc24c6b9 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -484,47 +484,47 @@ bool LLFloaterUIPreview::postBuild() bool found_en_us = false; std::string language_directory; std::string xui_dir = get_xui_dir(); // directory containing localizations -- don't forget trailing delim - mLanguageSelection->removeall(); // clear out anything temporarily in list from XML + mLanguageSelection->removeall(); // clear out anything temporarily in list from XML LLDirIterator iter(xui_dir, "*"); - while (found) // for every directory + while(found) // for every directory { - if ((found = iter.next(language_directory))) // get next directory + if((found = iter.next(language_directory))) // get next directory { std::string full_path = gDirUtilp->add(xui_dir, language_directory); - if (!LLFile::isdir(full_path.c_str())) // if it's not a directory, skip it + if(LLFile::isfile(full_path.c_str())) // if it's not a directory, skip it { continue; } - if (strncmp("template",language_directory.c_str(), 8) && -1 == language_directory.find(".")) // if it's not the template directory or a hidden directory + if(strncmp("template",language_directory.c_str(),8) && -1 == language_directory.find(".")) // if it's not the template directory or a hidden directory { - if (!strncmp("en",language_directory.c_str(), 5)) // remember if we've seen en, so we can make it default + if(!strncmp("en",language_directory.c_str(),5)) // remember if we've seen en, so we can make it default { found_en_us = true; } else { - mLanguageSelection->add(std::string(language_directory)); // add it to the language selection dropdown menu + mLanguageSelection->add(std::string(language_directory)); // add it to the language selection dropdown menu mLanguageSelection_2->add(std::string(language_directory)); } } } } - if (found_en_us) + if(found_en_us) { - mLanguageSelection->add(std::string("en"), ADD_TOP); // make en first item if we found it - mLanguageSelection_2->add(std::string("en"), ADD_TOP); + mLanguageSelection->add(std::string("en"),ADD_TOP); // make en first item if we found it + mLanguageSelection_2->add(std::string("en"),ADD_TOP); } else { std::string warning = std::string("No EN localization found; check your XUI directories!"); popupAndPrintWarning(warning); } - mLanguageSelection->selectFirstItem(); // select the first item + mLanguageSelection->selectFirstItem(); // select the first item mLanguageSelection_2->selectFirstItem(); - refreshList(); // refresh the list of available floaters + refreshList(); // refresh the list of available floaters return true; } @@ -892,7 +892,8 @@ void LLFloaterUIPreview::displayFloater(bool click, S32 ID) // Add localization to title so user knows whether it's localized or defaulted to en std::string full_path = getLocalizedDirectory() + path; std::string floater_lang = "EN"; - if (LLFile::isfile(full_path.c_str())) // use localized language if the file exists + llstat dummy; + if(!LLFile::stat(full_path.c_str(), &dummy)) // if the file does not exist { floater_lang = getLocStr(ID); } @@ -965,8 +966,9 @@ void LLFloaterUIPreview::onClickEditFloater() } file_path = getLocalizedDirectory() + file_name; - // Does it exist? (Some localized versions may not have it when there are no diffs, and then we try to open a nonexistent file) - if (!LLFile::isfile(file_path.c_str())) // if the file does not exist + // 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 { 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 @@ -1115,14 +1117,15 @@ void LLFloaterUIPreview::onClickToggleDiffHighlighting() std::string path_in_textfield = mDiffPathTextBox->getText(); // get file path bool error = false; - if(std::string("") == path_in_textfield) // check for blank file + if(std::string("") == path_in_textfield) // check for blank file { std::string warning = "Unable to highlight differences because no file was provided; fill in the relevant text field"; popupAndPrintWarning(warning); error = true; } - if (!LLFile::isfile(path_in_textfield.c_str()) && !error) // check if the file exists (empty check is redundant but useful for the informative error message) + 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) { 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/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp old mode 100755 new mode 100644 index 4fe661b055..9a991727b2 --- a/indra/newview/llpreviewnotecard.cpp +++ b/indra/newview/llpreviewnotecard.cpp @@ -575,7 +575,8 @@ void LLPreviewNotecard::syncExternal() { // Sync with external editor. std::string tmp_file = getTmpFileName(); - if (LLFile::isfile(tmp_file)) // file exists + llstat s; + if (LLFile::stat(tmp_file, &s) == 0) // file exists { if (mLiveFile) mLiveFile->ignoreNextUpdate(); writeToFile(tmp_file); diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp old mode 100755 new mode 100644 index 2c436198e3..c2aa4925bd --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -694,7 +694,8 @@ void LLScriptEdCore::sync() if (mLiveFile) { std::string tmp_file = mLiveFile->filename(); - if (LLFile::isfile(tmp_file)) // file exists + llstat s; + if (LLFile::stat(tmp_file, &s) == 0) // file exists { mLiveFile->ignoreNextUpdate(); writeToFile(tmp_file); diff --git a/indra/newview/lltexturecache.cpp b/indra/newview/lltexturecache.cpp old mode 100755 new mode 100644 index a6d81816ce..1a7ce74ccc --- a/indra/newview/lltexturecache.cpp +++ b/indra/newview/lltexturecache.cpp @@ -180,7 +180,8 @@ private: bool LLTextureCacheLocalFileWorker::doRead() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - S32 local_size = (S32)LLFile::size(mFileName); + S32 local_size = LLAPRFile::size(mFileName, mCache->getLocalAPRFilePool()); + if (local_size > 0 && mFileName.size() > 4) { mDataSize = local_size; // Only a complete file is valid @@ -209,7 +210,8 @@ bool LLTextureCacheLocalFileWorker::doRead() } mReadData = (U8*)ll_aligned_malloc_16(mDataSize); - S32 bytes_read = (S32)LLFile::read(mFileName, mReadData, mOffset, mDataSize); + S32 bytes_read = LLAPRFile::readEx(mFileName, mReadData, mOffset, mDataSize, mCache->getLocalAPRFilePool()); + if (bytes_read != mDataSize) { // LL_WARNS() << "Error reading file from local cache: " << mFileName @@ -294,7 +296,7 @@ bool LLTextureCacheRemoteWorker::doRead() // Is it a JPEG2000 file? { local_filename = filename + ".j2c"; - local_size = (S32)LLFile::size(local_filename); + local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); if (local_size > 0) { mImageFormat = IMG_CODEC_J2C; @@ -304,7 +306,7 @@ bool LLTextureCacheRemoteWorker::doRead() if (local_size == 0) { local_filename = filename + ".jpg"; - local_size = (S32)LLFile::size(local_filename); + local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); if (local_size > 0) { mImageFormat = IMG_CODEC_JPEG; @@ -315,7 +317,7 @@ bool LLTextureCacheRemoteWorker::doRead() if (local_size == 0) { local_filename = filename + ".tga"; - local_size = (S32)LLFile::size(local_filename); + local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); if (local_size > 0) { mImageFormat = IMG_CODEC_TGA; @@ -344,10 +346,12 @@ bool LLTextureCacheRemoteWorker::doRead() if (mReadData) { - S32 bytes_read = (S32)LLFile::read(local_filename, - mReadData, - mOffset, - mDataSize); + S32 bytes_read = LLAPRFile::readEx( local_filename, + mReadData, + mOffset, + mDataSize, + mCache->getLocalAPRFilePool()); + if (bytes_read != mDataSize) { LL_WARNS() << "Error reading file from local cache: " << local_filename @@ -406,7 +410,8 @@ bool LLTextureCacheRemoteWorker::doRead() mReadData = (U8*)ll_aligned_malloc_16(size); if (mReadData) { - S32 bytes_read = (S32)LLFile::read(mCache->mHeaderDataFileName, mReadData, offset, size); + S32 bytes_read = LLAPRFile::readEx(mCache->mHeaderDataFileName, + mReadData, offset, size, mCache->getLocalAPRFilePool()); if (bytes_read != size) { LL_WARNS() << "LLTextureCacheWorker: " << mID @@ -441,9 +446,9 @@ bool LLTextureCacheRemoteWorker::doRead() if (!done && (mState == BODY)) { std::string filename = mCache->getTextureFileName(mID); - S32 filesize = (S32)LLFile::size(filename); + S32 filesize = LLAPRFile::size(filename, mCache->getLocalAPRFilePool()); - if (filesize > 0 && (filesize + TEXTURE_CACHE_ENTRY_SIZE) > mOffset) + if (filesize && (filesize + TEXTURE_CACHE_ENTRY_SIZE) > mOffset) { S32 max_datasize = TEXTURE_CACHE_ENTRY_SIZE + filesize - mOffset; mDataSize = llmin(max_datasize, mDataSize); @@ -482,9 +487,10 @@ bool LLTextureCacheRemoteWorker::doRead() mReadData = data; // Read the data at last - S32 bytes_read = (S32)LLFile::read(filename, - mReadData + data_offset, - file_offset, file_size); + S32 bytes_read = LLAPRFile::readEx(filename, + mReadData + data_offset, + file_offset, file_size, + mCache->getLocalAPRFilePool()); if (bytes_read != file_size) { LL_WARNS() << "LLTextureCacheWorker: " << mID @@ -630,13 +636,13 @@ bool LLTextureCacheRemoteWorker::doWrite() U8* padBuffer = (U8*)ll_aligned_malloc_16(TEXTURE_CACHE_ENTRY_SIZE); memset(padBuffer, 0, TEXTURE_CACHE_ENTRY_SIZE); // Init with zeros memcpy(padBuffer, mWriteData, mDataSize); // Copy the write buffer - bytes_written = (S32)LLFile::write(mCache->mHeaderDataFileName, padBuffer, offset, size); + bytes_written = LLAPRFile::writeEx(mCache->mHeaderDataFileName, padBuffer, offset, size, mCache->getLocalAPRFilePool()); ll_aligned_free_16(padBuffer); } else { // Write the header record (== first TEXTURE_CACHE_ENTRY_SIZE bytes of the raw file) in the header file - bytes_written = (S32)LLFile::write(mCache->mHeaderDataFileName, mWriteData, offset, size); + bytes_written = LLAPRFile::writeEx(mCache->mHeaderDataFileName, mWriteData, offset, size, mCache->getLocalAPRFilePool()); } if (bytes_written <= 0) @@ -672,11 +678,15 @@ bool LLTextureCacheRemoteWorker::doWrite() else { S32 file_size = mDataSize - TEXTURE_CACHE_ENTRY_SIZE; + { // build the cache file name from the UUID std::string filename = mCache->getTextureFileName(mID); // LL_INFOS() << "Writing Body: " << filename << " Bytes: " << file_offset+file_size << LL_ENDL; - S32 bytes_written = (S32)LLFile::write(filename, mWriteData + TEXTURE_CACHE_ENTRY_SIZE, 0, file_size); + S32 bytes_written = LLAPRFile::writeEx(filename, + mWriteData + TEXTURE_CACHE_ENTRY_SIZE, + 0, file_size, + mCache->getLocalAPRFilePool()); if (bytes_written <= 0) { LL_WARNS() << "LLTextureCacheWorker: " << mID @@ -881,7 +891,7 @@ bool LLTextureCache::isInLocal(const LLUUID& id) // Is it a JPEG2000 file? { local_filename = filename + ".j2c"; - local_size = (S32)LLFile::size(local_filename); + local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); if (local_size > 0) { return true ; @@ -891,7 +901,7 @@ bool LLTextureCache::isInLocal(const LLUUID& id) // If not, is it a jpeg file? { local_filename = filename + ".jpg"; - local_size = (S32)LLFile::size(local_filename); + local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); if (local_size > 0) { return true ; @@ -901,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 = (S32)LLFile::size(local_filename); + local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); if (local_size > 0) { return true ; @@ -933,6 +943,8 @@ const char* fast_cache_filename = "FastCache.cache"; void LLTextureCache::setDirNames(ELLPath location) { + std::string delem = gDirUtilp->getDirDelimiter(); + mHeaderEntriesFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, entries_filename); mHeaderDataFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, cache_filename); mTexturesDirName = gDirUtilp->getExpandedFilename(location, textures_dirname); @@ -954,10 +966,11 @@ void LLTextureCache::purgeCache(ELLPath location, bool remove_dir) if(LLFile::isdir(mTexturesDirName)) { std::string file_name = gDirUtilp->getExpandedFilename(location, entries_filename); - LLFile::remove(file_name); + // mHeaderAPRFilePoolp because we are under header mutex, and can be in main thread + LLAPRFile::remove(file_name, mHeaderAPRFilePoolp); file_name = gDirUtilp->getExpandedFilename(location, cache_filename); - LLFile::remove(file_name); + LLAPRFile::remove(file_name, mHeaderAPRFilePoolp); purgeAllTextures(true); } @@ -1058,9 +1071,10 @@ void LLTextureCache::readEntriesHeader() { // mHeaderEntriesInfo initializes to default values so safe not to read it llassert_always(mHeaderAPRFile == NULL); - if (LLFile::isfile(mHeaderEntriesFileName)) + if (LLAPRFile::isExist(mHeaderEntriesFileName, mHeaderAPRFilePoolp)) { - LLFile::read(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo)); + LLAPRFile::readEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo), + mHeaderAPRFilePoolp); } else //create an empty entries header. { @@ -1076,7 +1090,7 @@ void LLTextureCache::setEntriesHeader() // For simplicity we use predefined size of header, so if version string // doesn't fit, either getEngineInfo() returned malformed string or // sHeaderEncoderStringSize need to be increased. - // Also take into account that c_str() returns additional null character + // Also take into accout that c_str() returns additional null character LL_ERRS() << "Version string doesn't fit in header" << LL_ENDL; } @@ -1091,7 +1105,8 @@ void LLTextureCache::writeEntriesHeader() llassert_always(mHeaderAPRFile == NULL); if (!mReadOnly) { - LLFile::write(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo)); + LLAPRFile::writeEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo), + mHeaderAPRFilePoolp); } } @@ -1195,7 +1210,8 @@ void LLTextureCache::writeEntryToHeaderImmediately(S32& idx, Entry& entry, bool idx = -1 ;//mark the idx invalid. return ; } - aprfile->seek(APR_SET, offset); + + mHeaderAPRFile->seek(APR_SET, offset); } else { @@ -1599,7 +1615,7 @@ void LLTextureCache::purgeAllTextures(bool purge_directories) gDirUtilp->deleteFilesInDir(mTexturesDirName, mask); // headers, fast cache if (purge_directories) { - LLFile::remove(mTexturesDirName); + LLFile::rmdir(mTexturesDirName); } } mHeaderIDMap.clear(); @@ -1779,7 +1795,8 @@ void LLTextureCache::purgeTextures(bool validate) { std::string filename = getTextureFileName(entries[idx].mID); LL_DEBUGS("TextureCache") << "Validating: " << filename << "Size: " << entries[idx].mBodySize << LL_ENDL; - S32 bodysize = (S32)LLFile::size(filename); + // mHeaderAPRFilePoolp because this is under header mutex in main thread + S32 bodysize = LLAPRFile::size(filename, mHeaderAPRFilePoolp); if (bodysize != entries[idx].mBodySize) { LL_WARNS("TextureCache") << "TEXTURE CACHE BODY HAS BAD SIZE: " << bodysize << " != " << entries[idx].mBodySize << filename << LL_ENDL; @@ -2130,7 +2147,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 (LLFile::isfile(mFastCacheFileName)) + if (LLAPRFile::isExist(mFastCacheFileName, mFastCachePoolp)) { mFastCachep = new LLAPRFile(mFastCacheFileName, APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ; } @@ -2213,7 +2230,9 @@ void LLTextureCache::removeCachedTexture(const LLUUID& id) mTexturesSizeMap.erase(id); } mHeaderIDMap.erase(id); - LLFile::remove(getTextureFileName(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); } //called after mHeaderMutex is locked. @@ -2226,7 +2245,9 @@ 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. - if (LLFile::isfile(filename)) + // 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)) { LL_WARNS("TextureCache") << "Entry has body size of zero but file " << filename << " exists. Deleting this file, too." << LL_ENDL; } @@ -2246,7 +2267,7 @@ void LLTextureCache::removeEntry(S32 idx, Entry& entry, std::string& filename) if (file_maybe_exists) { - LLFile::remove(filename); + LLAPRFile::remove(filename, mHeaderAPRFilePoolp); } } diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index e76d340eda..141f370ecb 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -182,7 +182,7 @@ void LLViewerAssetStorage::storeAssetData( else { // LLAssetStorage metric: Successful Request - S32 size = (S32)LLFileSystem::getFileSize(asset_id, asset_type); + S32 size = LLFileSystem::getFileSize(asset_id, asset_type); const char *message = "Added to upload queue"; reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, size, MR_OKAY, __FILE__, __LINE__, message ); diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 5ac7d6b1fe..65a69acc88 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -45,7 +45,11 @@ #include "llappviewer.h" #include "llviewerstats.h" #include "llfilesystem.h" +#include "llgesturemgr.h" +#include "llpreviewnotecard.h" +#include "llpreviewgesture.h" #include "llcoproceduremanager.h" +#include "llthread.h" #include "llkeyframemotion.h" #include "lldatapacker.h" #include "llvoavatarself.h" @@ -401,7 +405,6 @@ LLSD LLNewFileResourceUploadInfo::exportTempFile() std::string errorMessage; std::string errorLabel; - std::error_code ec; bool error = false; @@ -472,38 +475,30 @@ LLSD LLNewFileResourceUploadInfo::exportTempFile() error = true; // read from getFileName() - LLFile infile(getFileName(), LLFile::in | LLFile::binary, ec); - if (ec || !infile) + LLAPRFile infile; + infile.open(getFileName(),LL_APR_RB); + if (!infile.getFileHandle()) { LL_WARNS() << "Couldn't open file for reading: " << getFileName() << LL_ENDL; errorMessage = llformat("Failed to open animation file %s\n", getFileName().c_str()); } else { - S64 size = infile.size(ec); - if (ec || size <= 0) - { - LLError::LLUserWarningMsg::showMissingFiles(); - LL_ERRS() << "Invalid file" << LL_ENDL; - } - else if (size > INT_MAX) - { - LL_ERRS() << "File is too big, size: " << size << LL_ENDL; - } + S32 size = LLAPRFile::size(getFileName()); U8* buffer = new(std::nothrow) U8[size]; if (!buffer) { LLError::LLUserWarningMsg::showOutOfMemory(); LL_ERRS() << "Bad memory allocation for buffer, size: " << size << LL_ENDL; } - S64 size_read = infile.read(buffer, size, ec); - if (ec || size_read != size) + S32 size_read = infile.read(buffer,size); + if (size_read != size) { errorMessage = llformat("Failed to read animation file %s: wanted %d bytes, got %d\n", getFileName().c_str(), size, size_read); } else { - LLDataPackerBinaryBuffer dp(buffer, (S32)size); + LLDataPackerBinaryBuffer dp(buffer, size); LLKeyframeMotion *motionp = new LLKeyframeMotion(getAssetId()); motionp->setCharacter(gAgentAvatarp); if (motionp->deserialize(dp, getAssetId(), false)) @@ -549,17 +544,18 @@ LLSD LLNewFileResourceUploadInfo::exportTempFile() setAssetType(assetType); // copy this file into the cache for upload - LLFile infile(filename, LLFile::in | LLFile::binary, ec); - if (!ec && infile.size(ec) > 0) + S32 file_size; + LLAPRFile infile; + infile.open(filename, LL_APR_RB, NULL, &file_size); + if (infile.getFileHandle()) { LLFileSystem file(getAssetId(), assetType, LLFileSystem::APPEND); - S64 read_bytes; const S32 buf_size = 65536; U8 copy_buf[buf_size]; - while (((read_bytes = infile.read(copy_buf, buf_size, ec))) > 0) + while ((file_size = infile.read(copy_buf, buf_size))) { - file.write(copy_buf, (S32)read_bytes); + file.write(copy_buf, file_size); } } else @@ -573,6 +569,7 @@ LLSD LLNewFileResourceUploadInfo::exportTempFile() } return LLSD(); + } //========================================================================= diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp old mode 100755 new mode 100644 index 7534d778a5..35ac7919ac --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -1140,12 +1140,15 @@ std::string getProfileStatsFilename() // same second), may produce (e.g.) sec==61, but avoids collisions and // preserves chronological filename sort order. std::string name; + std::error_code ec; do { // base + missing 2-digit seconds, append ".json" // post-increment sec in case we have to try again name = stringize(base, std::setw(2), std::setfill('0'), sec++, ".json"); - } while (LLFile::exists(fsyspath(name))); + } while (std::filesystem::exists(fsyspath(name), ec)); + // Ignoring ec means we might potentially return a name that does already + // exist -- but if we can't check its existence, what more can we do? return name; } diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp old mode 100755 new mode 100644 index 5cb05460bc..a77b9f6103 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -1827,11 +1827,12 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ user_data_path_cache += gDirUtilp->getDirDelimiter(); // See if the plugin executable exists - if (!LLFile::isfile(launcher_name)) + llstat s; + if(LLFile::stat(launcher_name, &s)) { LL_WARNS_ONCE("Media") << "Couldn't find launcher at " << launcher_name << LL_ENDL; } - else if (!LLFile::isfile(plugin_name)) + else if(LLFile::stat(plugin_name, &s)) { #if !LL_LINUX LL_WARNS_ONCE("Media") << "Couldn't find plugin at " << plugin_name << LL_ENDL; diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp old mode 100755 new mode 100644 index cdc41baa88..5d456b1a19 --- a/indra/newview/llvocache.cpp +++ b/indra/newview/llvocache.cpp @@ -1250,7 +1250,7 @@ void LLVOCache::removeCache(ELLPath location, bool started) std::string cache_dir = gDirUtilp->getExpandedFilename(location, object_cache_dirname); LL_INFOS() << "Removing cache at " << cache_dir << LL_ENDL; gDirUtilp->deleteFilesInDir(cache_dir, mask); //delete all files - LLFile::remove(cache_dir); + LLFile::rmdir(cache_dir); clearCacheInMemory(); mInitialized = false; @@ -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; - LLFile::remove(filename); + LLAPRFile::remove(filename, mLocalAPRFilePoolp); // 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 (LLFile::isfile(mHeaderFileName)) + if (LLAPRFile::isExist(mHeaderFileName, mLocalAPRFilePoolp)) { LLAPRFile apr_file(mHeaderFileName, APR_READ|APR_BINARY, mLocalAPRFilePoolp); diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp old mode 100755 new mode 100644 index e58a6577f1..d132cbfa36 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -943,7 +943,8 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() gDirUtilp->append(exe_path, "SLVoice"); #endif // See if the vivox executable exists - if (LLFile::isfile(exe_path)) + llstat s; + if (!LLFile::stat(exe_path, &s)) { // vivox executable exists. Build the command line and launch the daemon. LLProcess::Params params; diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt old mode 100755 new mode 100644 index 69c9660575..246fc5e6f8 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -16,7 +16,6 @@ set(test_SOURCE_FILES llbuffer_tut.cpp lldoubledispatch_tut.cpp llevents_tut.cpp - llfile_tut.cpp llhttpdate_tut.cpp llhttpnode_tut.cpp lliohttpserver_tut.cpp @@ -68,7 +67,7 @@ target_link_libraries(lltest if (WINDOWS) set_target_properties(lltest - PROPERTIES + PROPERTIES LINK_FLAGS "/NODEFAULTLIB:LIBCMT" LINK_FLAGS_DEBUG "/NODEFAULTLIB:\"LIBCMT;LIBCMTD;MSVCRT\"" RUNTIME_OUTPUT_DIRECTORY "${EXE_STAGING_DIR}" @@ -86,10 +85,10 @@ set(TEST_EXE $) SET_TEST_PATH(LD_LIBRARY_PATH) -LL_TEST_COMMAND(command +LL_TEST_COMMAND(command "${LD_LIBRARY_PATH}" "${TEST_EXE}" - "--output=${CMAKE_CURRENT_BINARY_DIR}/cpp_test_results.txt" + "--output=${CMAKE_CURRENT_BINARY_DIR}/cpp_test_results.txt" "--touch=${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt") ADD_CUSTOM_COMMAND( @@ -102,11 +101,11 @@ ADD_CUSTOM_COMMAND( set(test_results ${CMAKE_CURRENT_BINARY_DIR}/cpp_tests_ok.txt) -# This should cause the test executable to be built, but not +# This should cause the test executable to be built, but not # run if LL_TESTS is disabled. This will hopefully keep the -# tests up to date with any code changes changes even if +# tests up to date with any code changes changes even if # developers choose to disable LL_TESTS. -if (LL_TESTS) +if (LL_TESTS) add_custom_target(tests_ok ALL DEPENDS ${test_results}) if(DARWIN) # Support our "@executable_path/../Resources" load path for our test diff --git a/indra/test/llfile_tut.cpp b/indra/test/llfile_tut.cpp deleted file mode 100755 index 9f2a9b2988..0000000000 --- a/indra/test/llfile_tut.cpp +++ /dev/null @@ -1,253 +0,0 @@ -/** - * @file llfile_tut.cpp - * @author Frederick Martian - * @date 2025-11 - * @brief LLFile test cases. - * - * $LicenseInfo:firstyear=2025&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2025, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include -#include "lltut.h" -#include "linden_common.h" -#include "llfile.h" - -namespace tut -{ - static void clear_entire_dir(std::filesystem::path &dir) - { - std::error_code ec; - std::filesystem::remove_all(dir, ec); - } - - static std::filesystem::path append_filename(const std::filesystem::path& dir, const std::string& element) - { - std::filesystem::path path = dir; - return path.append(element); - } - - static std::filesystem::path get_testdir(const std::filesystem::path& tempdir) - { - return append_filename(tempdir, std::string("test_dir")); - } - - struct llfile_test - { - std::filesystem::path tempdir = LLFile::tmpdir(); - std::filesystem::path testdir = get_testdir(tempdir); - }; - typedef test_group llfile_test_t; - typedef llfile_test_t::object llfile_test_object_t; - tut::llfile_test_t tut_llfile_test("llfile_test"); - - template<> template<> - void llfile_test_object_t::test<1>() - { - // Test creating directories and files and deleting them and checking if the - // relevant status functions work as expected - ensure("LLFile::tmpdir() empty", !tempdir.empty()); - ensure("LLFile::tmpdir() doesn't exist", LLFile::exists(tempdir.string())); - ensure("LLFile::tmpdir() is not a directory", LLFile::isdir(tempdir.string())); - ensure("LLFile::tmpdir() should not be a file", !LLFile::isfile(tempdir.string())); - - // Make sure there is nothing left from a previous test run - clear_entire_dir(testdir); - ensure("llfile_test should not exist anymore", !LLFile::exists(testdir.string())); - - int rc = LLFile::mkdir(testdir.string()); - ensure("LLFile::mkdir() failed", rc == 0); - ensure("llfile_test should be a directory", LLFile::isdir(testdir.string())); - rc = LLFile::mkdir(testdir.string()); - ensure("LLFile::mkdir() should not fail when the directory already exists", rc == 0); - - std::filesystem::path testfile1 = testdir; - testfile1.append("llfile_test.dat"); - ensure("llfile_test1.dat should not yet exist", !LLFile::exists(testfile1.string())); - - const char* testdata = "testdata"; - S64 bytes = LLFile::write(testfile1.string(), testdata, 0, sizeof(testdata)); - ensure("LLFile::write() did not write correctly", bytes == sizeof(testdata)); - - rc = LLFile::remove(testfile1.string()); - ensure("LLFile::remove() for file test_file.dat", rc == 0); - ensure("llfile_test.dat should not exist anymore", !LLFile::exists(testfile1.string())); - ensure("llfile_test.dat should not be a file", !LLFile::isfile(testfile1.string())); - ensure("llfile_test.dat should not be a directory", !LLFile::isdir(testfile1.string())); - ensure("llfile_test.dat should not be a symlink", !LLFile::islink(testfile1.string())); - - rc = LLFile::remove(testdir.string()); - ensure("LLFile::remove() for directory llfile_test failed", rc == 0); - ensure("llfile_test should not exist anymore", !LLFile::exists(testdir.string())); - } - - template<> template<> - void llfile_test_object_t::test<2>() - { - // High level static file IO functions to read and write data files - LLFile::mkdir(testdir.string()); - ensure("llfile_test should exist", LLFile::isdir(testdir.string())); - - std::filesystem::path testfile1 = testdir; - testfile1.append("llfile_test.dat"); - - std::string testdata1("testdata"); - std::string testdata2("datateststuff"); - std::time_t current = time(nullptr); - S64 bytes = LLFile::write(testfile1.string(), testdata1.c_str(), 0, testdata1.length()); - ensure("LLFile::write() did not write correctly", bytes == testdata1.length()); - ensure("llfile_test.dat should exist", LLFile::exists(testfile1.string())); - ensure("llfile_test.dat should be a file", LLFile::isfile(testfile1.string())); - ensure("llfile_test.dat should not be a directory", !LLFile::isdir(testfile1.string())); - - bytes = LLFile::size(testfile1.string()); - ensure("LLFile::size() did not return the correct size", bytes == testdata1.length()); - - std::string data = LLFile::getContents(testfile1.string()); - ensure("LLFile::getContents() did not return the correct size data", data.length() == testdata1.length()); - ensure_memory_matches("LLFile::getContents() did not read correct data", testdata1.c_str(), (U32)testdata1.length(), data.c_str(), (U32)data.length()); - - std::time_t ctime = LLFile::getCreationTime(testfile1.string()); - ensure_approximately_equals_range("LLFile::getCreationTime() did not return correct time", (F32)(ctime - current), 0.f, 1); - - std::time_t mtime = LLFile::getModificationTime(testfile1.string()); - ensure_approximately_equals_range("LLFile::getModificationTime() did not return correct time", (F32)(mtime - current), 0.f, 1); - - char buffer[1024]; - bytes = LLFile::read(testfile1.string(), buffer, 0, testdata1.length()); - ensure("LLFile:read() did not return the correct size", bytes == testdata1.length()); - ensure_memory_matches("LLFile::read() did not read correct data", testdata1.c_str(), (U32)bytes, buffer, (U32)bytes); - - // What if we try to read more data than there is in the file? - bytes = LLFile::read(testfile1.string(), buffer, 0, bytes + 10); - ensure("LLFile:read() did not correctly stop on eof", bytes == testdata1.length()); - ensure_memory_matches("LLFile::read() did not read correct data", testdata1.c_str(), (U32)bytes, buffer, (U32)bytes); - - // Let's append more data - bytes = LLFile::write(testfile1.string(), testdata2.c_str(), -1, testdata2.length()); - ensure("LLFile::write() did not write correctly", bytes == testdata2.length()); - - bytes = LLFile::size(testfile1.string()); - ensure("LLFile::size() did not return the correct size", bytes == testdata1.length() + testdata2.length()); - bytes = LLFile::read(testfile1.string(), buffer, 0, bytes); - ensure("LLFile:read() did not read correct number of bytes", bytes == testdata1.length() + testdata2.length()); - ensure_memory_matches("LLFile:read() did not read correct testdata1", testdata1.c_str(), (U32)testdata1.length(), buffer, (U32)testdata1.length()); - ensure_memory_matches("LLFile:read() did not read correct testdata2", testdata2.c_str(), (U32)testdata2.length(), buffer + testdata1.length(), (U32)testdata2.length()); - } - - template<> template<> - void llfile_test_object_t::test<3>() - { - const size_t numints = 1024; - - // Testing the LLFile class implementation - std::filesystem::path testfile = testdir; - testfile.append("llfile_test.bin"); - - int data[numints]; - for (int &t : data) - { - t = rand(); - } - - std::error_code ec; - LLFile fileout(testfile.string(), LLFile::out, ec); - ensure("LLFile constructor did not open correctly", (bool)fileout); - ensure("error_code from LLFile constructor should not indicate an error", !ec); - if (fileout) - { - S64 length = fileout.size(ec); - ensure("freshly created file should be empty", length == 0); - ensure("error_code from LLFile::size() should not indicate an error", !ec); - S64 bytes = fileout.write(data, sizeof(data), ec); - ensure("LLFile::write() did not write correctly", bytes == sizeof(data)); - ensure("error_code from LLFile::write() should not indicate an error", !ec); - bytes = fileout.write(data, sizeof(data), ec); - ensure("LLFile::write() did not write correctly", bytes == sizeof(data)); - ensure("error_code from LLFile::write() should not indicate an error", !ec); - bytes = fileout.size(ec); - ensure("LLFile::size() returned wrong size", bytes == 2 * sizeof(data)); - ensure("error_code from LLFile::size() should not indicate an error", !ec); - fileout.close(); - } - - LLFile filein(testfile.string(), LLFile::in, ec); - ensure("LLFile constructor did not open correctly", (bool)filein); - ensure("error_code from LLFile constructor should not indicate an error", !ec); - if (filein) - { - S64 length = filein.size(ec); - ensure("LLFile::size() returned wrong size", length == 2 * sizeof(data)); - ensure("error_code from LLFile::size() should not indicate an error", !ec); - char* buffer = (char*)malloc(length); - S64 bytes = filein.read(buffer, length, ec); - ensure("LLFile::read() did not read correctly", bytes == length); - ensure("error_code from LLFile::read() should not indicate an error", !ec); - ensure_memory_matches("LLFile:read() did not read correct data1", data, (U32)sizeof(data), buffer, (U32)sizeof(data)); - ensure_memory_matches("LLFile:read() did not read correct data2", data, (U32)sizeof(data), buffer + sizeof(data), (U32)sizeof(data)); - S64 offset = filein.tell(ec); - ensure("LLFile::tell() returned a bad offset", offset == length); - ensure("error_code from LLFile::read() should not indicate an error", !ec); - offset = sizeof(data) / 2; - int rc = filein.seek(offset, ec); - ensure("LLFile::seek() indicated an error", rc == 0); - ensure("error_code from LLFile::seek() should not indicate an error", !ec); - bytes = filein.read(buffer, 2 * sizeof(data), ec); - ensure("LLFile::read() did not read correctly", bytes == sizeof(data) + offset); - ensure("error_code from LLFile::read() should not indicate an error", !ec); - ensure_memory_matches("LLFile:read() did not read correct data3", (char*)data + offset, (U32)offset, buffer, (U32)offset); - ensure_memory_matches("LLFile:read() did not read correct data4", (char*)data, (U32)sizeof(data), buffer + offset, (U32)sizeof(data)); - filein.close(); - - free(buffer); - } - } - - template<> template<> - void llfile_test_object_t::test<4>() - { - // Testing the LLFile class implementation with wrong paths and parameters - std::filesystem::path testfile = testdir; - testfile.append("llfile_test.bin"); - - std::error_code ec; - LLFile file(testfile.string(), LLFile::out | LLFile::noreplace, ec); - ensure("LLFile constructor should not have opened the already existing file", !file); - ensure("error_code from LLFile constructor should indicate an error", (bool)ec); - - LLFile::remove(testfile.string()); - file = LLFile(testfile.string(), LLFile::out | LLFile::app | LLFile::trunc, ec); - ensure("LLFile constructor should not have opened the file with conflicting flags", !file); - ensure("error_code from LLFile constructor should indicate an error", (bool)ec); - - file = LLFile(testfile.string(), LLFile::out | LLFile::app | LLFile::noreplace, ec); - ensure("LLFile constructor should not have opened the file with conflicting flags", !file); - ensure("error_code from LLFile constructor should indicate an error", (bool)ec); - - testfile = testdir; - testfile.append("llfile_test"); - testfile.append("llfile_test.bin"); - - file = LLFile(testfile.string(), LLFile::in, ec); - ensure("LLFile constructor should not have been able to open the file in the non-existing directory", !file); - ensure("error_code from LLFile constructor should indicate an error", (bool)ec); - } -} // namespace tut diff --git a/indra/test/llmessageconfig_tut.cpp b/indra/test/llmessageconfig_tut.cpp old mode 100755 new mode 100644 index b8942e99b3..93443467a2 --- a/indra/test/llmessageconfig_tut.cpp +++ b/indra/test/llmessageconfig_tut.cpp @@ -62,7 +62,7 @@ namespace tut int rmfile = LLFile::remove((mTestConfigDir + "/message.xml")); ensure_equals("rmfile value", rmfile, 0); // rm temp dir - int rmdir = LLFile::remove(mTestConfigDir); + int rmdir = LLFile::rmdir(mTestConfigDir); ensure_equals("rmdir value", rmdir, 0); } diff --git a/indra/test/message_tut.cpp b/indra/test/message_tut.cpp old mode 100755 new mode 100644 index 6fb2b92803..11cd710ef6 --- a/indra/test/message_tut.cpp +++ b/indra/test/message_tut.cpp @@ -113,7 +113,7 @@ namespace tut ensure_equals("rmfile value", rmfile, 0); // rm temp dir - int rmdir = LLFile::remove(mTestConfigDir); + int rmdir = LLFile::rmdir(mTestConfigDir); ensure_equals("rmdir value", rmdir, 0); } -- cgit v1.3 From f4eec813a3043e2277ae62da6a829c65887d0785 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 11 Dec 2025 21:05:00 +0200 Subject: #5148 Crash cleaning LLVOCache --- indra/newview/llappviewer.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 569fd30b21..b8aac829fe 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4510,7 +4510,19 @@ void LLAppViewer::purgeCacheImmediate() { LL_INFOS("AppCache") << "Purging Object Cache and Texture Cache immediately..." << LL_ENDL; LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE, false); - LLVOCache::getInstance()->removeCache(LL_PATH_CACHE, true); + if (LLVOCache::instanceExists()) + { + LLVOCache::getInstance()->removeCache(LL_PATH_CACHE, true); + } + else if (!mSecondInstance) + { + // LLVOCache requires parameters to be initialized, if it's not there, try manually + std::string mask = "*"; + std::string cache_dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "objectcache"); + LL_INFOS() << "Removing cache at " << cache_dir << LL_ENDL; + gDirUtilp->deleteFilesInDir(cache_dir, mask); //delete all files + LLFile::rmdir(cache_dir); + } } std::string LLAppViewer::getSecondLifeTitle() const -- cgit v1.3 From 0c9dfeb2c709ced86ca9967a82b9816eb192c54c Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 6 Jan 2026 23:58:57 +0200 Subject: #5217 Treat issues with control variables smarter and show a proper warning --- indra/llxml/llcontrol.cpp | 29 ++++++++++++++++++++++++----- indra/llxml/llcontrol.h | 2 +- indra/newview/llappviewer.cpp | 5 ++++- 3 files changed, 29 insertions(+), 7 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp index 562a30e8d1..5a83f8c541 100644 --- a/indra/llxml/llcontrol.cpp +++ b/indra/llxml/llcontrol.cpp @@ -157,9 +157,12 @@ LLControlVariable::LLControlVariable(const std::string& name, eControlType type, { if ((persist != PERSIST_NO) && mComment.empty()) { - // File isn't actually missing, but something is wrong with it - // so the main point is to warn user to reinstall - LLError::LLUserWarningMsg::showMissingFiles(); + std::string error_string = + "Second Life failed to initialize settings. Setting " + mName + " is invalid. " + "Either settings' files were supplied incorrectly or default files were corrupted." + "\n\nPlease reinstall viewer from https://secondlife.com/support/downloads/ and " + "contact https://support.secondlife.com if issue persists after reinstall."; + LLError::LLUserWarningMsg::show(error_string); LL_ERRS() << "Must supply a comment for control " << mName << LL_ENDL; } //Push back versus setValue'ing here, since we don't want to call a signal yet @@ -990,7 +993,7 @@ U32 LLControlGroup::saveToFile(const std::string& filename, bool nondefault_only return num_saved; } -U32 LLControlGroup::loadFromFile(const std::string& filename, bool set_default_values, bool save_values) +U32 LLControlGroup::loadFromFile(const std::string& filename, bool set_default_values, bool save_values, bool error_when_no_comment) { LLSD settings; llifstream infile; @@ -1105,10 +1108,26 @@ U32 LLControlGroup::loadFromFile(const std::string& filename, bool set_default_v } } + std::string comment = control_map["Comment"].asString(); + if (!error_when_no_comment + && !set_default_values + && comment.empty()) + { + // Only error for default settings that should remind the developer to provide comments + // and otherwise indicate a problem with viewer's files. + // But permit this minor transgression in user's files. + // Otherwise user might have a hard time figuring out source of the error or how to fix it. + // Instead make setting to not persist so that unrecognized invalid settings won't be saved + // for the next run. + persist = LLControlVariable::PERSIST_NO; + comment = "Comment not provided, setting won't persist"; + LL_WARNS() << "Control " << name << " is missing a comment value. Setting will be marked as PERSIST_NO" << LL_ENDL; + } + declareControl(name, typeStringToEnum(control_map["Type"].asString()), control_map["Value"], - control_map["Comment"].asString(), + comment, persist, hidefromsettingseditor ); diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h index da7268bf45..c9bd0a8a32 100644 --- a/indra/llxml/llcontrol.h +++ b/indra/llxml/llcontrol.h @@ -278,7 +278,7 @@ public: // as the given type. U32 loadFromFileLegacy(const std::string& filename, bool require_declaration = true, eControlType declare_as = TYPE_STRING); U32 saveToFile(const std::string& filename, bool nondefault_only); - U32 loadFromFile(const std::string& filename, bool default_values = false, bool save_values = true); + U32 loadFromFile(const std::string& filename, bool default_values = false, bool save_values = true, bool error_when_no_comment = true); void resetToDefaults(); void incrCount(std::string_view name); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index b8aac829fe..bf00a7fed4 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2455,7 +2455,10 @@ bool LLAppViewer::loadSettingsFromDirectory(const std::string& location_key, full_settings_path = gDirUtilp->getExpandedFilename((ELLPath)path_index, file.file_name()); } - if(settings_group->loadFromFile(full_settings_path, set_defaults, file.persistent)) + // Be softer for files in the user's folders, user can't just reinstall those + bool error_when_no_comment = !set_defaults && location_key != "User"; + + if(settings_group->loadFromFile(full_settings_path, set_defaults, file.persistent, error_when_no_comment)) { // success! LL_INFOS("Settings") << "Loaded settings file " << full_settings_path << LL_ENDL; } -- cgit v1.3 From 147e8642466df56590197fa0c16aedecc0fa553b Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 12 Jan 2026 20:17:22 +0200 Subject: #5241 Crash at onIdleUpdateFavorites --- indra/llcommon/llapp.cpp | 1 + indra/llcommon/llapp.h | 2 ++ indra/llui/llfolderviewitem.cpp | 6 ++++++ indra/newview/llappviewer.cpp | 3 --- indra/newview/llappviewer.h | 2 -- 5 files changed, 9 insertions(+), 5 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index c532620daa..f92bb98ba6 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -93,6 +93,7 @@ bool LLApp::sDisableCrashlogger = false; LLScalarCond LLApp::sStatus{LLApp::APP_STATUS_STOPPED}; LLAppErrorHandler LLApp::sErrorHandler = NULL; +bool gDisconnected = false; LLApp::LLApp() { diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index 57f5a112d9..ce09c566a9 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -50,6 +50,8 @@ void clear_signals(); #endif +extern bool gDisconnected; + class LL_COMMON_API LLApp { public: diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 878f1cb856..3a6caaceea 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -28,6 +28,7 @@ #include "llflashtimer.h" #include "linden_common.h" +#include "llapp.h" #include "llfolderviewitem.h" #include "llfolderview.h" #include "llfolderviewmodel.h" @@ -1884,6 +1885,11 @@ void LLFolderViewFolder::updateHasFavorites(bool new_childs_value) void LLFolderViewFolder::onIdleUpdateFavorites(void* data) { LLFolderViewFolder* self = reinterpret_cast(data); + if (gDisconnected || !self) + { + return; + } + if (self->mFavoritesDirtyFlags == FAVORITE_CLEANUP) { // parent or child already processed the update, clean the callback diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 8aabdc5669..e956fc024e 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -343,9 +343,6 @@ F32 gLogoutMaxTime = LOGOUT_REQUEST_TIME; S32 gPendingMetricsUploads = 0; - -bool gDisconnected = false; - // Used to restore texture state after a mode switch LLFrameTimer gRestoreGLTimer; bool gRestoreGL = false; diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 6b0d3e0b27..e1119419af 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -417,8 +417,6 @@ extern S32 gPendingMetricsUploads; extern F32 gSimLastTime; extern F32 gSimFrames; -extern bool gDisconnected; - extern LLFrameTimer gRestoreGLTimer; extern bool gRestoreGL; extern bool gUseWireframe; -- cgit v1.3 From aad49bd41461269bc3294df73050a2dd4fc76fe1 Mon Sep 17 00:00:00 2001 From: Rye Date: Sun, 11 Jan 2026 13:09:02 -0500 Subject: Introduced Tracy instrumentation to app initialization --- indra/llfilesystem/lldiskcache.cpp | 2 ++ indra/llui/llnotifications.cpp | 7 ++++ indra/llui/llui.cpp | 1 + indra/llxml/llcontrol.cpp | 2 ++ indra/newview/llagent.cpp | 2 ++ indra/newview/llappcorehttp.cpp | 2 ++ indra/newview/llappviewer.cpp | 17 ++++++++- indra/newview/llinventorymodel.cpp | 70 +++++++++++++++++++++----------------- indra/newview/llmachineid.cpp | 2 ++ indra/newview/llmutelist.cpp | 4 +++ indra/newview/lltexturecache.cpp | 2 ++ indra/newview/llviewercontrol.cpp | 1 + indra/newview/llviewermenu.cpp | 4 +++ 13 files changed, 83 insertions(+), 33 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index e971e1885a..a19d263174 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -91,6 +91,8 @@ LLDiskCache::LLDiskCache(const std::string& cache_dir, // asset will have to be re-requested. void LLDiskCache::purge() { + LL_PROFILE_ZONE_SCOPED; + if (mEnableCacheDebugInfo) { LL_INFOS() << "Total dir size before purge is " << dirFileSize(sCacheDir) << LL_ENDL; diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 0ffe6cff5e..56475a2d8d 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1424,6 +1424,7 @@ LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelN // this function is called once at construction time, after the object is constructed. void LLNotifications::initSingleton() { + LL_PROFILE_ZONE_SCOPED; loadTemplates(); loadVisibilityRules(); createDefaultChannels(); @@ -1436,6 +1437,8 @@ void LLNotifications::cleanupSingleton() void LLNotifications::createDefaultChannels() { + LL_PROFILE_ZONE_SCOPED; + LL_INFOS("Notifications") << "Generating default notification channels" << LL_ENDL; // now construct the various channels AFTER loading the notifications, // because the history channel is going to rewrite the stored notifications file @@ -1578,6 +1581,8 @@ void addPathIfExists(const std::string& new_path, std::vector& path bool LLNotifications::loadTemplates() { + LL_PROFILE_ZONE_SCOPED; + LL_INFOS("Notifications") << "Reading notifications template" << LL_ENDL; // Passing findSkinnedFilenames(constraint=LLDir::ALL_SKINS) makes it // output all relevant pathnames instead of just the ones from the most @@ -1663,6 +1668,8 @@ bool LLNotifications::loadTemplates() bool LLNotifications::loadVisibilityRules() { + LL_PROFILE_ZONE_SCOPED; + const std::string xml_filename = "notification_visibility.xml"; // Note that here we're looking for the "en" version, the default // language, rather than the most localized version of this file. diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index 38d5720556..29eb887948 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -157,6 +157,7 @@ mWindow(NULL), // set later in startup mRootView(NULL), mHelpImpl(NULL) { + LL_PROFILE_ZONE_SCOPED; LLRender2D::createInstance(image_provider); LLSpellChecker::createInstance(); diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp index 5a83f8c541..34643d5f5c 100644 --- a/indra/llxml/llcontrol.cpp +++ b/indra/llxml/llcontrol.cpp @@ -761,6 +761,7 @@ void LLControlGroup::setUntypedValue(std::string_view name, const LLSD& val) // Returns number of controls loaded, so 0 if failure U32 LLControlGroup::loadFromFileLegacy(const std::string& filename, bool require_declaration, eControlType declare_as) { + LL_PROFILE_ZONE_SCOPED; std::string name; LLXmlTree xml_controls; @@ -995,6 +996,7 @@ U32 LLControlGroup::saveToFile(const std::string& filename, bool nondefault_only U32 LLControlGroup::loadFromFile(const std::string& filename, bool set_default_values, bool save_values, bool error_when_no_comment) { + LL_PROFILE_ZONE_SCOPED; LLSD settings; llifstream infile; infile.open(filename.c_str()); diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 0d7ad0a124..60af0cad05 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -4850,6 +4850,8 @@ const std::string& LLAgent::getTeleportStateName() const void LLAgent::parseTeleportMessages(const std::string& xml_filename) { + LL_PROFILE_ZONE_SCOPED; + LLXMLNodePtr root; bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index f3265afebd..ece18fb5cc 100644 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -144,6 +144,8 @@ LLAppCoreHttp::~LLAppCoreHttp() void LLAppCoreHttp::init() { + LL_PROFILE_ZONE_SCOPED; + LLCoreHttpUtil::setPropertyMethods( std::bind(&LLControlGroup::getBOOL, std::ref(gSavedSettings), std::placeholders::_1), std::bind(&LLControlGroup::declareBOOL, std::ref(gSavedSettings), std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, LLControlVariable::PERSIST_NONDFT)); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index e956fc024e..8d9fb32bf0 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -732,6 +732,8 @@ public: bool LLAppViewer::init() { + LL_PROFILE_ZONE_SCOPED; + setupErrorHandling(mSecondInstance); // @@ -933,6 +935,7 @@ bool LLAppViewer::init() // Early out from user choice. LL_WARNS("InitInfo") << "initHardwareTest() failed." << LL_ENDL; // quit immediately + LL_PROFILER_FRAME_END; return false; } LL_INFOS("InitInfo") << "Hardware test initialization done." << LL_ENDL ; @@ -951,6 +954,7 @@ bool LLAppViewer::init() OSMessageBox(msg.c_str(), LLStringUtil::null, OSMB_OK); LL_WARNS("InitInfo") << "Failed to init cache" << LL_ENDL; // quit immediately + LL_PROFILER_FRAME_END; return false; } LL_INFOS("InitInfo") << "Cache initialization is done." << LL_ENDL ; @@ -986,6 +990,7 @@ bool LLAppViewer::init() // Already handled with a MBVideoDrvErr LL_WARNS("InitInfo") << "gGLManager.mHasRequirements is false." << LL_ENDL; // quit immediately + LL_PROFILER_FRAME_END; return false; } @@ -999,6 +1004,7 @@ bool LLAppViewer::init() OSMessageBox(msg.c_str(), LLStringUtil::null, OSMB_OK); LL_WARNS("InitInfo") << "SSE2 is not supported" << LL_ENDL; // quit immediately + LL_PROFILER_FRAME_END; return false; } #endif @@ -1269,7 +1275,7 @@ bool LLAppViewer::init() gDirUtilp->deleteDirAndContents(gDirUtilp->getDumpLogsDirPath()); } #endif - + LL_PROFILER_FRAME_END; return true; } @@ -2191,6 +2197,8 @@ void LLAppViewer::initGeneralThread() bool LLAppViewer::initThreads() { + LL_PROFILE_ZONE_SCOPED; + static const bool enable_threads = true; LLImage::initClass(gSavedSettings.getBOOL("TextureNewByteRange"),gSavedSettings.getS32("TextureReverseByteRange")); @@ -3038,6 +3046,8 @@ bool LLAppViewer::initConfiguration() // keeps growing, necessitating a method all its own. void LLAppViewer::initStrings() { + LL_PROFILE_ZONE_SCOPED; + std::string strings_file = "strings.xml"; std::string strings_path_full = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, strings_file); if (strings_path_full.empty() || !LLFile::isfile(strings_path_full)) @@ -3127,6 +3137,7 @@ void LLAppViewer::sendOutOfDiskSpaceNotification() bool LLAppViewer::initWindow() { + LL_PROFILE_ZONE_SCOPED; LL_INFOS("AppInit") << "Initializing window..." << LL_ENDL; // store setting in a global for easy access and modification @@ -4365,6 +4376,7 @@ U32 LLAppViewer::getObjectCacheVersion() bool LLAppViewer::initCache() { + LL_PROFILE_ZONE_SCOPED; mPurgeCache = false; bool read_only = mSecondInstance; LLAppViewer::getTextureCache()->setReadOnly(read_only) ; @@ -4513,6 +4525,7 @@ void LLAppViewer::loadKeyBindings() // As per GHI #4498, remove old, stale CEF cache folders from previous sessions void LLAppViewer::purgeCefStaleCaches() { + LL_PROFILE_ZONE_SCOPED; // TODO: we really shouldn't use a hard coded name for the cache folder here... const std::string browser_parent_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "cef_cache"); if (LLFile::isdir(browser_parent_cache)) @@ -4720,6 +4733,8 @@ std::string get_name_cache_filename(const std::string &base_file, const std::str void LLAppViewer::loadNameCache() { + LL_PROFILE_ZONE_SCOPED; + // display names cache std::string filename = get_name_cache_filename("avatar_name_cache", "xml"); LL_INFOS("AvNameCache") << filename << LL_ENDL; diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 7056ed1ce4..e80c506bc0 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -3425,6 +3425,7 @@ bool LLInventoryModel::loadFromFile(const std::string& filename, LLSD inventory; if (!is_cache_obsolete) { + LL_PROFILE_ZONE_NAMED("inventory load from file - llsd parse"); LLPointer parser = new LLSDBinaryParser(); if (parser->parse(file, inventory, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE) @@ -3436,56 +3437,61 @@ bool LLInventoryModel::loadFromFile(const std::string& filename, if (!is_cache_obsolete) { - const LLSD& llsd_cats = inventory["categories"]; - if (llsd_cats.isArray()) { - LLSD::array_const_iterator iter = llsd_cats.beginArray(); - LLSD::array_const_iterator end = llsd_cats.endArray(); - for (; iter != end; ++iter) + LL_PROFILE_ZONE_NAMED("inventory load from file - categories"); + const LLSD& llsd_cats = inventory["categories"]; + if (llsd_cats.isArray()) { - LLPointer inv_cat = new LLViewerInventoryCategory(LLUUID::null); - if (inv_cat->importLLSDMap(*iter)) + LLSD::array_const_iterator iter = llsd_cats.beginArray(); + LLSD::array_const_iterator end = llsd_cats.endArray(); + for (; iter != end; ++iter) { - categories.push_back(inv_cat); + LLPointer inv_cat = new LLViewerInventoryCategory(LLUUID::null); + if (inv_cat->importLLSDMap(*iter)) + { + categories.push_back(inv_cat); + } } } } - const LLSD& llsd_items = inventory["items"]; - if (llsd_items.isArray()) { - LLSD::array_const_iterator iter = llsd_items.beginArray(); - LLSD::array_const_iterator end = llsd_items.endArray(); - for (; iter != end; ++iter) + LL_PROFILE_ZONE_NAMED("inventory load from file - categories"); + const LLSD& llsd_items = inventory["items"]; + if (llsd_items.isArray()) { - LLPointer inv_item = new LLViewerInventoryItem; - if (inv_item->fromLLSD(*iter)) + LLSD::array_const_iterator iter = llsd_items.beginArray(); + LLSD::array_const_iterator end = llsd_items.endArray(); + for (; iter != end; ++iter) { - if (inv_item->getUUID().isNull()) + LLPointer inv_item = new LLViewerInventoryItem; + if (inv_item->fromLLSD(*iter)) { - LL_DEBUGS(LOG_INV) << "Ignoring inventory with null item id: " - << inv_item->getName() << LL_ENDL; - } - else - { - if (inv_item->getType() == LLAssetType::AT_UNKNOWN) + if (inv_item->getUUID().isNull()) { - cats_to_update.insert(inv_item->getParentUUID()); + LL_DEBUGS(LOG_INV) << "Ignoring inventory with null item id: " << inv_item->getName() << LL_ENDL; } else { - items.push_back(inv_item); + if (inv_item->getType() == LLAssetType::AT_UNKNOWN) + { + cats_to_update.insert(inv_item->getParentUUID()); + } + else + { + items.push_back(inv_item); + } } } - } - // TODO(brad) - figure out how to reenable this without breaking everything else - // static constexpr U64 BATCH_SIZE = 512U; - // if ((++lines_count % BATCH_SIZE) == 0) - // { - // // SL-19968 - make sure message system code gets a chance to run every so often - // pump_idle_startup_network(); - // } + // TODO(brad) - figure out how to reenable this without breaking everything else + // static constexpr U64 BATCH_SIZE = 512U; + // if ((++lines_count % BATCH_SIZE) == 0) + // { + // // SL-19968 - make sure message system code gets a chance to run every so often + // pump_idle_startup_network(); + // } + } } } } diff --git a/indra/newview/llmachineid.cpp b/indra/newview/llmachineid.cpp index 51c38aba3a..0a90cf0699 100644 --- a/indra/newview/llmachineid.cpp +++ b/indra/newview/llmachineid.cpp @@ -398,6 +398,8 @@ bool getSerialNumber(unsigned char *unique_id, size_t len) S32 LLMachineID::init() { + LL_PROFILE_ZONE_SCOPED; + size_t len = sizeof(static_unique_id); memset(static_unique_id, 0, len); S32 ret_code = 0; diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index 9157e34833..fb6e8a62ff 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -582,6 +582,8 @@ std::vector LLMuteList::getMutes() const //----------------------------------------------------------------------------- bool LLMuteList::loadFromFile(const std::string& filename) { + LL_PROFILE_ZONE_SCOPED; + if(!filename.size()) { LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; @@ -962,6 +964,8 @@ bool LLRenderMuteList::saveToFile() bool LLRenderMuteList::loadFromFile() { + LL_PROFILE_ZONE_SCOPED; + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "render_mute_settings.txt"); LLFILE* fp = LLFile::fopen(filename, "rb"); if (!fp) diff --git a/indra/newview/lltexturecache.cpp b/indra/newview/lltexturecache.cpp index 1a7ce74ccc..2f8158f6f2 100644 --- a/indra/newview/lltexturecache.cpp +++ b/indra/newview/lltexturecache.cpp @@ -991,6 +991,8 @@ void LLTextureCache::setReadOnly(bool read_only) // Returns the unused amount of max_size if any S64 LLTextureCache::initCache(ELLPath location, S64 max_size, bool texture_cache_mismatch) { + LL_PROFILE_ZONE_SCOPED; + llassert_always(getPending() == 0) ; //should not start accessing the texture cache before initialized. S64 entries_size = (max_size * 36) / 100; //0.36 * max_size diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 8b4b508d7c..0c93b24751 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -813,6 +813,7 @@ void setting_setup_signal_listener(LLControlGroup& group, const std::string& set void settings_setup_listeners() { + LL_PROFILE_ZONE_SCOPED; setting_setup_signal_listener(gSavedSettings, "FirstPersonAvatarVisible", handleRenderAvatarMouselookChanged); setting_setup_signal_listener(gSavedSettings, "RenderFarClip", handleRenderFarClipChanged); setting_setup_signal_listener(gSavedSettings, "RenderTerrainScale", handleTerrainScaleChanged); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index f29760c824..5af6a41c11 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -473,6 +473,8 @@ void check_merchant_status(bool force) void init_menus() { + LL_PROFILE_ZONE_SCOPED; + // Initialize actions initialize_menus(); @@ -9689,6 +9691,8 @@ void initialize_spellcheck_menu() void initialize_menus() { + LL_PROFILE_ZONE_SCOPED; + // A parameterized event handler used as ctrl-8/9/0 zoom controls below. class LLZoomer : public view_listener_t { -- cgit v1.3 From 4dcd070ef310cec9c027fd29cc7c486674cc98c8 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 12 Jan 2026 02:32:19 -0500 Subject: Disable disk cache LRU purge on main thread during startup by default and instead rely on background thread purge --- indra/newview/app_settings/settings.xml | 11 +++++++++++ indra/newview/llappviewer.cpp | 13 ++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 5a1a1187a0..d81756f647 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -16563,5 +16563,16 @@ Value 2 + PurgeDiskCacheOnStartup + + Comment + Whether or not to LRU purge the disk cache during startup on main thread + Persist + 1 + Type + Boolean + Value + 0 + diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 8d9fb32bf0..e016d0e341 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4470,19 +4470,22 @@ bool LLAppViewer::initCache() if (mPurgeCache) { - LLSplashScreen::update(LLTrans::getString("StartupClearingCache")); - purgeCache(); + LLSplashScreen::update(LLTrans::getString("StartupClearingCache")); + purgeCache(); // clear the new C++ file system based cache LLDiskCache::getInstance()->clearCache(); - } - else + } + else if (gSavedSettings.getBOOL("PurgeDiskCacheOnStartup")) { // purge excessive files from the new file system based cache LLDiskCache::getInstance()->purge(); } + + // Start disk cache purge thread to + // purge excessive files from the file system based cache + LLAppViewer::getPurgeDiskCacheThread()->start(); } - LLAppViewer::getPurgeDiskCacheThread()->start(); LLSplashScreen::update(LLTrans::getString("StartupInitializingTextureCache")); -- cgit v1.3 From 6eab9e63f6b4ce3b94878b0a9aa82aca5237ae2e Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:49:38 +0200 Subject: #5084 Convert watchdog to a simpleton --- indra/llcommon/llwatchdog.cpp | 2 +- indra/llcommon/llwatchdog.h | 6 +++--- indra/newview/llappviewer.cpp | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp index 1bc1283d0b..d3242a6c96 100644 --- a/indra/llcommon/llwatchdog.cpp +++ b/indra/llcommon/llwatchdog.cpp @@ -87,7 +87,7 @@ void LLWatchdogEntry::start() void LLWatchdogEntry::stop() { // this can happen very late in the shutdown sequence - if (!LLWatchdog::wasDeleted()) + if (LLWatchdog::instanceExists()) { LLWatchdog::getInstance()->remove(this); } diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h index fded881bb8..2100a90879 100644 --- a/indra/llcommon/llwatchdog.h +++ b/indra/llcommon/llwatchdog.h @@ -83,12 +83,12 @@ private: }; class LLWatchdogTimerThread; // Defined in the cpp -class LLWatchdog : public LLSingleton +class LLWatchdog : public LLSimpleton { - LLSINGLETON(LLWatchdog); +public: + LLWatchdog(); ~LLWatchdog(); -public: // Add an entry to the watchdog. void add(LLWatchdogEntry* e); void remove(LLWatchdogEntry* e); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index bae9749772..20dba648fd 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1240,6 +1240,7 @@ bool LLAppViewer::init() // TODO: consider moving proxy initialization here or LLCopocedureManager after proxy initialization, may be implement // some other protection to make sure we don't use network before initializng proxy + LLWatchdog::createInstance(); /*----------------------------------------------------------------------*/ // nat 2016-06-29 moved the following here from the former mainLoop(). mMainloopTimeout = new LLWatchdogTimeout("mainloop"); -- cgit v1.3 From 19690db372cbce8cafe7252a54682948c6afa28c Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:32:23 +0200 Subject: #5084 Fix watchdog's simpleton init It's needed for initWindow() --- indra/newview/llappviewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 20dba648fd..4bca656108 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -961,6 +961,7 @@ bool LLAppViewer::init() // Initialize event recorder LLViewerEventRecorder::createInstance(); + LLWatchdog::createInstance(); // // Initialize the window @@ -1240,7 +1241,6 @@ bool LLAppViewer::init() // TODO: consider moving proxy initialization here or LLCopocedureManager after proxy initialization, may be implement // some other protection to make sure we don't use network before initializng proxy - LLWatchdog::createInstance(); /*----------------------------------------------------------------------*/ // nat 2016-06-29 moved the following here from the former mainLoop(). mMainloopTimeout = new LLWatchdogTimeout("mainloop"); -- cgit v1.3 From fdd7e88b8b6ee98b212bb4d31de6c9f650cdf07d Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:20:55 +0200 Subject: #5084 Improve watchdog's behavior --- indra/llcommon/llapp.h | 1 + indra/llcommon/llwatchdog.cpp | 68 +++++++++--- indra/llcommon/llwatchdog.h | 19 +++- indra/newview/llappviewer.cpp | 142 ++++++++++++++++++++----- indra/newview/llappviewer.h | 3 + indra/newview/llappviewerwin32.cpp | 50 ++++++++- indra/newview/llappviewerwin32.h | 1 + indra/newview/skins/default/xui/en/strings.xml | 3 + 8 files changed, 246 insertions(+), 41 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index ce09c566a9..fef7dc80b3 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -285,6 +285,7 @@ public: #ifdef LL_WINDOWS virtual bool reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { return false; } + virtual bool reportCustomToBugsplat(const std::string& desription) { return false; } #endif public: diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp index d3242a6c96..1622aeb180 100644 --- a/indra/llcommon/llwatchdog.cpp +++ b/indra/llcommon/llwatchdog.cpp @@ -173,6 +173,17 @@ void LLWatchdog::add(LLWatchdogEntry* e) { lockThread(); mSuspects.insert(e); + + if (!mFrozeList.empty()) + { + mFrozeList.erase(e); + if (mFrozeList.empty()) + { + // Clear error marker file if there is no frozen threads, + // viewer is responsive again. + mClearMarkerFnc(); + } + } unlockThread(); } @@ -183,7 +194,12 @@ void LLWatchdog::remove(LLWatchdogEntry* e) unlockThread(); } -void LLWatchdog::init(func_t set_error_state_callback) +void LLWatchdog::init( + create_marker_func_t error_state_callback, + clear_marker_func_t clear_marker_callback, + report_func_t report_callback, + notify_func_t notify_callback, + bool crash_on_freeze) { if (!mSuspectsAccessMutex && !mTimer) { @@ -196,7 +212,11 @@ void LLWatchdog::init(func_t set_error_state_callback) // start needs to use the mSuspectsAccessMutex mTimer->start(); } - mCreateMarkerFnc = set_error_state_callback; + mCreateMarkerFnc = error_state_callback; + mClearMarkerFnc = clear_marker_callback; + mCrashReportFnc = report_callback; + mNotifyFnc = notify_callback; + mCrashOnFreeze = crash_on_freeze; } void LLWatchdog::cleanup() @@ -251,21 +271,45 @@ void LLWatchdog::run() mTimer->stop(); } - // Sets error marker file - mCreateMarkerFnc(); - // Todo1: Warn user? - // Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'. std::string last_state = (*result)->getLastState(); - if (last_state.empty()) + std::string description = "Watchdog timer for thread " + (*result)->getThreadName() + " expired"; + if (!last_state.empty()) { - LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName() - << " expired; assuming viewer is hung and crashing" << LL_ENDL; + description += " with state: " + last_state; + } + description += "; assuming viewer is hung and crashing"; + + if (!mCrashOnFreeze) + { + // Sets watchdog marker file + mCreateMarkerFnc(false); + // If it's mainloop and it somehow recovers, it will re-add itself + mSuspects.erase(*result); + mFrozeList.insert(*result); + LL_WARNS() << description << LL_ENDL; } else { - LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName() - << " expired with state: " << last_state - << "; assuming viewer is hung and crashing" << LL_ENDL; + + if (!mCrashReportFnc(description)) + { + // Sets error marker file + mCreateMarkerFnc(true); + // If false is returned, then we failed to report the issue to bugsplat, + // instead, Notify user, then crash viewer. + // Todo: ask user if viewer should quit or wait? + mNotifyFnc(); + LL_ERRS() << description << LL_ENDL; + } + else + { + // Sets watchdog marker file + mCreateMarkerFnc(false); + // Already reported, don't report again. + // If it's mainloop and it somehow recovers, it will re-add itself + mSuspects.erase(result); + mFrozeList.insert(*result); + } } } } diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h index 2100a90879..f138fbccb0 100644 --- a/indra/llcommon/llwatchdog.h +++ b/indra/llcommon/llwatchdog.h @@ -93,8 +93,16 @@ public: void add(LLWatchdogEntry* e); void remove(LLWatchdogEntry* e); - typedef std::function func_t; - void init(func_t set_error_state_callback); + typedef std::function create_marker_func_t; + typedef std::function clear_marker_func_t; + typedef std::function report_func_t; + typedef std::function notify_func_t; + void init( + create_marker_func_t error_state_callback, + clear_marker_func_t clear_marker_callback, + report_func_t report_callback, + notify_func_t notify_callback, + bool crash_on_freeze); void run(); void cleanup(); @@ -105,14 +113,19 @@ private: typedef std::set SuspectsRegistry; SuspectsRegistry mSuspects; + SuspectsRegistry mFrozeList; LLMutex* mSuspectsAccessMutex; LLWatchdogTimerThread* mTimer; U64 mLastClockCount; + bool mCrashOnFreeze; // At the moment watchdog expects app to set markers in mCreateMarkerFnc, // but technically can be used to set any error states or do some cleanup // or show warnings. - func_t mCreateMarkerFnc; + create_marker_func_t mCreateMarkerFnc; + clear_marker_func_t mClearMarkerFnc; + report_func_t mCrashReportFnc; + notify_func_t mNotifyFnc; }; #endif // LL_LLTHREADWATCHDOG_H diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 4bca656108..288da8ead8 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -377,6 +377,7 @@ const std::string MARKER_FILE_NAME("SecondLife.exec_marker"); const std::string START_MARKER_FILE_NAME("SecondLife.start_marker"); const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker"); const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); +const std::string WATCHDOG_MARKER_FILE_NAME("SecondLife.watchdog_marker"); static std::string gLaunchFileOnQuit; // Used on Win32 for other apps to identify our window (eg, win_setup) @@ -3201,20 +3202,60 @@ bool LLAppViewer::initWindow() << " (setting = " << watchdog_enabled_setting << ")" << LL_ENDL; - if (use_watchdog) + // Watchdog reports to statistics via marker files, that is + // pointless without ability to write (!mSecondInstance) those files. + // If use_watchdog is set, watchdog also reports to bugspat. + if (use_watchdog || !mSecondInstance) { - LLWatchdog::getInstance()->init([]() - { - LLAppViewer* app = LLAppViewer::instance(); - if (app->logoutRequestSent()) + LLWatchdog::getInstance()->init( + [](bool final_marker) { - app->createErrorMarker(LAST_EXEC_LOGOUT_FROZE); - } - else + LLAppViewer* app = LLAppViewer::instance(); + // Without watchdog everything will be counted as + // either 'unknown' (no crash marker) or based of present crash marker + if (final_marker) + { + // watchdog is going to crash viewer, so crate a 'crash' marker + if (app->logoutRequestSent()) + { + app->createErrorMarker(LAST_EXEC_LOGOUT_FROZE); + } + else + { + app->createErrorMarker(LAST_EXEC_FROZE); + } + } + else + { + // not going to crash, just create a 'watchdog' marker + app->createWatchdogMarker(); + } + }, + []() { - app->createErrorMarker(LAST_EXEC_FROZE); - } - }); + LLAppViewer* app = LLAppViewer::instance(); + // in case process recovered from freeze, remove watchdog marker. + app->removeWatchdogMarker(); + }, + [](std::string &desc) + { +#if LL_WINDOWS && LL_BUGSPLAT + LLAppViewer* app = LLAppViewer::instance(); + app->writeDebugInfo(); + return app->reportCustomToBugsplat(desc); +#else + return false; +#endif + }, + []() + { + LLAppViewer* app = LLAppViewer::instance(); + app->sendLogoutRequest(); + // Might be better to ask user if user wants to terminate the app or wait. + OSMessageBox(LLTrans::getString("MBFreezeDetected"), LLTrans::getString("MBFatalError"), OSMB_OK); + }, + use_watchdog); + } LLNotificationsUI::LLNotificationManager::getInstance(); @@ -3997,13 +4038,8 @@ void LLAppViewer::processMarkerFiles() { // the file existed, is ours, and matched our version, so we can report on what it says LL_INFOS("MarkerFile") << "Exec marker '"<< mMarkerFileName << "' found; last exec crashed or froze" << LL_ENDL; -#if LL_WINDOWS && LL_BUGSPLAT - // bugsplat will set correct state in bugsplatSendLog - // Might be more accurate to rename this one into 'unknown' + // App terminated unexpectedly or froze, we don't know the cause yet. gLastExecEvent = LAST_EXEC_UNKNOWN; -#else - gLastExecEvent = LAST_EXEC_OTHER_CRASH; -#endif // LL_WINDOWS } else @@ -4056,23 +4092,29 @@ void LLAppViewer::processMarkerFiles() } LLAPRFile::remove(logout_marker_file); } - // and last refine based on whether or not a marker created during a non-llerr crash is found + // Refine based on whether or not a marker created during + // a crash is found or if wathdog caught a freeze. + // Bugsplat will set correct state in bugsplatSendLog. std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); + std::string watchdog_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) { S32 marker_code = getMarkerErrorCode(error_marker_file); if (marker_code >= 0) { - if (gLastExecEvent == LAST_EXEC_LOGOUT_FROZE) - { - gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; - LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; - } - else if (marker_code > 0 && marker_code < (S32)LAST_EXEC_COUNT) + if (marker_code > 0 && marker_code < (S32)LAST_EXEC_COUNT) { + // If we have a code, it takes precendence gLastExecEvent = (eLastExecEvent)marker_code; LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to " << gLastExecEvent << LL_ENDL; } + // if we have the marker, even without a code, it's a crash. + else if (gLastExecEvent == LAST_EXEC_LOGOUT_UNKNOWN + || gLastExecEvent == LAST_EXEC_LOGOUT_FROZE) + { + gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; + LL_INFOS("MarkerFile") << "Error marker '" << error_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; + } else { gLastExecEvent = LAST_EXEC_OTHER_CRASH; @@ -4084,6 +4126,33 @@ 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); + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + // If viewer crashed after a freeze was detected, + // crash still takes precendence. Just clear watchdog. + removeWatchdogMarker(); + } + } + else + { + // so only check watchdog marker if there is no error marker. + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + if (LAST_EXEC_UNKNOWN == gLastExecEvent + || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) + { + // watchdog marker gets created if we detect a freeze, + // so if viwer did not stop gracefully, and we know it wasn't a crash, + // we have no other info, check watchdog. + if (markerIsSameVersion(watchdog_marker_file)) + { + gLastExecEvent = LAST_EXEC_UNKNOWN == gLastExecEvent ? LAST_EXEC_FROZE : LAST_EXEC_LOGOUT_FROZE; + LL_INFOS("MarkerFile") << "Watchdog marker '" << watchdog_marker_file << "' found, setting LastExecEvent to FROZE" + << LL_ENDL; + } + } + removeWatchdogMarker(); + } } #if LL_DARWIN @@ -4128,6 +4197,7 @@ void LLAppViewer::removeMarkerFiles() { LL_WARNS("MarkerFile") << "logout marker '"<getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); + + LLAPRFile file; + file.open(error_marker, LL_APR_WB); + if (file.getFileHandle()) + { + recordMarkerVersion(file); + file.close(); + } + } +} +void LLAppViewer::removeWatchdogMarker() const +{ + if (!mSecondInstance) + { + std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); + LLFile::remove(error_marker_file); + } +} + void LLAppViewer::outOfMemorySoftQuit() { if (!mQuitRequested) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index e1119419af..71033b6d3f 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -257,6 +257,9 @@ public: void createErrorMarker(eLastExecEvent error_code) const; bool errorMarkerExists() const; + void createWatchdogMarker() const; + void removeWatchdogMarker() const; + // Attempt a 'soft' quit with disconnect and saving of settings/cache. // Intended to be thread safe. // Good chance of viewer crashing either way, but better than alternatives. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 0620b625d9..45c8c369ca 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -114,6 +114,7 @@ namespace // MiniDmpSender pointer. As things stand, though, we must define an // actual function and store the pointer statically. static MiniDmpSender *sBugSplatSender = nullptr; + static std::string sBugsplatDesriptionField; bool bugsplatSendLog(UINT nCode, LPVOID lpVal1, LPVOID lpVal2) { @@ -150,8 +151,21 @@ namespace WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "settings_per_account.xml"))); } - // LL_ERRS message, when there is one - sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + if (!sBugsplatDesriptionField.empty()) + { + // Can be set by watchdog or other code that detects a problem + // and wants to add some context to the crash report. + // Will be visible in the BugSplat web UI. + sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + // This type of crash is not nessesarily a crash, or final. + // Prepare for the next one. + sBugsplatDesriptionField.clear(); + } + else + { + // LL_ERRS message, when there is one + sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + } sBugSplatSender->setAttribute(WCSTR(L"OS"), WCSTR(LLOSInfo::instance().getOSStringSimple())); // In case we ever stop using email for this sBugSplatSender->setAttribute(WCSTR(L"AppState"), WCSTR(LLStartUp::getStartupStateString())); @@ -833,6 +847,38 @@ bool LLAppViewerWin32::reportCrashToBugsplat(void* pExcepInfo) return false; } +#if defined(LL_BUGSPLAT) +static int reportCustomToBugsplatFilter(EXCEPTION_POINTERS* pExcepInfo) +{ + if (sBugSplatSender) + { + sBugSplatSender->createReport(pExcepInfo); + } + return EXCEPTION_EXECUTE_HANDLER; +} +#endif + +bool LLAppViewerWin32::reportCustomToBugsplat(const std::string &description) +{ +#if defined(LL_BUGSPLAT) + if (sBugSplatSender) + { + sBugsplatDesriptionField = description; + + __try + { + // Generate a custom exception code + RaiseException(0xE0000001, 0, 0, NULL); + } + __except (reportCustomToBugsplatFilter(GetExceptionInformation())) + { + } + return true; + } +#endif // LL_BUGSPLAT + return false; +} + bool LLAppViewerWin32::initWindow() { // This is a workaround/hotfix for a change in Windows 11 24H2 (and possibly later) diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index 3fad53ec72..5abcca1ce9 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -44,6 +44,7 @@ public: bool cleanup() override; bool reportCrashToBugsplat(void* pExcepInfo) override; + bool reportCustomToBugsplat(const std::string& desription) override; protected: bool initWindow() override; // Override to initialize the viewer's window. diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 1860d38b0e..e8bd5cc51b 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3007,6 +3007,9 @@ If this message persists, restart your computer. [APP_NAME] appears to have frozen or crashed on the previous run. Would you like to send a crash report? + + [APP_NAME] appears to have frozen. If this issue occurs regularly, please contact support at https://support.secondlife.com. + Notification [APP_NAME] is unable to detect DirectX 9.0b or greater. -- cgit v1.3 From 46412a6bfcf232790b9a57931d3efafcbf758511 Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 7 Apr 2026 19:12:59 -0400 Subject: Release/26.1.1 (#5530) * Integrate Velopack installer and update framework * Add Velopack update support for macOS and VVM integration * Update Velopack version and dependencies * Improve Velopack packaging for macOS * #5346 Uninstall older non-velopack viewer (#5363) * #5335 Fix silent uninstall asking about registry * #5346 Uninstall older non-velopack viewer * Use runtime viewer exe name, handle Velopack URL * Velopack download failure diagnostic (#5520) * Velopack download failure diagnostic * Fix up velopack downloading updates. Handle updates internally then hand them off to velopack. (#5524) * More velopack changes. Should download updates properly now. * Don't include NSI files * Restore optional updates, refine viewer restart behavior. (#5527) * Add support for optional updates. * Don't restart the viewer after the update unless it was optional. * Setup UpdaterServiceSetting with velopack properly. * Refine the restart behavior a bit - readd the old "the viewer must update" UX. * If the update is still downloading, close should just reopen the downloading dialog. --------- Co-authored-by: Jonathan "Geenz" Goodman * Remove SLVersionChecker from the viewer with velopack. (#5528) * Remove SLVersionChecker updater integration * Ensure that the portable install has the correct version number. * Don't produce shortcuts with VPK - we do this with our post install. * Bump viewer version from 26.1.0 to 26.1.1 * Potential fix for uninstaller not being functional. * Fix for UpdaterServiceSetting being ignored. * Filter for release channel when generating shortcuts. * Add some more logging for icons on Windows builds. * More VPK logging. * Move velopack packaging in CI to the sign and package step. * Enable velopack downgrade and skip older updates * Move the version required checking into velopack's checks. * Potential fix for downgrade prompts. * Make sure our macOS flow mirrors Windows. * Make sure to use the dev version of the mac sign and package. * p#553 Only one of two uninstallers displayed * #5346 Don't force user to shutdown velopack build for NSIS uninstall * #5346 Ignore option for the uninstall dialog * #5346 Fix early exit crash * #5346 Properly reset version flag. * Add some autodetect logic on macOS. * p#564 Clear legacy links * p#553 Handle uninstall records * p#549 Permit testing release notes on a test build * p#564 Remake nsis to velopack update flow * p#564 Remake nsis to velopack update flow #2 * p#564 Fix incorrect value type * p#553 Clear velopack's own registry entry in favor of a custom one * #5346 Resolve duplicated window class name * Bump to 2.1.0 of sign and package. --------- Co-authored-by: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> --- .github/workflows/build.yaml | 60 +- .github/workflows/tag-release.yaml | 36 +- autobuild.xml | 50 + indra/cmake/CMakeLists.txt | 1 + indra/cmake/Velopack.cmake | 68 ++ indra/lib/python/indra/util/llmanifest.py | 3 +- indra/llcommon/workqueue.cpp | 18 +- indra/llcommon/workqueue.h | 6 + indra/newview/CMakeLists.txt | 15 + indra/newview/app_settings/settings.xml | 16 +- .../installers/windows/installer_template.nsi | 16 +- indra/newview/llappviewer.cpp | 90 +- indra/newview/llappviewer.h | 12 +- indra/newview/llappviewerwin32.cpp | 33 +- indra/newview/llappviewerwin32.h | 2 - indra/newview/lllogininstance.cpp | 81 +- indra/newview/lllogininstance.h | 6 - indra/newview/llpanellogin.cpp | 16 + indra/newview/llstartup.cpp | 89 +- indra/newview/llvelopack.cpp | 1201 ++++++++++++++++++++ indra/newview/llvelopack.h | 59 + indra/newview/llversioninfo.cpp | 2 +- indra/newview/llversioninfo.h | 4 +- indra/newview/llviewernetwork.cpp | 8 + indra/newview/llviewerstats.cpp | 6 +- indra/newview/llvvmquery.cpp | 189 +++ indra/newview/llvvmquery.h | 42 + .../newview/skins/default/xui/en/notifications.xml | 18 + indra/newview/tests/lllogininstance_test.cpp | 2 - indra/newview/viewer_manifest.py | 261 ++++- indra/viewer_components/login/lllogin.cpp | 62 - 31 files changed, 2190 insertions(+), 282 deletions(-) create mode 100644 indra/cmake/Velopack.cmake create mode 100644 indra/newview/llvelopack.cpp create mode 100644 indra/newview/llvelopack.h create mode 100644 indra/newview/llvvmquery.cpp create mode 100644 indra/newview/llvvmquery.h (limited to 'indra/newview/llappviewer.cpp') diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2101b9b5e8..84c05dacdf 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,6 +2,14 @@ name: Build on: workflow_dispatch: + inputs: + installer_type: + description: 'Windows installer type' + type: choice + options: + - velopack + - nsis + default: 'velopack' pull_request: push: branches: ["main", "release/*", "project/*"] @@ -53,6 +61,20 @@ jobs: relnotes: ${{ steps.which-branch.outputs.relnotes }} imagename: ${{ steps.build.outputs.imagename }} configuration: ${{ matrix.configuration }} + # Windows Velopack outputs (passed to sign-pkg-windows) + velopack_pack_id: ${{ steps.build.outputs.velopack_pack_id }} + velopack_pack_version: ${{ steps.build.outputs.velopack_pack_version }} + velopack_pack_title: ${{ steps.build.outputs.velopack_pack_title }} + velopack_main_exe: ${{ steps.build.outputs.velopack_main_exe }} + velopack_exclude: ${{ steps.build.outputs.velopack_exclude }} + velopack_icon: ${{ steps.build.outputs.velopack_icon }} + velopack_installer_base: ${{ steps.build.outputs.velopack_installer_base }} + # macOS Velopack outputs (passed to sign-pkg-mac) + velopack_mac_pack_id: ${{ steps.build.outputs.velopack_mac_pack_id }} + velopack_mac_pack_version: ${{ steps.build.outputs.velopack_mac_pack_version }} + velopack_mac_pack_title: ${{ steps.build.outputs.velopack_mac_pack_title }} + velopack_mac_main_exe: ${{ steps.build.outputs.velopack_mac_main_exe }} + velopack_mac_bundle_id: ${{ steps.build.outputs.velopack_mac_bundle_id }} env: AUTOBUILD_ADDRSIZE: 64 AUTOBUILD_BUILD_ID: ${{ github.run_id }} @@ -84,6 +106,8 @@ jobs: # Only set variants to the one configuration: don't let build.sh loop # over variants, let GitHub distribute variants over multiple hosts. variants: ${{ matrix.configuration }} + # Pass USE_VELOPACK to CMake when using Velopack installer (default) - Windows and macOS + autobuild_configure_parameters: ${{ (contains(matrix.runner, 'windows') || contains(matrix.runner, 'macos')) && (github.event.inputs.installer_type || 'velopack') == 'velopack' && '-- -DUSE_VELOPACK:BOOL=ON' || '' }} steps: - name: Checkout code uses: actions/checkout@v6 @@ -126,6 +150,17 @@ jobs: with: token: ${{ github.token }} + - name: Setup .NET for Velopack + if: (runner.os == 'Windows' || runner.os == 'macOS') && (github.event.inputs.installer_type || 'velopack') == 'velopack' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Install Velopack CLI + if: (runner.os == 'Windows' || runner.os == 'macOS') && (github.event.inputs.installer_type || 'velopack') == 'velopack' + shell: bash + run: dotnet tool install -g vpk + - name: Build id: build shell: bash @@ -310,13 +345,21 @@ jobs: steps: - name: Sign and package Windows viewer if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID - uses: secondlife/viewer-build-util/sign-pkg-windows@v2.0.4 + uses: secondlife/viewer-build-util/sign-pkg-windows@v2.1.0 with: vault_uri: "${{ env.AZURE_KEY_VAULT_URI }}" cert_name: "${{ env.AZURE_CERT_NAME }}" client_id: "${{ env.AZURE_CLIENT_ID }}" client_secret: "${{ env.AZURE_CLIENT_SECRET }}" tenant_id: "${{ env.AZURE_TENANT_ID }}" + installer_type: "${{ github.event.inputs.installer_type || 'velopack' }}" + velopack_pack_id: "${{ needs.build.outputs.velopack_pack_id }}" + velopack_pack_version: "${{ needs.build.outputs.velopack_pack_version }}" + velopack_pack_title: "${{ needs.build.outputs.velopack_pack_title }}" + velopack_main_exe: "${{ needs.build.outputs.velopack_main_exe }}" + velopack_exclude: "${{ needs.build.outputs.velopack_exclude }}" + velopack_icon: "${{ needs.build.outputs.velopack_icon }}" + velopack_installer_base: "${{ needs.build.outputs.velopack_installer_base }}" sign-and-package-mac: env: @@ -349,7 +392,7 @@ jobs: - name: Sign and package Mac viewer if: env.SIGNING_CERT_MACOS && env.SIGNING_CERT_MACOS_IDENTITY && env.SIGNING_CERT_MACOS_PASSWORD && steps.note-creds.outputs.note_user && steps.note-creds.outputs.note_pass && steps.note-creds.outputs.note_team - uses: secondlife/viewer-build-util/sign-pkg-mac@v2 + uses: secondlife/viewer-build-util/sign-pkg-mac@v2.1.0 with: channel: ${{ needs.build.outputs.viewer_channel }} imagename: ${{ needs.build.outputs.imagename }} @@ -359,6 +402,11 @@ jobs: note_user: ${{ steps.note-creds.outputs.note_user }} note_pass: ${{ steps.note-creds.outputs.note_pass }} note_team: ${{ steps.note-creds.outputs.note_team }} + velopack_pack_id: "${{ needs.build.outputs.velopack_mac_pack_id }}" + velopack_pack_version: "${{ needs.build.outputs.velopack_mac_pack_version }}" + velopack_pack_title: "${{ needs.build.outputs.velopack_mac_pack_title }}" + velopack_main_exe: "${{ needs.build.outputs.velopack_mac_main_exe }}" + velopack_bundle_id: "${{ needs.build.outputs.velopack_mac_bundle_id }}" post-windows-symbols: env: @@ -439,6 +487,10 @@ jobs: with: pattern: "*-metadata" + - uses: actions/download-artifact@v4 + with: + pattern: "*-releases" + - name: Rename metadata run: | cp Windows-metadata/autobuild-package.xml Windows-autobuild-package.xml @@ -464,12 +516,14 @@ jobs: generate_release_notes: true target_commitish: ${{ github.sha }} append_body: true - fail_on_unmatched_files: true + fail_on_unmatched_files: false files: | macOS-installer/*.dmg Windows-installer/*.exe *-autobuild-package.xml *-viewer_version.txt + Windows-releases/* + macOS-releases/* - name: post release URL run: | diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml index 2922065f99..0f826222a0 100644 --- a/.github/workflows/tag-release.yaml +++ b/.github/workflows/tag-release.yaml @@ -21,7 +21,9 @@ on: project: description: "Project Name (used for channel name in project builds, and tag name for all builds)" default: "hippo" - # TODO - add an input for selecting another sha to build other than head of branch + tag_override: + description: "Override the tag name (optional). If the tag already exists, a numeric suffix is appended." + required: false jobs: tag-release: @@ -34,7 +36,7 @@ jobs: NIGHTLY_DATE=$(date --rfc-3339=date) echo NIGHTLY_DATE=${NIGHTLY_DATE} >> ${GITHUB_ENV} echo TAG_ID="$(echo ${{ github.sha }} | cut -c1-8)-${{ inputs.project || '${NIGHTLY_DATE}' }}" >> ${GITHUB_ENV} - - name: Update Tag + - name: Create Tag uses: actions/github-script@v8 with: # use a real access token instead of GITHUB_TOKEN default. @@ -44,9 +46,27 @@ jobs: # this token will need to be renewed anually in January github-token: ${{ secrets.LL_TAG_RELEASE_TOKEN }} script: | - github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: "refs/tags/${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}", - sha: context.sha - }) + const override = `${{ inputs.tag_override }}`.trim(); + const baseTag = override || `${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}`; + + // Try the base tag first, then append -2, -3, etc. if it already exists + let tag = baseTag; + for (let attempt = 1; ; attempt++) { + try { + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/${tag}`, + sha: context.sha + }); + core.info(`Created tag: ${tag}`); + break; + } catch (e) { + if (e.status === 422 && attempt < 10) { + core.info(`Tag '${tag}' already exists, trying next suffix...`); + tag = `${baseTag}-${attempt + 1}`; + } else { + throw e; + } + } + } diff --git a/autobuild.xml b/autobuild.xml index 6e48cbf509..9945672bd2 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2914,6 +2914,56 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors description Voxelized Hierarchical Approximate Convex Decomposition + velopack + + platforms + + windows64 + + archive + + creds + github + hash + 91abbc360640b5b2e0a4c001a36ad411a9a42602 + hash_algorithm + sha1 + url + https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583560 + + name + windows64 + + darwin64 + + archive + + creds + github + hash + 05563a79bdeb83d66a72ac1e97587dc2a8f64511 + hash_algorithm + sha1 + url + https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583554 + + name + darwin64 + + + license + MIT + license_file + LICENSES/velopack.txt + copyright + Velopack Ltd. + version + 40232ef.23500976684 + name + velopack + description + Velopack C/C++ Library + package_description diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 2ba282bdb7..c10f6ec934 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -62,6 +62,7 @@ set(cmake_SOURCE_FILES UI.cmake UnixInstall.cmake Variables.cmake + Velopack.cmake VHACD.cmake ViewerMiscLibs.cmake VisualLeakDetector.cmake diff --git a/indra/cmake/Velopack.cmake b/indra/cmake/Velopack.cmake new file mode 100644 index 0000000000..a1dbe2cbe9 --- /dev/null +++ b/indra/cmake/Velopack.cmake @@ -0,0 +1,68 @@ +# -*- cmake -*- +# Velopack installer and update framework integration +# https://velopack.io/ + +include_guard() + +# USE_VELOPACK controls whether to use Velopack for installer packaging (instead of NSIS/DMG) +option(USE_VELOPACK "Use Velopack for installer packaging" OFF) + +if (WINDOWS) + include(Prebuilt) + use_prebuilt_binary(velopack) + + add_library(ll::velopack INTERFACE IMPORTED) + + target_include_directories(ll::velopack SYSTEM INTERFACE + ${LIBS_PREBUILT_DIR}/include/velopack + ) + + target_link_libraries(ll::velopack INTERFACE + ${ARCH_PREBUILT_DIRS_RELEASE}/velopack_libc.lib + ) + + # Windows system libraries required by Velopack + target_link_libraries(ll::velopack INTERFACE + winhttp + ole32 + shell32 + shlwapi + version + userenv + ws2_32 + bcrypt + ntdll + ) + + target_compile_definitions(ll::velopack INTERFACE LL_VELOPACK=1) + +elseif (DARWIN) + include(Prebuilt) + use_prebuilt_binary(velopack) + + add_library(ll::velopack INTERFACE IMPORTED) + + target_include_directories(ll::velopack SYSTEM INTERFACE + ${LIBS_PREBUILT_DIR}/include/velopack + ) + + target_link_libraries(ll::velopack INTERFACE + ${ARCH_PREBUILT_DIRS_RELEASE}/libvelopack_libc.a + ) + + # macOS system frameworks required by Velopack (Rust static library dependencies) + target_link_libraries(ll::velopack INTERFACE + "-framework Foundation" + "-framework Security" + "-framework SystemConfiguration" + "-framework AppKit" + "-framework CoreFoundation" + "-framework CoreServices" + "-framework IOKit" + "-liconv" + "-lresolv" + ) + + target_compile_definitions(ll::velopack INTERFACE LL_VELOPACK=1) + +endif() diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index 1bd65eb57d..0ad0b6b1a9 100755 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -157,7 +157,8 @@ BASE_ARGUMENTS=[ for use by a .bat file.""", default=None), dict(name='versionfile', - description="""The name of a file containing the full version number."""), + description="""The name of a file containing the full version number.""", + default=None), ] def usage(arguments, srctree=""): diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index 67c23358ed..fbd92d089e 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -38,7 +38,8 @@ LL::WorkQueueBase::WorkQueueBase(const std::string& name, bool auto_shutdown) { // Register for "LLApp" events so we can implicitly close() on viewer shutdown std::string listener_name = "WorkQueue:" + getKey(); - LLEventPumps::instance().obtain("LLApp").listen( + LLEventPumps* pump = LLEventPumps::getInstance(); + pump->obtain("LLApp").listen( listener_name, [this](const LLSD& stat) { @@ -54,14 +55,25 @@ LL::WorkQueueBase::WorkQueueBase(const std::string& name, bool auto_shutdown) // Store the listener name so we can unregister in the destructor mListenerName = listener_name; + mPumpHandle = pump->getHandle(); } } LL::WorkQueueBase::~WorkQueueBase() { - if (!mListenerName.empty() && !LLEventPumps::wasDeleted()) + if (!mListenerName.empty() && !mPumpHandle.isDead()) { - LLEventPumps::instance().obtain("LLApp").stopListening(mListenerName); + // Due to shutdown order issues, use handle, not a singleton + // and ignore fiber issue. + try + { + LLEventPumps* pump = mPumpHandle.get(); + pump->obtain("LLApp").stopListening(mListenerName); + } + catch (const boost::fibers::lock_error&) + { + // Likely mutex is down, ignore + } } } diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 573203a5b3..69f3286c1b 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -14,6 +14,7 @@ #include "llcoros.h" #include "llexception.h" +#include "llhandle.h" #include "llinstancetracker.h" #include "llinstancetrackersubclass.h" #include "threadsafeschedule.h" @@ -22,6 +23,9 @@ #include // std::function #include +class LLEventPumps; + + namespace LL { @@ -202,6 +206,8 @@ namespace LL // Name used for the LLApp event listener (empty if not registered) std::string mListenerName; + // Due to shutdown order issues, store by handle + LLHandle mPumpHandle; }; /***************************************************************************** diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 5818a900f5..03055bfbab 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -43,6 +43,7 @@ include(TinyEXR) include(ThreeJS) include(Tracy) include(UI) +include(Velopack) include(ViewerMiscLibs) include(ViewerManager) include(VisualLeakDetector) @@ -655,6 +656,7 @@ set(viewer_SOURCE_FILES llurllineeditorctrl.cpp llurlwhitelist.cpp llversioninfo.cpp + llvvmquery.cpp llviewchildren.cpp llviewerassetstats.cpp llviewerassetstorage.cpp @@ -1329,6 +1331,7 @@ set(viewer_HEADER_FILES llurllineeditorctrl.h llurlwhitelist.h llversioninfo.h + llvvmquery.h llviewchildren.h llviewerassetstats.h llviewerassetstorage.h @@ -1448,6 +1451,8 @@ if (DARWIN) LIST(APPEND viewer_SOURCE_FILES llappviewermacosx-objc.h) LIST(APPEND viewer_SOURCE_FILES llfilepicker_mac.mm) LIST(APPEND viewer_HEADER_FILES llfilepicker_mac.h) + LIST(APPEND viewer_SOURCE_FILES llvelopack.cpp) + LIST(APPEND viewer_HEADER_FILES llvelopack.h) set_source_files_properties( llappviewermacosx-objc.mm @@ -1510,16 +1515,19 @@ if (WINDOWS) list(APPEND viewer_SOURCE_FILES llappviewerwin32.cpp + llvelopack.cpp llwindebug.cpp ) set_source_files_properties( llappviewerwin32.cpp + llvelopack.cpp PROPERTIES COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" ) list(APPEND viewer_HEADER_FILES llappviewerwin32.h + llvelopack.h llwindebug.h ) @@ -1933,6 +1941,7 @@ if (WINDOWS) "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" + "--velopack=${USE_VELOPACK}" --build=${CMAKE_CURRENT_BINARY_DIR} --buildtype=$ "--channel=${VIEWER_CHANNEL}" @@ -2056,6 +2065,10 @@ if (USE_DISCORD) target_link_libraries(${VIEWER_BINARY_NAME} ll::discord_sdk ) endif () +if (TARGET ll::velopack) + target_link_libraries(${VIEWER_BINARY_NAME} ll::velopack ) +endif () + if( TARGET ll::intel_memops ) target_link_libraries(${VIEWER_BINARY_NAME} ll::intel_memops ) endif() @@ -2253,9 +2266,11 @@ if (DARWIN) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + --bundleid=${MACOSX_BUNDLE_GUI_IDENTIFIER} "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" + "--velopack=${USE_VELOPACK}" --build=${CMAKE_CURRENT_BINARY_DIR} --buildtype=$ "--channel=${VIEWER_CHANNEL}" diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index e86154ddc1..0863386bd7 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4248,7 +4248,17 @@ Value 0.0.0 - + PreviousInstallChecked + + Comment + Whether viewer checked previous install on the same channel for NSIS + Persist + 1 + Type + Boolean + Value + 0 + LimitDragDistance Comment @@ -13123,11 +13133,11 @@ UpdaterShowReleaseNotes Comment - Enables displaying of the Release notes in a web floater after update. + Enables displaying of the Release notes in a web floater after update. 0 - don't show, 1 - show, 2 - show even for test viewers Persist 1 Type - Boolean + S32 Value 1 diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi index ae40e8830f..07ed0d0824 100644 --- a/indra/newview/installers/windows/installer_template.nsi +++ b/indra/newview/installers/windows/installer_template.nsi @@ -939,21 +939,7 @@ Function .onInstSuccess Call CheckWindowsServPack # Warn if not on the latest SP before asking to launch. StrCmp $SKIP_AUTORUN "true" +2; - # Assumes SetOutPath $INSTDIR - # Run INSTEXE (our updater), passing VIEWER_EXE plus the command-line - # arguments built into our shortcuts. This gives the updater a chance - # to verify that the viewer we just installed is appropriate for the - # running system -- or, if not, to download and install a different - # viewer. For instance, if a user running 32-bit Windows installs a - # 64-bit viewer, it cannot run on this system. But since the updater - # is a 32-bit executable even in the 64-bit viewer package, the - # updater can detect the problem and adapt accordingly. - # Once everything is in order, the updater will run the specified - # viewer with the specified params. - # Quote the updater executable and the viewer executable because each - # must be a distinct command-line token, but DO NOT quote the language - # string because it must decompose into separate command-line tokens. - Exec '"$INSTDIR\$INSTEXE" precheck "$INSTDIR\$VIEWER_EXE" $SHORTCUT_LANG_PARAM' + Exec '"$INSTDIR\$VIEWER_EXE" $SHORTCUT_LANG_PARAM' # FunctionEnd diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 288da8ead8..ba9fbd7b08 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -98,6 +98,11 @@ #include "llurlmatch.h" #include "lltextutil.h" #include "lllogininstance.h" +#include "llvvmquery.h" + +#if LL_VELOPACK +#include "llvelopack.h" +#endif #include "llprogressview.h" #include "llvocache.h" #include "lldiskcache.h" @@ -380,9 +385,6 @@ const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); const std::string WATCHDOG_MARKER_FILE_NAME("SecondLife.watchdog_marker"); static std::string gLaunchFileOnQuit; -// Used on Win32 for other apps to identify our window (eg, win_setup) -const char* const VIEWER_WINDOW_CLASSNAME = "Second Life"; - //---------------------------------------------------------------------------- // List of entries from strings.xml to always replace @@ -654,7 +656,6 @@ LLAppViewer::LLAppViewer() mPurgeCacheOnExit(false), mPurgeUserDataOnExit(false), mSecondInstance(false), - mUpdaterNotFound(false), mSavedFinalSnapshot(false), mSavePerAccountSettings(false), // don't save settings on logout unless login succeeded. mQuitRequested(false), @@ -1117,68 +1118,17 @@ bool LLAppViewer::init() gGLActive = false; -#if LL_RELEASE_FOR_DOWNLOAD - // Skip updater if this is a non-interactive instance +//#if LL_RELEASE_FOR_DOWNLOAD + // Launch VVM update check if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive) { - LLProcess::Params updater; - updater.desc = "updater process"; - // Because it's the updater, it MUST persist beyond the lifespan of the - // viewer itself. - updater.autokill = false; - std::string updater_file; -#if LL_WINDOWS - updater_file = "SLVersionChecker.exe"; - updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file); -#elif LL_DARWIN - updater_file = "SLVersionChecker"; - updater.executable = gDirUtilp->add(gDirUtilp->getAppRODataDir(), "updater", updater_file); -#else - updater_file = "SLVersionChecker"; - updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file); -#endif - // add LEAP mode command-line argument to whichever of these we selected - updater.args.add("leap"); - // UpdaterServiceSettings - if (gSavedSettings.getBOOL("FirstLoginThisInstall")) - { - // Befor first login, treat this as 'manual' updates, - // updater won't install anything, but required updates - updater.args.add("0"); - } - else - { - updater.args.add(stringize(gSavedSettings.getU32("UpdaterServiceSetting"))); - } - // channel - updater.args.add(LLVersionInfo::instance().getChannel()); - // testok - updater.args.add(stringize(gSavedSettings.getBOOL("UpdaterWillingToTest"))); - // ForceAddressSize - updater.args.add(stringize(gSavedSettings.getU32("ForceAddressSize"))); - - try - { - // Run the updater. An exception from launching the updater should bother us. - LLLeap::create(updater, true); - mUpdaterNotFound = false; - } - catch (...) - { - LLUIString details = LLNotifications::instance().getGlobalString("LLLeapUpdaterFailure"); - details.setArg("[UPDATER_APP]", updater_file); - OSMessageBox( - details.getString(), - LLStringUtil::null, - OSMB_OK); - mUpdaterNotFound = true; - } + initVVMUpdateCheck(); } else { LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL; } -#endif //LL_RELEASE_FOR_DOWNLOAD +//#endif //LL_RELEASE_FOR_DOWNLOAD { // Iterate over --leap command-line options. But this is a bit tricky: if @@ -1716,6 +1666,16 @@ void LLAppViewer::flushLFSIO() bool LLAppViewer::cleanup() { +#if LL_VELOPACK + // Apply any pending Velopack update before shutdown + if (velopack_is_update_pending()) + { + LL_INFOS("AppInit") << "Applying pending Velopack update on shutdown..." << LL_ENDL; + velopack_apply_pending_update(velopack_should_restart_after_update()); + } + velopack_cleanup(); +#endif + //ditch LLVOAvatarSelf instance gAgentAvatarp = NULL; @@ -3160,7 +3120,7 @@ bool LLAppViewer::initWindow() LLViewerWindow::Params window_params; window_params .title(gWindowTitle) - .name(VIEWER_WINDOW_CLASSNAME) + .name(sWindowClass) .x(gSavedSettings.getS32("WindowX")) .y(gSavedSettings.getS32("WindowY")) .width(gSavedSettings.getU32("WindowWidth")) @@ -3325,16 +3285,6 @@ bool LLAppViewer::initWindow() return true; } -bool LLAppViewer::isUpdaterMissing() -{ - return mUpdaterNotFound; -} - -bool LLAppViewer::waitForUpdater() -{ - return !gSavedSettings.getBOOL("CmdLineSkipUpdater") && !mUpdaterNotFound && !gNonInteractive; -} - void LLAppViewer::writeDebugInfo(bool isStatic) { #if LL_WINDOWS && LL_BUGSPLAT diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 71033b6d3f..d76e5015e9 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -117,9 +117,6 @@ public: bool quitRequested() { return mQuitRequested; } bool logoutRequestSent() { return mLogoutRequestSent; } bool isSecondInstance() { return mSecondInstance; } - bool isUpdaterMissing(); // In use by tests - bool waitForUpdater(); - void writeDebugInfo(bool isStatic=true); void setServerReleaseNotesURL(const std::string& url) { mServerReleaseNotesURL = url; } @@ -290,6 +287,14 @@ protected: virtual void sendOutOfDiskSpaceNotification(); +protected: + + // NSIS relies on this to detect if viewer is up. + // NSIS's method is somewhat unreliable since window + // can close long before cleanup is done. + // sendURLToOtherInstance also relies on this to detect if viewer is up. + static constexpr const char* sWindowClass = "Second Life"; + private: bool doFrame(); @@ -330,7 +335,6 @@ private: static LLAppViewer* sInstance; bool mSecondInstance; // Is this a second instance of the app? - bool mUpdaterNotFound; // True when attempt to start updater failed std::string mMarkerFileName; LLAPRFile mMarkerFile; // A file created to indicate the app is running. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 45c8c369ca..8f11b219f5 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -72,6 +72,11 @@ #include #include +// Velopack installer and update framework +#if LL_VELOPACK +#include "llvelopack.h" +#endif + // Bugsplat (http://bugsplat.com) crash reporting tool #ifdef LL_BUGSPLAT #include "BugSplat.h" @@ -234,7 +239,6 @@ LONG WINAPI catchallCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/) return 0; } -const std::string LLAppViewerWin32::sWindowClass = "Second Life"; /* This function is used to print to the command line a text message @@ -438,6 +442,31 @@ int APIENTRY WINMAIN(HINSTANCE hInstance, PWSTR pCmdLine, int nCmdShow) { +#if LL_VELOPACK + // Velopack MUST be initialized first - it may handle install/uninstall + // commands and exit the process before we do anything else. + if (!velopack_initialize()) + { + // Velopack handled the invocation (install/uninstall hook) + + // Drop install related settings + gDirUtilp->initAppDirs("SecondLife"); + + std::string user_settings_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "settings.xml"); + LLControlGroup settings("global"); + if (settings.loadFromFile(user_settings_path)) + { + // If user reinstalls or updates, we want to recheck for nsis leftovers. + if (settings.controlExists("PreviousInstallChecked")) + { + settings.setBOOL("PreviousInstallChecked", false); + } + settings.saveToFile(user_settings_path, true); + } + return 0; + } +#endif + // Call Tracy first thing to have it allocate memory // https://github.com/wolfpld/tracy/issues/196 LL_PROFILER_FRAME_END; @@ -979,7 +1008,7 @@ bool LLAppViewerWin32::restoreErrorTrap() bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url) { wchar_t window_class[256]; /* Flawfinder: ignore */ // Assume max length < 255 chars. - mbstowcs(window_class, sWindowClass.c_str(), 255); + mbstowcs(window_class, sWindowClass, 255); window_class[255] = 0; // Use the class instead of the window name. HWND other_window = FindWindow(window_class, NULL); diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index 5abcca1ce9..b31fa49cb2 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -60,8 +60,6 @@ protected: std::string generateSerialNumber(); - static const std::string sWindowClass; - private: void disableWinErrorReporting(); diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index e9d68723d3..0358233637 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -277,11 +277,6 @@ void LLLoginInstance::constructAuthParams(LLPointer user_credentia mRequestData["params"] = request_params; mRequestData["options"] = requested_options; mRequestData["http_params"] = http_params; -#if LL_RELEASE_FOR_DOWNLOAD - mRequestData["wait_for_updater"] = LLAppViewer::instance()->waitForUpdater(); -#else - mRequestData["wait_for_updater"] = false; -#endif } bool LLLoginInstance::handleLoginEvent(const LLSD& event) @@ -316,13 +311,6 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) // Login has failed. // Figure out why and respond... LLSD response = event["data"]; - LLSD updater = response["updater"]; - - // Always provide a response to the updater, if in fact the updater - // contacted us, if in fact the ping contains a 'reply' key. Most code - // paths tell it not to proceed with updating. - ResponsePtr resp(std::make_shared - (LLSDMap("update", false), updater)); std::string reason_response = response["reason"].asString(); std::string message_response = response["message"].asString(); @@ -385,26 +373,15 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) } else if(reason_response == "update") { - // This can happen if the user clicked Login quickly, before we heard - // back from the Viewer Version Manager, but login failed because - // login.cgi is insisting on a required update. We were called with an - // event that bundles both the login.cgi 'response' and the - // synchronization event from the 'updater'. + // login.cgi rejected login and requires an update. Since Velopack + // handles updates now, the best we can do here is tell the user + // to download the update manually via the release notes URL. std::string login_version = response["message_args"]["VERSION"]; - std::string vvm_version = updater["VERSION"]; - std::string relnotes = updater["URL"]; LL_WARNS("LLLogin") << "Login failed because an update to version " << login_version << " is required." << LL_ENDL; - // vvm_version might be empty because we might not have gotten - // SLVersionChecker's LoginSync handshake. But if it IS populated, it - // should (!) be the same as the version we got from login.cgi. - if ((! vvm_version.empty()) && vvm_version != login_version) - { - LL_WARNS("LLLogin") << "VVM update version " << vvm_version - << " differs from login version " << login_version - << "; presenting VVM version to match release notes URL" - << LL_ENDL; - login_version = vvm_version; - } + + // Try to use the release notes URL from the VVM query if available, + // otherwise fall back to constructing one from the version. + std::string relnotes = LLVersionInfo::instance().getReleaseNotes(); if (relnotes.empty() || relnotes.find("://") == std::string::npos) { relnotes = LLTrans::getString("RELEASE_NOTES_BASE_URL"); @@ -420,32 +397,11 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) args["VERSION"] = login_version; args["URL"] = relnotes; - if (updater.isUndefined()) - { - // If the updater failed to shake hands, better advise the user to - // download the update him/herself. - LLNotificationsUtil::add( - "RequiredUpdate", - args, - updater, - boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); - } - else - { - // If we've heard from the updater that an update is required, - // then display the prompt that assures the user we'll take care - // of it. This is the one case in which we bind 'resp': - // instead of destroying our Response object (and thus sending a - // negative reply to the updater) as soon as we exit this - // function, bind our shared_ptr so it gets passed into - // syncWithUpdater. That ensures that the response is delayed - // until the user has responded to the notification. - LLNotificationsUtil::add( - "PauseForUpdate", - args, - updater, - boost::bind(&LLLoginInstance::syncWithUpdater, this, resp, _1, _2)); - } + LLNotificationsUtil::add( + "RequiredUpdate", + args, + LLSD(), + boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); } else if(reason_response == "mfa_challenge") { @@ -479,19 +435,6 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) } } -void LLLoginInstance::syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response) -{ - LL_INFOS("LLLogin") << "LLLoginInstance::syncWithUpdater" << LL_ENDL; - // 'resp' points to an instance of LLEventAPI::Response that will be - // destroyed as soon as we return and the notification response functor is - // unregistered. Modify it so that it tells the updater to go ahead and - // perform the update. Naturally, if we allowed the user a choice as to - // whether to proceed or not, this assignment would reflect the user's - // selection. - (*resp)["update"] = true; - attemptComplete(); -} - void LLLoginInstance::handleLoginDisallowed(const LLSD& notification, const LLSD& response) { attemptComplete(); diff --git a/indra/newview/lllogininstance.h b/indra/newview/lllogininstance.h index 54ce51720f..551ad92d33 100644 --- a/indra/newview/lllogininstance.h +++ b/indra/newview/lllogininstance.h @@ -28,8 +28,6 @@ #define LL_LLLOGININSTANCE_H #include "lleventdispatcher.h" -#include "lleventapi.h" -#include // std::shared_ptr #include "llsecapi.h" class LLLogin; class LLEventStream; @@ -72,10 +70,7 @@ public: void saveMFAHash(LLSD const& response); private: - typedef std::shared_ptr ResponsePtr; void constructAuthParams(LLPointer user_credentials); - void updateApp(bool mandatory, const std::string& message); - bool updateDialogCallback(const LLSD& notification, const LLSD& response); bool handleLoginEvent(const LLSD& event); void handleLoginFailure(const LLSD& event); @@ -83,7 +78,6 @@ private: void handleDisconnect(const LLSD& event); void handleIndeterminate(const LLSD& event); void handleLoginDisallowed(const LLSD& notification, const LLSD& response); - void syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response); bool handleTOSResponse(bool v, const std::string& key); void showMFAChallange(const std::string& message); diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 868e02f28b..0cfb3ec49a 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -37,6 +37,9 @@ #include "llappviewer.h" #include "llbutton.h" +#if LL_VELOPACK +#include "llvelopack.h" +#endif #include "llcheckboxctrl.h" #include "llcommandhandler.h" // for secondlife:///app/login/ #include "llcombobox.h" @@ -939,6 +942,19 @@ void LLPanelLogin::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent ev // static void LLPanelLogin::onClickConnect(bool commit_fields) { +#if LL_VELOPACK + // In theory, you should never be able to get here. + // If there's a required update, try as you might you're not supposed to actually close the downloading update dialog. + // But just in case... + if (velopack_is_required_update_in_progress()) + { + LLSD args; + args["VERSION"] = velopack_get_required_update_version(); + LLNotificationsUtil::add("DownloadingUpdate", args); + return; + } +#endif + if (sInstance && sInstance->mCallback && !sInstance->mAlertNotif) { if (commit_fields) diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 59d97943e3..c23b493ad0 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -29,6 +29,11 @@ #include "llappviewer.h" #include "llstartup.h" +#if LL_VELOPACK && LL_WINDOWS +#include "llvelopack.h" +#include +#endif + #if LL_WINDOWS # include // _spawnl() #else @@ -266,6 +271,7 @@ std::unique_ptr LLStartUp::sPhases(new LLViewerStats::P void login_show(); void login_callback(S32 option, void* userdata); +void uninstall_nsis_if_required(); void show_release_notes_if_required(); void show_first_run_dialog(); bool first_run_dialog_callback(const LLSD& notification, const LLSD& response); @@ -921,6 +927,7 @@ bool idle_startup() LL_DEBUGS("AppInit") << "PeekMessage processed" << LL_ENDL; #endif do_startup_frame(); + uninstall_nsis_if_required(); timeout.reset(); return false; } @@ -2605,6 +2612,67 @@ void release_notes_coro(const std::string url) LLWeb::loadURLInternal(url); } +/** +* Check if this is a fresh velopack install and +* if uninstallation of old viewer is needed. +*/ +void uninstall_nsis_if_required() +{ +#if LL_VELOPACK && LL_WINDOWS + bool checked_for_legacy_install = gSavedSettings.getBOOL("PreviousInstallChecked"); + if (checked_for_legacy_install) + { + return; + } + gSavedSettings.setBOOL("PreviousInstallChecked", true); + + LL_INFOS() << "Looking for previous NSIS installs" << LL_ENDL; + + S32 found_major = 0; + S32 found_minor = 0; + S32 found_patch = 0; + U64 found_build = 0; + + if (!get_nsis_version(found_major, found_minor, found_patch, found_build)) + { + return; + } + + LLVersionInfo* ver_inst = LLVersionInfo::getInstance(); + + if (found_major > ver_inst->getMajor()) + { + LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL; + return; + } + + if (found_major == ver_inst->getMajor() + && found_minor > ver_inst->getMinor()) + { + LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL; + return; + } + + if (found_major == ver_inst->getMajor() + && found_minor == ver_inst->getMinor() + && found_patch > ver_inst->getPatch()) + { + LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL; + return; + } + + // Assume that nsis is going to be something like x.x.x, while velopack is x.x.(x+1), + // so there is no point to check build. + LL_INFOS() << "Found NSIS install " << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL; + + clear_nsis_links(); + + LLSD args; + args["VERSION"] = llformat("%d.%d.%d", found_major, found_minor, found_patch); + LLNotificationsUtil::add("FoundLegacyNsisInstallation", args); +#endif +} + void validate_release_notes_coro(const std::string url) { LLVersionInfo& versionInfo(LLVersionInfo::instance()); @@ -2638,15 +2706,24 @@ void show_release_notes_if_required() // below. If viewer release notes stop working, might be because that // LLEventMailDrop got moved out of LLVersionInfo and hasn't yet been // instantiated. - if (!release_notes_shown && (LLVersionInfo::instance().getChannelAndVersion() != gLastRunVersion) - && LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER // don't show Release Notes for the test builds - && gSavedSettings.getBOOL("UpdaterShowReleaseNotes") - && !gSavedSettings.getBOOL("FirstLoginThisInstall")) + if (release_notes_shown + || LLVersionInfo::instance().getChannelAndVersion() == gLastRunVersion + || gSavedSettings.getBOOL("FirstLoginThisInstall")) // New users don't need to see release notes + { + return; + } + S32 mode = gSavedSettings.getS32("UpdaterShowReleaseNotes"); + if (mode == 0) + { + return; + } + if (mode == 2 // Show even for test builds + || LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER) // don't show Release Notes for the test builds + { #if LL_RELEASE_FOR_DOWNLOAD - if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") - && !LLAppViewer::instance()->isUpdaterMissing()) + if (!gSavedSettings.getBOOL("CmdLineSkipUpdater")) { // Instantiate a "relnotes" listener which assumes any arriving event // is the release notes URL string. Since "relnotes" is an diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp new file mode 100644 index 0000000000..28e989c4ba --- /dev/null +++ b/indra/newview/llvelopack.cpp @@ -0,0 +1,1201 @@ +/** + * @file llvelopack.cpp + * @brief Velopack installer and update framework integration + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#if LL_VELOPACK + +#include "llviewerprecompiledheaders.h" +#include "llvelopack.h" +#include "llstring.h" +#include "llcorehttputil.h" +#include "llversioninfo.h" + +#include +#include +#include +#include "llnotificationsutil.h" +#include "llviewercontrol.h" +#include "llappviewer.h" +#include "llcoros.h" + +#include "Velopack.h" + +#if LL_WINDOWS +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "shell32.lib") +#endif // LL_WINDOWS + +// Common state +static std::string sUpdateUrl; +static std::function sProgressCallback; +static vpkc_update_manager_t* sUpdateManager = nullptr; +static vpkc_update_info_t* sPendingUpdate = nullptr; // Downloaded, ready to apply +static vpkc_update_info_t* sPendingCheckInfo = nullptr; // Checked, awaiting user response +static vpkc_update_source_t* sUpdateSource = nullptr; +static LLNotificationPtr sDownloadingNotification; +static bool sRestartAfterUpdate = false; +static bool sIsRequired = false; // Is the pending check a required update? +static std::string sReleaseNotesUrl; +static std::string sTargetVersion; // Velopack's actual target version + +// Forward declarations +static void show_required_update_prompt(); +static void show_downloading_notification(const std::string& version); +static void ensure_update_manager(bool allow_downgrade); +static void velopack_download_pending_update(); +static std::unordered_map sAssetUrlMap; // basename -> original absolute URL + +// +// Custom update source helpers +// + +static std::string extract_basename(const std::string& url) +{ + // Strip query params / fragment + std::string path = url; + auto qpos = path.find('?'); + if (qpos != std::string::npos) path = path.substr(0, qpos); + auto fpos = path.find('#'); + if (fpos != std::string::npos) path = path.substr(0, fpos); + + auto spos = path.rfind('/'); + if (spos != std::string::npos && spos + 1 < path.size()) + return path.substr(spos + 1); + return path; +} + +static void rewrite_asset_urls(boost::json::value& jv) +{ + if (jv.is_object()) + { + auto& obj = jv.as_object(); + auto it = obj.find("FileName"); + if (it != obj.end() && it->value().is_string()) + { + std::string filename(it->value().as_string()); + if (filename.find("://") != std::string::npos) + { + std::string basename = extract_basename(filename); + sAssetUrlMap[basename] = filename; + it->value() = basename; + LL_DEBUGS("Velopack") << "Rewrote FileName: " << basename << LL_ENDL; + } + } + for (auto& kv : obj) + { + rewrite_asset_urls(kv.value()); + } + } + else if (jv.is_array()) + { + for (auto& elem : jv.as_array()) + { + rewrite_asset_urls(elem); + } + } +} + +static std::string rewrite_release_feed(const std::string& json_str) +{ + boost::json::value jv = boost::json::parse(json_str); + rewrite_asset_urls(jv); + return boost::json::serialize(jv); +} + +static std::string download_url_raw(const std::string& url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + auto httpAdapter = std::make_shared("VelopackSource", httpPolicy); + auto httpRequest = std::make_shared(); + auto httpOpts = std::make_shared(); + httpOpts->setFollowRedirects(true); + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts); + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("Velopack") << "HTTP request failed for " << url << ": " << status.toString() << LL_ENDL; + return {}; + } + + const LLSD::Binary& rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); + return std::string(rawBody.begin(), rawBody.end()); +} + +static bool download_url_to_file(const std::string& url, const std::string& local_path) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + auto httpAdapter = std::make_shared("VelopackDownload", httpPolicy); + auto httpRequest = std::make_shared(); + auto httpOpts = std::make_shared(); + httpOpts->setFollowRedirects(true); + httpOpts->setTransferTimeout(1200); + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts); + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("Velopack") << "Download failed for " << url << ": " << status.toString() << LL_ENDL; + return false; + } + + const LLSD::Binary& rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); + llofstream outFile(local_path, std::ios::binary | std::ios::trunc); + if (!outFile.is_open()) + { + LL_WARNS("Velopack") << "Failed to open file for writing: " << local_path << LL_ENDL; + return false; + } + outFile.write(reinterpret_cast(rawBody.data()), rawBody.size()); + outFile.close(); + return true; +} + +// +// Custom source callbacks +// + +static char* custom_get_release_feed(void* user_data, const char* releases_name) +{ + std::string base = sUpdateUrl; + if (!base.empty() && base.back() == '/') + base.pop_back(); + std::string url = base + "/" + releases_name; + LL_INFOS("Velopack") << "Fetching release feed: " << url << LL_ENDL; + + std::string json_str = download_url_raw(url); + if (json_str.empty()) + { + return nullptr; + } + + try + { + std::string rewritten = rewrite_release_feed(json_str); + char* result = static_cast(malloc(rewritten.size() + 1)); + if (result) + { + memcpy(result, rewritten.c_str(), rewritten.size() + 1); + } + return result; + } + catch (const std::exception& e) + { + LL_WARNS("Velopack") << "Failed to parse/rewrite release feed: " << e.what() << LL_ENDL; + // Return original unmodified feed as fallback + char* result = static_cast(malloc(json_str.size() + 1)); + if (result) + { + memcpy(result, json_str.c_str(), json_str.size() + 1); + } + return result; + } +} + +static void custom_free_release_feed(void* user_data, char* feed) +{ + free(feed); +} + +static std::string sPreDownloadedAssetPath; + +static bool custom_download_asset(void* user_data, + const vpkc_asset_t* asset, + const char* local_path, + size_t progress_callback_id) +{ + // The asset has already been downloaded at the coroutine level (before vpkc_download_updates). + // This callback just copies the pre-downloaded file to where Velopack expects it. + // We cannot use getRawAndSuspend here — coroutine context is lost through the Rust FFI boundary. + if (sPreDownloadedAssetPath.empty()) + { + LL_WARNS("Velopack") << "No pre-downloaded asset available" << LL_ENDL; + return false; + } + + std::string filename = asset->FileName ? asset->FileName : ""; + LL_INFOS("Velopack") << "Download asset callback: filename=" << filename + << " local_path=" << local_path + << " size=" << asset->Size << LL_ENDL; + vpkc_source_report_progress(progress_callback_id, 0); + + std::ifstream src(sPreDownloadedAssetPath, std::ios::binary); + llofstream dst(local_path, std::ios::binary | std::ios::trunc); + if (!src.is_open() || !dst.is_open()) + { + LL_WARNS("Velopack") << "Failed to open files for copy" << LL_ENDL; + return false; + } + + dst << src.rdbuf(); + dst.close(); + src.close(); + + vpkc_source_report_progress(progress_callback_id, 100); + LL_INFOS("Velopack") << "Asset copy complete" << LL_ENDL; + return true; +} + +// +// Platform-specific helpers and hooks +// + +#if LL_WINDOWS + +static const wchar_t* PROTOCOL_SECONDLIFE = L"secondlife"; +static const wchar_t* PROTOCOL_GRID_INFO = L"x-grid-location-info"; +static std::wstring get_viewer_exe_name() +{ + return ll_convert(gDirUtilp->getExecutableFilename()); +} + +static std::wstring get_install_dir() +{ + wchar_t path[MAX_PATH]; + GetModuleFileNameW(NULL, path, MAX_PATH); + PathRemoveFileSpecW(path); + return path; +} + +static std::wstring get_app_name() +{ + // Match viewer_manifest.py app_name() logic: release channel uses "Viewer" + // suffix instead of "Release" for display purposes (shortcuts, uninstall, etc.) + std::wstring channel = LL_TO_WSTRING(LL_VIEWER_CHANNEL); + std::wstring release_suffix = L" Release"; + if (channel.size() >= release_suffix.size() && + channel.compare(channel.size() - release_suffix.size(), release_suffix.size(), release_suffix) == 0) + { + channel.replace(channel.size() - release_suffix.size(), release_suffix.size(), L" Viewer"); + } + return channel; +} + +static std::wstring get_app_name_oneword() +{ + std::wstring name = get_app_name(); + name.erase(std::remove(name.begin(), name.end(), L' '), name.end()); + return name; +} + +static std::wstring get_start_menu_path() +{ + wchar_t path[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, 0, path))) + { + return path; + } + return L""; +} + +static std::wstring get_desktop_path() +{ + wchar_t path[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, path))) + { + return path; + } + return L""; +} + +static bool create_shortcut(const std::wstring& shortcut_path, + const std::wstring& target_path, + const std::wstring& arguments, + const std::wstring& description, + const std::wstring& icon_path) +{ + HRESULT hr = CoInitialize(NULL); + if (FAILED(hr)) return false; + + IShellLinkW* shell_link = nullptr; + hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_IShellLinkW, (void**)&shell_link); + if (SUCCEEDED(hr)) + { + shell_link->SetPath(target_path.c_str()); + shell_link->SetArguments(arguments.c_str()); + shell_link->SetDescription(description.c_str()); + shell_link->SetIconLocation(icon_path.c_str(), 0); + + wchar_t work_dir[MAX_PATH]; + wcscpy_s(work_dir, target_path.c_str()); + PathRemoveFileSpecW(work_dir); + shell_link->SetWorkingDirectory(work_dir); + + IPersistFile* persist_file = nullptr; + hr = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); + if (SUCCEEDED(hr)) + { + hr = persist_file->Save(shortcut_path.c_str(), TRUE); + persist_file->Release(); + } + shell_link->Release(); + } + + CoUninitialize(); + return SUCCEEDED(hr); +} + +static void register_protocol_handler(const std::wstring& protocol, + const std::wstring& description, + const std::wstring& exe_path) +{ + std::wstring key_path = L"SOFTWARE\\Classes\\" + protocol; + HKEY hkey; + + if (RegCreateKeyExW(HKEY_CURRENT_USER, key_path.c_str(), 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL) == ERROR_SUCCESS) + { + RegSetValueExW(hkey, NULL, 0, REG_SZ, + (BYTE*)description.c_str(), (DWORD)((description.size() + 1) * sizeof(wchar_t))); + RegSetValueExW(hkey, L"URL Protocol", 0, REG_SZ, (BYTE*)L"", sizeof(wchar_t)); + RegCloseKey(hkey); + } + + std::wstring icon_key_path = key_path + L"\\DefaultIcon"; + if (RegCreateKeyExW(HKEY_CURRENT_USER, icon_key_path.c_str(), 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL) == ERROR_SUCCESS) + { + std::wstring icon_value = L"\"" + exe_path + L"\""; + RegSetValueExW(hkey, NULL, 0, REG_SZ, + (BYTE*)icon_value.c_str(), (DWORD)((icon_value.size() + 1) * sizeof(wchar_t))); + RegCloseKey(hkey); + } + + std::wstring cmd_key_path = key_path + L"\\shell\\open\\command"; + if (RegCreateKeyExW(HKEY_CURRENT_USER, cmd_key_path.c_str(), 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL) == ERROR_SUCCESS) + { + std::wstring cmd_value = L"\"" + exe_path + L"\" -url \"%1\""; + RegSetValueExW(hkey, NULL, 0, REG_EXPAND_SZ, + (BYTE*)cmd_value.c_str(), (DWORD)((cmd_value.size() + 1) * sizeof(wchar_t))); + RegCloseKey(hkey); + } +} + +void clear_nsis_links() +{ + wchar_t path[MAX_PATH]; + + // 1. The 'start' shortcuts set by nsis would be global, like app shortcut: + // C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Second Life Viewer\Second Life Viewer.lnk + // But it isn't just one link, it's a whole directory that needs to be removed. + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_COMMON_PROGRAMS, NULL, 0, path))) + { + std::wstring start_menu_path = path; + std::wstring folder_path = start_menu_path + L"\\" + get_app_name(); + + std::error_code ec; + std::filesystem::path dir(folder_path); + if (std::filesystem::exists(dir, ec)) + { + std::filesystem::remove_all(dir, ec); + if (ec) + { + LL_WARNS("Velopack") << "Failed to remove NSIS start menu directory: " + << ll_convert_wide_to_string(folder_path) << LL_ENDL; + } + } + } + + // 2. Desktop link, also a global one. + // C:\Users\Public\Desktop + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, NULL, 0, path))) + { + std::wstring desktop_path = path; + std::wstring shortcut_path = desktop_path + L"\\" + get_app_name() + L".lnk"; + if (!DeleteFileW(shortcut_path.c_str())) + { + DWORD error = GetLastError(); + if (error != ERROR_FILE_NOT_FOUND) + { + LL_WARNS("Velopack") << "Failed to delete NSIS desktop shortcut: " + << ll_convert_wide_to_string(shortcut_path) + << " (error: " << error << ")" << LL_ENDL; + } + } + } +} + +static void parse_version(const wchar_t* version_str, int& major, int& minor, int& patch, uint64_t& build) +{ + major = minor = patch = 0; + build = 0; + if (!version_str) return; + // Use swscanf for wide strings + swscanf(version_str, L"%d.%d.%d.%llu", &major, &minor, &patch, &build); +} + +bool get_nsis_version( + int& nsis_major, + int& nsis_minor, + int& nsis_patch, + uint64_t& nsis_build) +{ + // Test for presence of NSIS viewer registration, then + // attempt to read uninstall info + std::wstring app_name_oneword = get_app_name_oneword(); + std::wstring uninstall_key_path = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + app_name_oneword; + HKEY hkey; + LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, uninstall_key_path.c_str(), 0, KEY_READ, &hkey); + if (result != ERROR_SUCCESS) + { + return false; + } + + // Read DisplayVersion + wchar_t version_buf[64] = { 0 }; + DWORD version_buf_size = sizeof(version_buf); + DWORD type = 0; + LONG ver_rv = RegGetValueW(hkey, nullptr, L"DisplayVersion", RRF_RT_REG_SZ, &type, version_buf, &version_buf_size); + + if (ver_rv != ERROR_SUCCESS) + { + RegCloseKey(hkey); + return false; + } + + parse_version(version_buf, nsis_major, nsis_minor, nsis_patch, nsis_build); + + // Make sure it actually exists and not a dead entry. + wchar_t path_buffer[MAX_PATH] = { 0 }; + DWORD path_buf_size = sizeof(path_buffer); + LONG rv = RegGetValueW(hkey, nullptr, L"UninstallString", RRF_RT_REG_SZ, &type, path_buffer, &path_buf_size); + RegCloseKey(hkey); + if (rv != ERROR_SUCCESS) + { + return false; + } + size_t len = wcslen(path_buffer); + if (len > 0) + { + if (path_buffer[0] == L'\"') + { + // Likely to contain leading " + memmove(path_buffer, path_buffer + 1, len * sizeof(wchar_t)); + } + wchar_t* pos = wcsstr(path_buffer, L"uninst.exe"); + if (pos) + { + // Likely to contain trailing " + pos[wcslen(L"uninst.exe")] = L'\0'; + } + } + std::error_code ec; + std::filesystem::path path(path_buffer); + if (!std::filesystem::exists(path, ec)) + { + return false; + } + + // Todo: check codesigning? + + return true; +} + +static void unregister_protocol_handler(const std::wstring& protocol) +{ + std::wstring key_path = L"SOFTWARE\\Classes\\" + protocol; + RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str()); +} + +static void register_uninstall_info(const std::wstring& install_dir, + const std::wstring& app_name, + const std::wstring& version) +{ + std::wstring app_name_oneword = get_app_name_oneword(); + // Clears velopack's recently created 'uninstall' registry entry. + // We are going to use a custom one. + // Note that velopack doesn't know about our custom entry. + std::wstring key_path = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + app_name_oneword; + RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str()); + // Use a unique key name to avoid conflicts with any existing NSIS-based uninstall info, + // which can cause only one of the two entries to show up in the Add/Remove Programs list. + // The UI will show DisplayName, so the key name itself is not important to be user-friendly. + key_path = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Vlpk" + app_name_oneword; + HKEY hkey; + + if (RegCreateKeyExW(HKEY_CURRENT_USER, key_path.c_str(), 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL) == ERROR_SUCCESS) + { + std::wstring exe_path = install_dir + L"\\" + get_viewer_exe_name(); + // Update.exe lives one level above the current\ directory where the viewer exe runs + std::filesystem::path update_exe = std::filesystem::path(install_dir).parent_path() / L"Update.exe"; + std::wstring uninstall_cmd = L"\"" + update_exe.wstring() + L"\" --uninstall"; + + RegSetValueExW(hkey, L"DisplayName", 0, REG_SZ, + (BYTE*)app_name.c_str(), (DWORD)((app_name.size() + 1) * sizeof(wchar_t))); + RegSetValueExW(hkey, L"DisplayVersion", 0, REG_SZ, + (BYTE*)version.c_str(), (DWORD)((version.size() + 1) * sizeof(wchar_t))); + RegSetValueExW(hkey, L"Publisher", 0, REG_SZ, + (BYTE*)L"Linden Research, Inc.", 44); + RegSetValueExW(hkey, L"UninstallString", 0, REG_SZ, + (BYTE*)uninstall_cmd.c_str(), (DWORD)((uninstall_cmd.size() + 1) * sizeof(wchar_t))); + RegSetValueExW(hkey, L"DisplayIcon", 0, REG_SZ, + (BYTE*)exe_path.c_str(), (DWORD)((exe_path.size() + 1) * sizeof(wchar_t))); + + std::wstring link_url = L"https://support.secondlife.com/contact-support/"; + RegSetValueExW(hkey, L"HelpLink", 0, REG_SZ, + (BYTE*)link_url.c_str(), (DWORD)((link_url.size() + 1) * sizeof(wchar_t))); + + link_url = L"https://secondlife.com/whatis/"; + RegSetValueExW(hkey, L"URLInfoAbout", 0, REG_SZ, + (BYTE*)link_url.c_str(), (DWORD)((link_url.size() + 1) * sizeof(wchar_t))); + + link_url = L"https://secondlife.com/support/downloads/"; + RegSetValueExW(hkey, L"URLUpdateInfo", 0, REG_SZ, + (BYTE*)link_url.c_str(), (DWORD)((link_url.size() + 1) * sizeof(wchar_t))); + + DWORD no_modify = 1; + RegSetValueExW(hkey, L"NoModify", 0, REG_DWORD, (BYTE*)&no_modify, sizeof(DWORD)); + RegSetValueExW(hkey, L"NoRepair", 0, REG_DWORD, (BYTE*)&no_modify, sizeof(DWORD)); + + // Format YYYYMMDD + wchar_t dateStr[9]; + time_t t = time(NULL); + struct tm tm; + localtime_s(&tm, &t); + wcsftime(dateStr, 9, L"%Y%m%d", &tm); + RegSetValueExW(hkey, L"InstallDate", 0, REG_SZ, (BYTE*)dateStr, (DWORD)((wcslen(dateStr) + 1) * sizeof(wchar_t))); // Let Windows fill in the install date + + // 800 MB, inaccurate, but for a rough idea. + // We can check folder size here, but it would take time and + // information is of low importance. + DWORD estimated_size = 800000; + RegSetValueExW(hkey, L"EstimatedSize", 0, REG_DWORD, (BYTE*)&estimated_size, sizeof(DWORD)); + + RegCloseKey(hkey); + } +} + +static void unregister_uninstall_info() +{ + std::wstring app_name_oneword = get_app_name_oneword(); + std::wstring key_path = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Vlpk" + app_name_oneword; + RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str()); +} + +static void create_shortcuts(const std::wstring& install_dir, const std::wstring& app_name) +{ + std::wstring exe_path = install_dir + L"\\" + get_viewer_exe_name(); + std::wstring start_menu_dir = get_start_menu_path() + L"\\" + app_name; + std::wstring desktop_path = get_desktop_path(); + + CreateDirectoryW(start_menu_dir.c_str(), NULL); + + create_shortcut(start_menu_dir + L"\\" + app_name + L".lnk", + exe_path, L"", app_name, exe_path); + + create_shortcut(desktop_path + L"\\" + app_name + L".lnk", + exe_path, L"", app_name, exe_path); +} + +static void remove_shortcuts(const std::wstring& app_name) +{ + std::wstring start_menu_dir = get_start_menu_path() + L"\\" + app_name; + std::wstring desktop_path = get_desktop_path(); + + DeleteFileW((start_menu_dir + L"\\" + app_name + L".lnk").c_str()); + RemoveDirectoryW(start_menu_dir.c_str()); + DeleteFileW((desktop_path + L"\\" + app_name + L".lnk").c_str()); +} + +static void on_first_run(void* p_user_data, const char* app_version) +{ + // Velopack first executes 'after install' hook, then writes registry, + // then executes 'on first run' hook. + // As we need to clear velopack's 'uninstall' registry entry and use + // our own, clean it here instead of on_after_install. + + std::wstring install_dir = get_install_dir(); + std::wstring app_name = get_app_name(); + + int len = MultiByteToWideChar(CP_UTF8, 0, app_version, -1, NULL, 0); + std::wstring version(len, 0); + MultiByteToWideChar(CP_UTF8, 0, app_version, -1, &version[0], len); + + register_uninstall_info(install_dir, app_name, version); +} + +static void on_after_install(void* user_data, const char* app_version) +{ + std::wstring install_dir = get_install_dir(); + std::wstring app_name = get_app_name(); + std::wstring exe_path = install_dir + L"\\" + get_viewer_exe_name(); + + register_protocol_handler(PROTOCOL_SECONDLIFE, L"URL:Second Life", exe_path); + register_protocol_handler(PROTOCOL_GRID_INFO, L"URL:Second Life", exe_path); + create_shortcuts(install_dir, app_name); +} + +static void on_before_uninstall(void* user_data, const char* app_version) +{ + std::wstring app_name = get_app_name(); + + unregister_protocol_handler(PROTOCOL_SECONDLIFE); + unregister_protocol_handler(PROTOCOL_GRID_INFO); + unregister_uninstall_info(); + remove_shortcuts(app_name); +} + +static void on_log_message(void* user_data, const char* level, const char* message) +{ + OutputDebugStringA("[Velopack] "); + OutputDebugStringA(level); + OutputDebugStringA(": "); + OutputDebugStringA(message); + OutputDebugStringA("\n"); +} + +#elif LL_DARWIN + +// macOS-specific hooks +// TODO: Implement protocol handler registration via Launch Services +// TODO: Implement app bundle management + +static void on_first_run(void* user_data, const char* app_version) +{ +} + +static void on_after_install(void* user_data, const char* app_version) +{ + // macOS handles protocol registration via Info.plist CFBundleURLTypes + // No additional registration needed at runtime + LL_INFOS("Velopack") << "macOS post-install hook called for version: " << app_version << LL_ENDL; +} + +static void on_before_uninstall(void* user_data, const char* app_version) +{ + LL_INFOS("Velopack") << "macOS pre-uninstall hook called for version: " << app_version << LL_ENDL; +} + +static void on_log_message(void* user_data, const char* level, const char* message) +{ + LL_INFOS("Velopack") << "[" << level << "] " << message << LL_ENDL; +} + +#endif // LL_WINDOWS / LL_DARWIN + +// +// Common progress callback +// + +static void on_progress(void* user_data, size_t progress) +{ + if (sProgressCallback) + { + sProgressCallback(static_cast(progress)); + } +} + +static void on_vpk_log(void* p_user_data, + const char* psz_level, + const char* psz_message) +{ + LL_DEBUGS("Velopack") << ll_safe_string(psz_message) << LL_ENDL; +} + +// +// Version comparison helper +// + +// Compare running version against a VVM version string "major.minor.patch.build". +// Returns -1 if running < vvm, 0 if equal, 1 if running > vvm. +static int compare_running_version(const std::string& vvm_version) +{ + S32 major = 0, minor = 0, patch = 0; + U64 build = 0; + sscanf(vvm_version.c_str(), "%d.%d.%d.%llu", &major, &minor, &patch, &build); + + const LLVersionInfo& vi = LLVersionInfo::instance(); + S32 cur_major = vi.getMajor(); + S32 cur_minor = vi.getMinor(); + S32 cur_patch = vi.getPatch(); + U64 cur_build = vi.getBuild(); + + if (cur_major != major) return cur_major < major ? -1 : 1; + if (cur_minor != minor) return cur_minor < minor ? -1 : 1; + if (cur_patch != patch) return cur_patch < patch ? -1 : 1; + if (cur_build != build) return cur_build < build ? -1 : 1; + return 0; +} + +// +// Update manager lifecycle +// + +static void ensure_update_manager(bool allow_downgrade) +{ + if (sUpdateManager) + return; + + vpkc_update_options_t options = {}; + options.AllowVersionDowngrade = allow_downgrade; + options.ExplicitChannel = nullptr; + + if (!sUpdateSource) + { + sUpdateSource = vpkc_new_source_custom_callback( + custom_get_release_feed, + custom_free_release_feed, + custom_download_asset, + nullptr); + } + + vpkc_locator_config_t* locator_ptr = nullptr; + +#if LL_DARWIN + // Try auto-detection first (works when the app bundle was packaged by vpk + // and has UpdateMac + sq.version already present) + if (!vpkc_new_update_manager_with_source(sUpdateSource, &options, nullptr, &sUpdateManager)) + { + char err[512]; + vpkc_get_last_error(err, sizeof(err)); + LL_INFOS("Velopack") << "Auto-detect failed (" << ll_safe_string(err) + << "), falling back to explicit locator" << LL_ENDL; + + // Auto-detection failed — construct an explicit locator. + // This handles legacy DMG installs that don't have Velopack's + // install state (UpdateMac, sq.version) in the bundle. + vpkc_locator_config_t locator = {}; + + // The executable lives at /Contents/MacOS/ + // The app bundle root is two levels up from the executable directory. + std::string exe_dir = gDirUtilp->getExecutableDir(); + std::string bundle_root = exe_dir + "/../.."; + char resolved[PATH_MAX]; + if (realpath(bundle_root.c_str(), resolved)) + { + bundle_root = resolved; + } + + // Construct a version string in Velopack SemVer format: major.minor.patch-build + const LLVersionInfo& vi = LLVersionInfo::instance(); + std::string current_version = llformat("%d.%d.%d-%llu", + vi.getMajor(), vi.getMinor(), vi.getPatch(), vi.getBuild()); + + // Create a minimal sq.version manifest so Velopack knows our version. + // Proper vpk-packaged builds have this in the bundle already. + std::string manifest_path = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, "sq.version"); + { + std::string app_name = LLVersionInfo::instance().getChannel(); + std::string pack_id = app_name; + pack_id.erase(std::remove(pack_id.begin(), pack_id.end(), ' '), pack_id.end()); + + std::string nuspec = "\n" + "\n" + " \n" + " " + pack_id + "\n" + " " + current_version + "\n" + " " + app_name + "\n" + " \n" + "\n"; + + llofstream manifest_file(manifest_path, std::ios::trunc); + if (manifest_file.is_open()) + { + manifest_file << nuspec; + manifest_file.close(); + } + } + + std::string packages_dir = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, "velopack-packages"); + LLFile::mkdir(packages_dir); + + locator.RootAppDir = const_cast(bundle_root.c_str()); + locator.CurrentBinaryDir = const_cast(exe_dir.c_str()); + locator.ManifestPath = const_cast(manifest_path.c_str()); + locator.PackagesDir = const_cast(packages_dir.c_str()); + locator.UpdateExePath = nullptr; + locator.IsPortable = false; + + locator_ptr = &locator; + + LL_INFOS("Velopack") << "Explicit locator: RootAppDir=" << bundle_root + << " CurrentBinaryDir=" << exe_dir + << " Version=" << current_version << LL_ENDL; + + if (!vpkc_new_update_manager_with_source(sUpdateSource, &options, locator_ptr, &sUpdateManager)) + { + char err2[512]; + vpkc_get_last_error(err2, sizeof(err2)); + LL_WARNS("Velopack") << "Failed to create update manager: " << ll_safe_string(err2) << LL_ENDL; + } + } + return; +#endif + + // Windows: Velopack auto-detection works because the viewer is installed + // by Velopack's Setup.exe which creates the proper install structure. + if (!vpkc_new_update_manager_with_source(sUpdateSource, &options, nullptr, &sUpdateManager)) + { + char err[512]; + vpkc_get_last_error(err, sizeof(err)); + LL_WARNS("Velopack") << "Failed to create update manager: " << ll_safe_string(err) << LL_ENDL; + } +} + +// +// Public API - Cross-platform +// + +bool velopack_initialize() +{ + vpkc_set_logger(on_log_message, nullptr); + vpkc_app_set_auto_apply_on_startup(false); + +#if LL_WINDOWS || LL_DARWIN + vpkc_app_set_hook_first_run(on_first_run); + vpkc_app_set_hook_after_install(on_after_install); + vpkc_app_set_hook_before_uninstall(on_before_uninstall); +#endif + + vpkc_app_run(nullptr); + return true; +} + +// Downloads the update that was found during the check phase. +// Operates on sPendingCheckInfo which was set by velopack_check_for_updates. +static void velopack_download_pending_update() +{ + if (!sUpdateManager || !sPendingCheckInfo) + { + LL_WARNS("Velopack") << "No pending check info to download" << LL_ENDL; + return; + } + + LL_DEBUGS("Velopack") << "Setting up detailed logging"; + vpkc_set_logger(on_vpk_log, nullptr); + LL_CONT << LL_ENDL; + LL_INFOS("Velopack") << "Downloading update..." << LL_ENDL; + + // Pre-download the nupkg at the coroutine level where getRawAndSuspend works. + // The download callback inside the Rust FFI cannot use coroutine HTTP. + std::string asset_filename = sPendingCheckInfo->TargetFullRelease->FileName + ? sPendingCheckInfo->TargetFullRelease->FileName : ""; + std::string asset_url; + auto url_it = sAssetUrlMap.find(asset_filename); + if (url_it != sAssetUrlMap.end()) + { + asset_url = url_it->second; + } + else + { + std::string base = sUpdateUrl; + if (!base.empty() && base.back() == '/') + base.pop_back(); + asset_url = base + "/" + asset_filename; + } + + sPreDownloadedAssetPath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, asset_filename); + LL_INFOS("Velopack") << "Pre-downloading " << asset_url + << " to " << sPreDownloadedAssetPath << LL_ENDL; + + if (!download_url_to_file(asset_url, sPreDownloadedAssetPath)) + { + LL_WARNS("Velopack") << "Failed to pre-download update asset" << LL_ENDL; + sPreDownloadedAssetPath.clear(); + return; + } + + LL_INFOS("Velopack") << "Pre-download complete, handing to Velopack" << LL_ENDL; + if (vpkc_download_updates(sUpdateManager, sPendingCheckInfo, on_progress, nullptr)) + { + if (sPendingUpdate) + { + vpkc_free_update_info(sPendingUpdate); + } + sPendingUpdate = sPendingCheckInfo; + sPendingCheckInfo = nullptr; // Ownership transferred + LL_INFOS("Velopack") << "Update downloaded and pending" << LL_ENDL; + } + else + { + char descr[512]; + vpkc_get_last_error(descr, sizeof(descr)); + LL_WARNS("Velopack") << "Failed to download update: " << ll_safe_string((const char*)descr) << LL_ENDL; + } +} + +static void on_downloading_closed(const LLSD& notification, const LLSD& response) +{ + sDownloadingNotification = nullptr; + if (sIsRequired) + { + // User closed the downloading dialog during a required update — re-show it + show_downloading_notification(sTargetVersion); + } +} + +static void show_downloading_notification(const std::string& version) +{ + LLSD args; + args["VERSION"] = version; + sDownloadingNotification = LLNotificationsUtil::add("DownloadingUpdate", args, LLSD(), on_downloading_closed); +} + +static void dismiss_downloading_notification() +{ + if (sDownloadingNotification) + { + LLNotificationsUtil::cancel(sDownloadingNotification); + sDownloadingNotification = nullptr; + } +} + +static void on_required_update_response(const LLSD& notification, const LLSD& response) +{ + LL_INFOS("Velopack") << "Required update acknowledged, starting download" << LL_ENDL; + show_downloading_notification(sTargetVersion); + LLCoros::instance().launch("VelopackRequiredUpdate", []() + { + velopack_download_pending_update(); + dismiss_downloading_notification(); + if (velopack_is_update_pending()) + { + LL_INFOS("Velopack") << "Required update downloaded, quitting to apply" << LL_ENDL; + velopack_request_restart_after_update(); + LLAppViewer::instance()->requestQuit(); + } + }); +} + +static void on_optional_update_response(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // "Install" + { + LL_INFOS("Velopack") << "User accepted optional update, starting download" << LL_ENDL; + show_downloading_notification(sTargetVersion); + LLCoros::instance().launch("VelopackOptionalUpdate", []() + { + velopack_download_pending_update(); + dismiss_downloading_notification(); + if (velopack_is_update_pending()) + { + LL_INFOS("Velopack") << "Optional update downloaded, quitting to apply" << LL_ENDL; + velopack_request_restart_after_update(); + LLAppViewer::instance()->requestQuit(); + } + }); + } + else + { + LL_INFOS("Velopack") << "User declined optional update (option=" << option << ")" << LL_ENDL; + // Free the check info since user declined + if (sPendingCheckInfo) + { + vpkc_free_update_info(sPendingCheckInfo); + sPendingCheckInfo = nullptr; + } + } +} + +static void show_required_update_prompt() +{ + LLSD args; + args["VERSION"] = sTargetVersion; + args["URL"] = sReleaseNotesUrl; + LLNotificationsUtil::add("PauseForUpdate", args, LLSD(), on_required_update_response); +} + +void velopack_check_for_updates(const std::string& required_version, const std::string& relnotes_url) +{ + if (sUpdateUrl.empty()) + { + LL_DEBUGS("Velopack") << "No update URL set, skipping update check" << LL_ENDL; + return; + } + + // Allow downgrades only for rollbacks: VVM requires a version that's + // strictly lower than what we're running (e.g., a retracted build). + bool has_required = !required_version.empty(); + int ver_cmp = has_required ? compare_running_version(required_version) : 0; + bool allow_downgrade = ver_cmp > 0; // running > required → rollback scenario + ensure_update_manager(allow_downgrade); + if (!sUpdateManager) + return; + + // Ask Velopack to check its feed — this is the source of truth + vpkc_update_info_t* update_info = nullptr; + vpkc_update_check_t result = vpkc_check_for_updates(sUpdateManager, &update_info); + + if (result != UPDATE_AVAILABLE || !update_info) + { + LL_INFOS("Velopack") << "No update available from feed (result=" << result << ")" << LL_ENDL; + return; + } + + // Extract the actual target version from Velopack's feed + std::string target_version = update_info->TargetFullRelease->Version + ? update_info->TargetFullRelease->Version : ""; + LL_INFOS("Velopack") << "Update available: " << target_version + << " (required_version=" << required_version << ")" << LL_ENDL; + + // Store state for the prompt/download phase + sReleaseNotesUrl = relnotes_url; + sTargetVersion = target_version; + if (sPendingCheckInfo) + { + vpkc_free_update_info(sPendingCheckInfo); + } + sPendingCheckInfo = update_info; + + // Determine if this is mandatory: running version is below VVM's required floor + bool is_required = ver_cmp < 0; // running < required → must update + sIsRequired = is_required; + + if (is_required) + { + LL_INFOS("Velopack") << "Required update (running below " << required_version + << "), prompting user for " << target_version << LL_ENDL; + show_required_update_prompt(); + return; + } + + // Optional update — check user preference + U32 updater_setting = gSavedSettings.getU32("UpdaterServiceSetting"); + + if (updater_setting == 3) + { + // "Install each update automatically" — download silently, apply on quit + LL_INFOS("Velopack") << "Optional update to " << target_version + << ", downloading automatically (UpdaterServiceSetting=3)" << LL_ENDL; + velopack_download_pending_update(); + return; + } + + // Default / value 1: "Ask me when an optional update is ready to install" + LL_INFOS("Velopack") << "Optional update available (" << target_version << "), prompting user" << LL_ENDL; + LLSD args; + args["VERSION"] = target_version; + args["URL"] = relnotes_url; + LLNotificationsUtil::add("PromptOptionalUpdate", args, LLSD(), on_optional_update_response); +} + +std::string velopack_get_current_version() +{ + if (!sUpdateManager) + { + return ""; + } + + char version[64]; + size_t len = vpkc_get_current_version(sUpdateManager, version, sizeof(version)); + if (len > 0) + { + return std::string(version, len); + } + return ""; +} + +bool velopack_is_update_pending() +{ + return sPendingUpdate != nullptr; +} + +bool velopack_is_required_update_in_progress() +{ + return sIsRequired && sPendingCheckInfo != nullptr; +} + +std::string velopack_get_required_update_version() +{ + return sTargetVersion; +} + +bool velopack_should_restart_after_update() +{ + return sRestartAfterUpdate; +} + +void velopack_request_restart_after_update() +{ + sRestartAfterUpdate = true; +} + +void velopack_apply_pending_update(bool restart) +{ + if (!sUpdateManager || !sPendingUpdate || !sPendingUpdate->TargetFullRelease) + { + LL_WARNS("Velopack") << "Cannot apply update: no pending update or manager" << LL_ENDL; + return; + } + + LL_INFOS("Velopack") << "Applying pending update (restart=" << restart << ")" << LL_ENDL; + vpkc_wait_exit_then_apply_updates(sUpdateManager, + sPendingUpdate->TargetFullRelease, + false, + restart, + nullptr, 0); +} + +void velopack_cleanup() +{ + if (sUpdateManager) + { + vpkc_free_update_manager(sUpdateManager); + sUpdateManager = nullptr; + } + if (sUpdateSource) + { + vpkc_free_source(sUpdateSource); + sUpdateSource = nullptr; + } + if (sPendingUpdate) + { + vpkc_free_update_info(sPendingUpdate); + sPendingUpdate = nullptr; + } + if (sPendingCheckInfo) + { + vpkc_free_update_info(sPendingCheckInfo); + sPendingCheckInfo = nullptr; + } + sAssetUrlMap.clear(); +} + +void velopack_set_update_url(const std::string& url) +{ + sUpdateUrl = url; + LL_INFOS("Velopack") << "Update URL set to: " << url << LL_ENDL; +} + +void velopack_set_progress_callback(std::function callback) +{ + sProgressCallback = callback; +} + +#endif // LL_VELOPACK diff --git a/indra/newview/llvelopack.h b/indra/newview/llvelopack.h new file mode 100644 index 0000000000..d04d0db7dc --- /dev/null +++ b/indra/newview/llvelopack.h @@ -0,0 +1,59 @@ +/** + * @file llvelopack.h + * @brief Velopack installer and update framework integration + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVELOPACK_H +#define LL_LLVELOPACK_H + +#if LL_VELOPACK + +#include +#include + +bool velopack_initialize(); +void velopack_check_for_updates(const std::string& required_version, const std::string& relnotes_url); +std::string velopack_get_current_version(); +bool velopack_is_update_pending(); +bool velopack_is_required_update_in_progress(); +std::string velopack_get_required_update_version(); +bool velopack_should_restart_after_update(); +void velopack_request_restart_after_update(); +void velopack_apply_pending_update(bool restart = true); +void velopack_set_update_url(const std::string& url); +void velopack_set_progress_callback(std::function callback); +void velopack_cleanup(); + +#if LL_WINDOWS +void clear_nsis_links(); +bool get_nsis_version( + int& nsis_major, + int& nsis_minor, + int& nsis_patch, + uint64_t& nsis_build); +#endif + +#endif // LL_VELOPACK +#endif +// EOF diff --git a/indra/newview/llversioninfo.cpp b/indra/newview/llversioninfo.cpp index 4e8320b72a..178f10257d 100644 --- a/indra/newview/llversioninfo.cpp +++ b/indra/newview/llversioninfo.cpp @@ -54,7 +54,7 @@ LLVersionInfo::LLVersionInfo(): mWorkingChannelName(LL_TO_STRING(LL_VIEWER_CHANNEL)), build_configuration(LLBUILD_CONFIG), // set in indra/cmake/BuildVersion.cmake // instantiate an LLEventMailDrop with canonical name to listen for news - // from SLVersionChecker + // from the Viewer Version Manager mPump{new LLEventMailDrop("relnotes")}, // immediately listen on mPump, store arriving URL into mReleaseNotes mStore{new LLStoreListener(*mPump, mReleaseNotes)} diff --git a/indra/newview/llversioninfo.h b/indra/newview/llversioninfo.h index 237b37a084..a2b93597e6 100644 --- a/indra/newview/llversioninfo.h +++ b/indra/newview/llversioninfo.h @@ -112,8 +112,8 @@ private: std::string mReleaseNotes; // Store unique_ptrs to the next couple things so we don't have to explain // to every consumer of this header file all the details of each. - // mPump is the LLEventMailDrop on which we listen for SLVersionChecker to - // post the release-notes URL from the Viewer Version Manager. + // mPump is the LLEventMailDrop on which we listen for the + // release-notes URL from the Viewer Version Manager. std::unique_ptr mPump; // mStore is an adapter that stores the release-notes URL in mReleaseNotes. std::unique_ptr> mStore; diff --git a/indra/newview/llviewernetwork.cpp b/indra/newview/llviewernetwork.cpp index 890580ddff..6cb3aee20c 100644 --- a/indra/newview/llviewernetwork.cpp +++ b/indra/newview/llviewernetwork.cpp @@ -575,6 +575,7 @@ std::string LLGridManager::getGridLoginID() std::string LLGridManager::getUpdateServiceURL() { + auto env_update_service = LLStringUtil::getoptenv("SL_UPDATE_SERVICE"); std::string update_url_base = gSavedSettings.getString("CmdLineUpdateService");; if ( !update_url_base.empty() ) { @@ -582,6 +583,13 @@ std::string LLGridManager::getUpdateServiceURL() << "Update URL base overridden from command line: " << update_url_base << LL_ENDL; } + else if (env_update_service && env_update_service->find("http") != std::string::npos) + { + update_url_base = *env_update_service; + LL_INFOS("UpdaterService", "GridManager") + << "Update URL base overridden from SL_UPDATE_SERVICE environment variable: " << update_url_base + << LL_ENDL; + } else if ( mGridList[mGrid].has(GRID_UPDATE_SERVICE_URL) ) { update_url_base = mGridList[mGrid][GRID_UPDATE_SERVICE_URL].asString(); diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index db6d83db5f..3a04b212a7 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -797,7 +797,11 @@ void send_viewer_stats(bool include_preferences) fail["failed_resends"] = (S32) gMessageSystem->mFailedResendPackets; fail["off_circuit"] = (S32) gMessageSystem->mOffCircuitPackets; fail["invalid"] = (S32) gMessageSystem->mInvalidOnCircuitPackets; - fail["missing_updater"] = (S32) LLAppViewer::instance()->isUpdaterMissing(); +#if LL_VELOPACK + fail["missing_updater"] = false; +#else + fail["missing_updater"] = true; +#endif LLSD &inventory = body["inventory"]; inventory["usable"] = gInventory.isInventoryUsable(); diff --git a/indra/newview/llvvmquery.cpp b/indra/newview/llvvmquery.cpp new file mode 100644 index 0000000000..12dcc1d04d --- /dev/null +++ b/indra/newview/llvvmquery.cpp @@ -0,0 +1,189 @@ +/** + * @file llvvmquery.cpp + * @brief Query the Viewer Version Manager (VVM) for update information + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llvvmquery.h" + +#include "llcorehttputil.h" +#include "llcoros.h" +#include "llevents.h" +#include "llviewernetwork.h" +#include "llversioninfo.h" +#include "llviewercontrol.h" +#include "llhasheduniqueid.h" +#include "lluri.h" +#include "llsys.h" + +#if LL_VELOPACK +#include "llvelopack.h" +#endif + +namespace +{ + std::string get_platform_string() + { +#if LL_WINDOWS + return "win64"; +#elif LL_DARWIN + return "mac64"; +#elif LL_LINUX + return "lnx64"; +#else + return "unknown"; +#endif + } + + std::string get_platform_version() + { + return LLOSInfo::instance().getOSVersionString(); + } + + std::string get_machine_id() + { + unsigned char id[MD5HEX_STR_SIZE]; + if (llHashedUniqueID(id)) + { + return std::string(reinterpret_cast(id)); + } + return "unknown"; + } + + void query_vvm_coro() + { + // Get base URL from grid manager + std::string base_url = LLGridManager::getInstance()->getUpdateServiceURL(); + + // We use this for dev testing when working with VVM and working on the updater. Not advisable to uncomment it. + //std::string base_url = "https://update.qa.secondlife.io/update"; + + if (base_url.empty()) + { + LL_WARNS("VVM") << "No update service URL configured" << LL_ENDL; + return; + } + + // Gather parameters for VVM query + std::string channel = LLVersionInfo::instance().getChannel(); + + // We use this for dev testing when working with VVM and working on the updater. Not advisable to uncomment it. + // std::string channel = "QA Target for Velopack"; + + std::string version = LLVersionInfo::instance().getVersion(); + std::string platform = get_platform_string(); + std::string platform_version = get_platform_version(); + std::string test_ok = gSavedSettings.getBOOL("UpdaterWillingToTest") ? "testok" : "testno"; + std::string machine_id = get_machine_id(); + + // Build URL: {base}/v1.2/{channel}/{version}/{platform}/{platform_version}/{testok}/{uuid} + std::string url = base_url + "/v1.2/" + + LLURI::escape(channel) + "/" + + LLURI::escape(version) + "/" + + platform + "/" + + LLURI::escape(platform_version) + "/" + + test_ok + "/" + + machine_id; + + LL_INFOS("VVM") << "Querying VVM: " << url << LL_ENDL; + + // Make HTTP GET request + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter = + std::make_shared("VVMQuery", httpPolicy); + LLCore::HttpRequest::ptr_t request = std::make_shared(); + + LLSD result = adapter->getAndSuspend(request, url); + + // Check HTTP status + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + if (status.getType() == 404) + { + LL_INFOS("VVM") << "Unmanaged channel, no updates available" << LL_ENDL; + return; + } + LL_WARNS("VVM") << "VVM query failed: " << status.toString() << LL_ENDL; + return; + } + + // Read whether this update is required or optional + bool update_required = result["required"].asBoolean(); + std::string relnotes = result["more_info"].asString(); + + // Extract update URL for current platform + LLSD platforms = result["platforms"]; + if (platforms.has(platform)) + { + std::string update_url = platforms[platform]["url"].asString(); +#if LL_VELOPACK + std::string velopack_url = platforms[platform]["velopack_url"].asString(); + U32 updater_service = gSavedSettings.getU32("UpdaterServiceSetting"); + std::string required_version = update_required ? result["version"].asString() : ""; + // Skip network check if no required version AND user only wants mandatory updates + if (!velopack_url.empty() && (update_required || updater_service != 0)) + { + LL_INFOS("VVM") << "Velopack feed URL: " << velopack_url + << " required_version: " << required_version << LL_ENDL; + velopack_set_update_url(velopack_url); + + LLCoros::instance().launch("VelopackUpdateCheck", + [required_version, relnotes]() + { + velopack_check_for_updates(required_version, relnotes); + }); + } + else if (!velopack_url.empty()) + { + LL_INFOS("VVM") << "Optional update skipped (UpdaterServiceSetting=0)" << LL_ENDL; + } + else +#endif + if (!update_url.empty()) + { + LL_INFOS("VVM") << "Update available at: " << update_url << LL_ENDL; + } + } + else + { + LL_INFOS("VVM") << "No update available for platform: " << platform << LL_ENDL; + } + + // Post release notes URL to the relnotes event pump + if (!relnotes.empty()) + { + LL_INFOS("VVM") << "Release notes URL: " << relnotes << LL_ENDL; + LLEventPumps::instance().obtain("relnotes").post(relnotes); + } + } +} + +void initVVMUpdateCheck() +{ + LL_INFOS("VVM") << "Initializing VVM update check" << LL_ENDL; + LLCoros::instance().launch("VVMUpdateCheck", &query_vvm_coro); +} diff --git a/indra/newview/llvvmquery.h b/indra/newview/llvvmquery.h new file mode 100644 index 0000000000..977d82af64 --- /dev/null +++ b/indra/newview/llvvmquery.h @@ -0,0 +1,42 @@ +/** + * @file llvvmquery.h + * @brief Query the Viewer Version Manager (VVM) for update information + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVVMQUERY_H +#define LL_LLVVMQUERY_H + +/** + * Initialize the VVM update check. + * + * This launches a coroutine that queries the Viewer Version Manager (VVM) + * to check for available updates. If an update is available, it configures + * Velopack with the update URL and initiates the update check/download. + * + * The release notes URL from the VVM response is posted to the "relnotes" + * event pump for display. + */ +void initVVMUpdateCheck(); + +#endif // LL_LLVVMQUERY_H diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index bc69242651..a9adb772e5 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -200,6 +200,16 @@ No tutorial is currently available. yestext="OK"/> + + [APP_NAME] found an installation of an older version [VERSION]. To uninstall the older version, please follow [https://community.secondlife.com/knowledgebase/english/how-to-uninstall-and-reinstall-second-life-r524 this manual]. + + + + +Downloading update [VERSION]... +The viewer will restart once the download is complete. + + 3 and self.args['version'][3]: + pack_version += '-' + self.args['version'][3] + pack_title = self.app_name() # Display name with spaces + pack_dir = self.get_dst_prefix() + main_exe = self.final_exe() + installer_base = self.installer_base_name() + exclude_pattern = r'.*\.pdb|.*\.map|.*\.bat|.*\.exp|.*\.lib|.*\.nsi|.*\.tar\.xz|secondlife-bin\..*|.*_Setup\.exe|.*-Setup\.exe' + + # Channel-specific icon for the Velopack installer. + # CMake copies icons/{channel}/secondlife.ico to res/ll_icon.ico at configure time. + # Try the CMake-generated copy first, fall back to the source icon. + icon_path = os.path.join(self.get_src_prefix(), 'res', 'll_icon.ico') + if not os.path.exists(icon_path): + icon_path = os.path.join(self.get_src_prefix(), self.icon_path(), 'secondlife.ico') + + # In CI, defer Velopack packaging to the sign step where Azure credentials + # are available. Emit metadata as GitHub outputs so the sign step can run + # vpk pack with --signTemplate, producing a package with signed executables. + if os.getenv('GITHUB_ACTIONS'): + # Copy the icon into pack_dir so it's included in the Windows-app artifact + icon_filename = '' + if os.path.exists(icon_path): + icon_filename = os.path.basename(icon_path) + icon_dest = os.path.join(pack_dir, icon_filename) + shutil.copy2(icon_path, icon_dest) + print("Copied icon %s to %s" % (icon_path, icon_dest)) + else: + print("WARNING: Icon not found at %s" % icon_path) + + # Emit metadata for the sign step + self.set_github_output('velopack_pack_id', pack_id) + self.set_github_output('velopack_pack_version', pack_version) + self.set_github_output('velopack_pack_title', pack_title) + self.set_github_output('velopack_main_exe', main_exe) + self.set_github_output('velopack_icon', icon_filename) + self.set_github_output('velopack_installer_base', installer_base) + self.set_github_output('velopack_exclude', exclude_pattern) + # Set package_file so llmanifest's touched.bat logic doesn't crash + self.package_file = installer_base + '_Setup.exe' + print("CI mode: Velopack packaging deferred to sign step") + return + + # Local builds: run vpk pack directly (unsigned) + vpk_args = [ + 'vpk', 'pack', + '--packId', pack_id, + '--packVersion', pack_version, + '--packDir', pack_dir, + '--mainExe', main_exe, + '--packTitle', pack_title, + '--exclude', exclude_pattern, + # Suppress Velopack's built-in shortcut creation; we create our own + # shortcuts in llvelopack.cpp on_after_install hook instead. + '--shortcuts', '', + ] + + # Add icon — CMake copies the channel-appropriate secondlife.ico to res/ll_icon.ico + if os.path.exists(icon_path): + print("Using icon: %s" % icon_path) + vpk_args.extend(['--icon', icon_path]) + else: + print("WARNING: Icon not found at %s — Setup.exe will have no icon" % icon_path) + + print("Running Velopack packaging: %s" % ' '.join(vpk_args)) + + # Run vpk command + import subprocess + result = subprocess.run(vpk_args, cwd=os.path.dirname(pack_dir), capture_output=True, text=True) + if result.stdout: + print("vpk stdout: %s" % result.stdout) + if result.stderr: + print("vpk stderr: %s" % result.stderr) + if result.returncode != 0: + raise ManifestError("Velopack packaging failed with code %d" % result.returncode) + + # Velopack outputs to a Releases directory + releases_dir = os.path.join(os.path.dirname(pack_dir), 'Releases') + + # Move the setup exe INTO pack_dir so it's included in the Windows-app artifact + # IMPORTANT: Use hyphen format (-Setup.exe) to avoid the *_Setup.exe exclusion pattern + # in viewer_app output (line ~538). The underscore pattern excludes NSIS installers + # which are rebuilt during signing, but Velopack installers are created here. + # Velopack creates: {packId}-win-Setup.exe + velopack_setup = os.path.join(releases_dir, '%s-win-Setup.exe' % pack_id) + self.package_file = installer_base + '_Setup.exe' + our_setup = os.path.join(pack_dir, self.package_file) + if os.path.exists(velopack_setup): + shutil.move(velopack_setup, our_setup) + print("Moved %s to %s" % (velopack_setup, our_setup)) + + # Rename the portable zip to include the version number + # Velopack creates: {packId}-win-Portable.zip + velopack_portable = os.path.join(releases_dir, '%s-win-Portable.zip' % pack_id) + if os.path.exists(velopack_portable): + our_portable = os.path.join(releases_dir, installer_base + '_Portable.zip') + shutil.move(velopack_portable, our_portable) + print("Moved %s to %s" % (velopack_portable, our_portable)) + + # Output the Releases directory path for artifact upload (contains nupkg, RELEASES for updates) + self.set_github_output('velopack_releases', releases_dir) + + def nsis_package_finish(self): + """Package the viewer using NSIS installer (legacy)""" # a standard map of strings for replacing in the templates substitution_strings = { 'version' : '.'.join(self.args['version']), @@ -781,7 +889,7 @@ class Windows_x86_64_Manifest(ViewerManifest): substitution_strings['installer_file'] = installer_file version_vars = """ - !define INSTEXE "SLVersionChecker.exe" + !define INSTEXE "%(final_exe)s" !define VERSION "%(version_short)s" !define VERSION_LONG "%(version)s" !define VERSION_DASHES "%(version_dashes)s" @@ -967,15 +1075,6 @@ class Darwin_x86_64_Manifest(ViewerManifest): with self.prefix(src=self.icon_path(), dst="") : self.path("secondlife.icns") - # Copy in the updater script and helper modules - self.path(src=os.path.join(pkgdir, 'VMP'), dst="updater") - - with self.prefix(src="", dst=os.path.join("updater", "icons")): - self.path2basename(self.icon_path(), "secondlife.ico") - with self.prefix(src="vmp_icons", dst=""): - self.path("*.png") - self.path("*.gif") - with self.prefix(src_dst="cursors_mac"): self.path("*.tif") @@ -1127,6 +1226,123 @@ class Darwin_x86_64_Manifest(ViewerManifest): arcname=self.app_name() + ".app") self.set_github_output_path('viewer_app', tarpath) + # Generate Velopack update packages if enabled + # This creates the nupkg and RELEASES files needed for auto-updates + # Distribution is still via DMG, but updates use Velopack + if self.args.get('velopack', 'OFF') == 'ON': + self.velopack_package_finish() + + def velopack_package_finish(self): + """Generate Velopack update packages for macOS. + + This creates the nupkg and releases.json files needed for auto-updates. + Distribution is still via DMG - Velopack only handles the update infrastructure. + """ + # packId determines install identification - same as Windows for consistency + pack_id = self.app_name_oneword() # "SecondLife", "SecondLifeBeta", etc. + # Velopack requires SemVer2. Use major.minor.patch-buildnumber so that + # Velopack can distinguish builds and order them correctly. + pack_version = '.'.join(self.args['version'][:3]) + if len(self.args['version']) > 3 and self.args['version'][3]: + pack_version += '-' + self.args['version'][3] + pack_title = self.app_name() # Display name with spaces + + # The .app bundle path (e.g., "/path/to/Second Life Release.app") + app_bundle = self.get_dst_prefix() + # Bundle ID from args (e.g., "com.secondlife.viewer") + bundle_id = self.args.get('bundleid', 'com.secondlife.indra.viewer') + + # Icon path for macOS + icon_path = os.path.join(self.get_src_prefix(), self.icon_path(), 'secondlife.icns') + + # The main executable inside Contents/MacOS/ is named after the channel + main_exe = self.channel() + + # In CI, defer Velopack packaging to the sign step where code signing + # credentials are available. Emit metadata as GitHub outputs so the + # sign step can run vpk pack after signing the app bundle. + if os.getenv('GITHUB_ACTIONS'): + self.set_github_output('velopack_mac_pack_id', pack_id) + self.set_github_output('velopack_mac_pack_version', pack_version) + self.set_github_output('velopack_mac_pack_title', pack_title) + self.set_github_output('velopack_mac_main_exe', main_exe) + self.set_github_output('velopack_mac_bundle_id', bundle_id) + print("CI mode: macOS Velopack packaging deferred to sign step") + return + + # Local builds: run vpk pack directly (unsigned) + + # Parent directory containing the .app bundle - this is where we run vpk from + # and where the Releases directory will be created + work_dir = os.path.dirname(app_bundle) + + # Output directory for releases - clean it first to avoid version conflicts + releases_dir = os.path.join(work_dir, 'Releases') + if os.path.exists(releases_dir): + print("Cleaning existing Releases directory: %s" % releases_dir) + shutil.rmtree(releases_dir) + + # Build vpk command for macOS + # See: https://docs.velopack.io/reference/cli/content/vpk-osx + vpk_args = [ + 'vpk', 'pack', + '--packId', pack_id, + '--packVersion', pack_version, + '--packDir', app_bundle, + '--packTitle', pack_title, + '--mainExe', main_exe, # Executable name inside Contents/MacOS/ + '--bundleId', bundle_id, + '--outputDir', releases_dir, + '--noInst', # Don't generate .pkg installer - we use DMG for distribution + '--verbose', # Show detailed output + ] + + # Add icon if exists + if os.path.exists(icon_path): + vpk_args.extend(['--icon', icon_path]) + + print("Running Velopack packaging for macOS:") + print(" Command: %s" % ' '.join(vpk_args)) + print(" Working directory: %s" % work_dir) + print(" App bundle: %s" % app_bundle) + print(" Main executable: %s" % main_exe) + + # Run vpk command + result = subprocess.run(vpk_args, cwd=work_dir, capture_output=True, text=True) + + # Always print output for debugging + if result.stdout: + print("vpk stdout:\n%s" % result.stdout) + if result.stderr: + print("vpk stderr:\n%s" % result.stderr) + + if result.returncode != 0: + raise ManifestError("Velopack packaging failed with code %d" % result.returncode) + + # Verify the Releases directory was created and contains expected files + if not os.path.exists(releases_dir): + raise ManifestError("Velopack releases directory not found: %s" % releases_dir) + + # List what was created + releases_contents = os.listdir(releases_dir) + print("Velopack releases directory contents: %s" % releases_contents) + + # Verify we have the expected files (nupkg and releases JSON) + nupkg_files = [f for f in releases_contents if f.endswith('.nupkg')] + json_files = [f for f in releases_contents if f.endswith('.json')] + + if not nupkg_files: + raise ManifestError("No .nupkg files found in releases directory") + if not json_files: + raise ManifestError("No releases JSON files found in releases directory") + + print("Generated %d nupkg file(s): %s" % (len(nupkg_files), nupkg_files)) + print("Generated %d JSON file(s): %s" % (len(json_files), json_files)) + + # Output the Releases directory path for artifact upload + self.set_github_output('velopack_releases', releases_dir) + print("Velopack releases directory: %s" % releases_dir) + class LinuxManifest(ViewerManifest): build_data_json_platform = 'lnx' @@ -1324,6 +1540,7 @@ if __name__ == "__main__": dict(name='discord', description="""Indication discord social sdk libraries are needed""", default='OFF'), dict(name='openal', description="""Indication openal libraries are needed""", default='OFF'), dict(name='tracy', description="""Indication tracy profiler is enabled""", default='OFF'), + dict(name='velopack', description="""Use Velopack installer instead of NSIS""", default='OFF'), ] try: main(extra=extra_arguments) diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp index 37b70964c3..144f807852 100644 --- a/indra/viewer_components/login/lllogin.cpp +++ b/indra/viewer_components/login/lllogin.cpp @@ -34,7 +34,6 @@ #include "llcoros.h" #include "llevents.h" -#include "lleventfilter.h" #include "lleventcoro.h" #include "llexception.h" #include "stringize.h" @@ -133,16 +132,6 @@ void LLLogin::Impl::connect(const std::string& uri, const LLSD& login_params) LL_DEBUGS("LLLogin") << " connected with uri '" << uri << "', login_params " << login_params << LL_ENDL; } -namespace -{ -// Instantiate this rendezvous point at namespace scope so it's already -// present no matter how early the updater might post to it. -// Use an LLEventMailDrop, which has future-like semantics: regardless of the -// relative order in which post() or listen() are called, it delivers each -// post() event to its listener(s) until one of them consumes that event. -static LLEventMailDrop sSyncPoint("LoginSync"); -} - void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params) { LLSD printable_params = hidePasswd(login_params); @@ -225,58 +214,7 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params) } else { - // Synchronize here with the updater. We synchronize here rather - // than in the fail.login handler, which actually examines the - // response from login.cgi, because here we are definitely in a - // coroutine and can definitely use suspendUntilBlah(). Whoever's - // listening for fail.login might not be. - - // If the reason for login failure is that we must install a - // required update, we definitely want to pass control to the - // updater to manage that for us. We'll handle any other login - // failure ourselves, as usual. We figure that no matter where you - // are in the world, or what kind of network you're on, we can - // reasonably expect the Viewer Version Manager to respond more or - // less as quickly as login.cgi. This synchronization is only - // intended to smooth out minor races between the two services. - // But what if the updater crashes? Use a timeout so that - // eventually we'll tire of waiting for it and carry on as usual. - // Given the above, it can be a fairly short timeout, at least - // from a human point of view. - - // Since sSyncPoint is an LLEventMailDrop, we DEFINITELY want to - // consume the posted event. - LLCoros::OverrideConsuming oc(true); LLSD responses(mAuthResponse["responses"]); - LLSD updater; - - if (printable_params["wait_for_updater"].asBoolean()) - { - std::string reason_response = responses["data"]["reason"].asString(); - // Timeout should produce the isUndefined() object passed here. - if (reason_response == "update") - { - LL_INFOS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL; - updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD()); - } - else - { - LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL; - updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 3, LLSD()); - } - if (updater.isUndefined()) - { - LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login" - << LL_ENDL; - } - else - { - LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL; - } - } - - // Let the fail.login handler deal with empty updater response. - responses["updater"] = updater; sendProgressEvent("offline", "fail.login", responses); } return; // Done! -- cgit v1.3 From df5e1c1613798b51fb516c63a52fdbf8b8419fee Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 21 Apr 2026 00:19:34 +0300 Subject: #5655 Fix freeze on purging cef cache --- indra/newview/llappviewer.cpp | 65 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ba9fbd7b08..6b8921b09f 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4564,13 +4564,66 @@ void LLAppViewer::purgeCefStaleCaches() LL_PROFILE_ZONE_SCOPED; // TODO: we really shouldn't use a hard coded name for the cache folder here... const std::string browser_parent_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "cef_cache"); - if (LLFile::isdir(browser_parent_cache)) + if (!LLFile::isdir(browser_parent_cache)) { - // This is a sledgehammer approach - nukes the cef_cache dir entirely - // which is then recreated the first time a CEF instance creates an - // individual cache folder. If we ever decide to retain some folders - // e.g. Search UI cache - then we will need a more granular approach. - gDirUtilp->deleteDirAndContents(browser_parent_cache); + return; + } + // We are using a fixed name to not leave stale folders + // around in case something goes wrong on startup. + const std::string holder_cache_name = browser_parent_cache + "_rename"; + + // Try to rename the entire directory first + if (LLFile::rename(browser_parent_cache, holder_cache_name) == 0) + { + LL_DEBUGS("AppInit") << "Successfully renamed CEF cache folder for deletion" << LL_ENDL; + } + else + { + // Rename failed (likely another instance has files open in the cache) + // Create holder folder and move individual subfolders instead + LL_DEBUGS("AppInit") << "Could not rename CEF cache folder (may be in use), moving individual folders" << LL_ENDL; + + if (!LLFile::isdir(holder_cache_name) && LLFile::mkdir(holder_cache_name) != 0) + { + LL_WARNS() << "Failed to create holder folder: " << holder_cache_name << LL_ENDL; + // Attept normal cleanup + gDirUtilp->deleteDirAndContents(browser_parent_cache); + return; + } + + // Iterate through subdirectories in the cache folder + LLDirIterator dir_iter(browser_parent_cache, "*"); + std::string subfolder_name; + while (dir_iter.next(subfolder_name)) + { + if (subfolder_name == "." || subfolder_name == "..") + { + continue; + } + + std::string source_path = browser_parent_cache + gDirUtilp->getDirDelimiter() + subfolder_name; + std::string dest_path = holder_cache_name + gDirUtilp->getDirDelimiter() + subfolder_name; + + // If folder is in use, move will fail, don't delete it. + LLFile::rename(source_path, dest_path); + } + } + + // Post deletion task to the General work queue to avoid blocking the main thread + if (auto queue = LL::WorkQueue::getInstance("General")) + { + // Alternatively throw it at LLPurgeDiskCacheThread to clean + // it during periodic purges. + queue->post([holder_cache_name]() + { + LL_PROFILE_ZONE_NAMED("cef_cache_cleanup"); + gDirUtilp->deleteDirAndContents(holder_cache_name); + }); + } + else + { + LL_WARNS() << "Failed to get General work queue, deleting CEF cache synchronously" << LL_ENDL; + gDirUtilp->deleteDirAndContents(holder_cache_name); } } -- cgit v1.3