diff options
Diffstat (limited to 'indra/llcommon')
64 files changed, 26789 insertions, 26622 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 5f4ed2fffa..95e991c246 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -8,7 +8,6 @@ include(bugsplat) include(Linking) include(Boost) include(LLSharedLibs) -include(JsonCpp) include(Copy3rdPartyLibs) include(ZLIBNG) include(URIPARSER) @@ -194,6 +193,7 @@ set(llcommon_HEADER_FILES llmetrics.h llmetricperformancetester.h llmortician.h + llmutex.h llnametable.h llpointer.h llprofiler.h @@ -276,7 +276,6 @@ target_link_libraries( llcommon ll::apr ll::expat - ll::jsoncpp ll::zlib-ng ll::boost ll::uriparser diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp index 201eeed56b..2c1bc47d0e 100644 --- a/indra/llcommon/StackWalker.cpp +++ b/indra/llcommon/StackWalker.cpp @@ -1,5 +1,5 @@ /********************************************************************** - * + * * StackWalker.cpp * http://stackwalker.codeplex.com/ * @@ -13,14 +13,14 @@ * http://www.codeproject.com/threads/StackWalker.asp * 2005-07-28 v2 - Changed the params of the constructor and ShowCallstack * (to simplify the usage) - * 2005-08-01 v3 - Changed to use 'CONTEXT_FULL' instead of CONTEXT_ALL + * 2005-08-01 v3 - Changed to use 'CONTEXT_FULL' instead of CONTEXT_ALL * (should also be enough) * - Changed to compile correctly with the PSDK of VC7.0 * (GetFileVersionInfoSizeA and GetFileVersionInfoA is wrongly defined: * it uses LPSTR instead of LPCSTR as first paremeter) * - Added declarations to support VC5/6 without using 'dbghelp.h' - * - Added a 'pUserData' member to the ShowCallstack function and the - * PReadProcessMemoryRoutine declaration (to pass some user-defined data, + * - Added a 'pUserData' member to the ShowCallstack function and the + * PReadProcessMemoryRoutine declaration (to pass some user-defined data, * which can be used in the readMemoryFunction-callback) * 2005-08-02 v4 - OnSymInit now also outputs the OS-Version by default * - Added example for doing an exception-callstack-walking in main.cpp @@ -60,26 +60,26 @@ * Copyright (c) 2005-2013, Jochen Kalmbach * All rights reserved. * - * Redistribution and use in source and binary forms, with or without modification, + * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of Jochen Kalmbach nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of Jochen Kalmbach nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **********************************************************************/ @@ -234,7 +234,7 @@ DWORD64 // Some missing defines (for VC5/6): #ifndef INVALID_FILE_ATTRIBUTES #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) -#endif +#endif // secure-CRT_functions are only available starting with VC8 @@ -292,10 +292,10 @@ public: free(m_szSymPath); m_szSymPath = NULL; } - BOOL Init(LPCSTR szSymPath) + bool Init(LPCSTR szSymPath) { if (m_parent == NULL) - return FALSE; + return false; // Dynamically load the Entry-Points for dbghelp.dll: // First try to load the newsest one from TCHAR szTemp[4096]; @@ -364,7 +364,7 @@ public: if (m_hDbhHelp == NULL) // if not already loaded, try to load a default-one m_hDbhHelp = LoadLibrary( _T("dbghelp.dll") ); if (m_hDbhHelp == NULL) - return FALSE; + return false; pSI = (tSI) GetProcAddress(m_hDbhHelp, "SymInitialize" ); pSC = (tSC) GetProcAddress(m_hDbhHelp, "SymCleanup" ); @@ -388,7 +388,7 @@ public: FreeLibrary(m_hDbhHelp); m_hDbhHelp = NULL; pSC = NULL; - return FALSE; + return false; } // SymInitialize @@ -396,7 +396,7 @@ public: m_szSymPath = _strdup(szSymPath); if (this->pSI(m_hProcess, m_szSymPath, FALSE) == FALSE) this->m_parent->OnDbgHelpErr("SymInitialize", GetLastError(), 0); - + DWORD symOptions = this->pSGO(); // SymGetOptions symOptions |= SYMOPT_LOAD_LINES; symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; @@ -415,7 +415,7 @@ public: GetUserNameA(szUserName, &dwSize); this->m_parent->OnSymInit(buf, symOptions, szUserName); - return TRUE; + return true; } StackWalker *m_parent; @@ -512,11 +512,11 @@ struct IMAGEHLP_MODULE64_V2 { tSSO pSSO; // StackWalk64() - typedef BOOL (__stdcall *tSW)( - DWORD MachineType, + typedef BOOL (__stdcall *tSW)( + DWORD MachineType, HANDLE hProcess, - HANDLE hThread, - LPSTACKFRAME64 StackFrame, + HANDLE hThread, + LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, @@ -555,7 +555,7 @@ private: typedef MODULEENTRY32 * LPMODULEENTRY32; #pragma pack( pop ) - BOOL GetModuleListTH32(HANDLE hProcess, DWORD pid) + bool GetModuleListTH32(HANDLE hProcess, DWORD pid) { // CreateToolhelp32Snapshot() typedef HANDLE (__stdcall *tCT32S)(DWORD dwFlags, DWORD th32ProcessID); @@ -592,13 +592,13 @@ private: } if (hToolhelp == NULL) - return FALSE; + return false; hSnap = pCT32S( TH32CS_SNAPMODULE, pid ); if (hSnap == (HANDLE) -1) { FreeLibrary(hToolhelp); - return FALSE; + return false; } keepGoing = !!pM32F( hSnap, &me ); @@ -612,8 +612,8 @@ private: CloseHandle(hSnap); FreeLibrary(hToolhelp); if (cnt <= 0) - return FALSE; - return TRUE; + return false; + return true; } // GetModuleListTH32 // **************************************** PSAPI ************************ @@ -623,7 +623,7 @@ private: LPVOID EntryPoint; } MODULEINFO, *LPMODULEINFO; - BOOL GetModuleListPSAPI(HANDLE hProcess) + bool GetModuleListPSAPI(HANDLE hProcess) { // EnumProcessModules() typedef BOOL (__stdcall *tEPM)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded ); @@ -652,7 +652,7 @@ private: hPsapi = LoadLibrary( _T("psapi.dll") ); if (hPsapi == NULL) - return FALSE; + return false; pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" ); pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" ); @@ -662,7 +662,7 @@ private: { // we couldn't find all functions FreeLibrary(hPsapi); - return FALSE; + return false; } hMods = (HMODULE*) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof(HMODULE))); @@ -797,7 +797,7 @@ private: return result; } public: - BOOL LoadModules(HANDLE hProcess, DWORD dwProcessId) + bool LoadModules(HANDLE hProcess, DWORD dwProcessId) { // first try toolhelp32 if (GetModuleListTH32(hProcess, dwProcessId)) @@ -807,13 +807,13 @@ public: } - BOOL GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V3 *pModuleInfo) + bool GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V3 *pModuleInfo) { memset(pModuleInfo, 0, sizeof(IMAGEHLP_MODULE64_V3)); if(this->pSGMI == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } // First try to use the larger ModuleInfo-Structure pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); @@ -821,7 +821,7 @@ public: if (pData == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return FALSE; + return false; } memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V3)); static bool s_useV3Version = true; @@ -833,7 +833,7 @@ public: memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V3)); pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); free(pData); - return TRUE; + return true; } s_useV3Version = false; // to prevent unneccessarry calls with the larger struct... } @@ -847,11 +847,11 @@ public: memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V2)); pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); free(pData); - return TRUE; + return true; } free(pData); SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } }; @@ -860,7 +860,7 @@ StackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess) { this->m_verbose = true; this->m_options = OptionsAll; - this->m_modulesLoaded = FALSE; + this->m_modulesLoaded = false; this->m_hProcess = hProcess; this->m_sw = new StackWalkerInternal(this, this->m_hProcess); this->m_dwProcessId = dwProcessId; @@ -871,7 +871,7 @@ StackWalker::StackWalker(bool verbose, int options, LPCSTR szSymPath, DWORD dwPr { this->m_verbose = verbose; this->m_options = options; - this->m_modulesLoaded = FALSE; + this->m_modulesLoaded = false; this->m_hProcess = hProcess; this->m_sw = new StackWalkerInternal(this, this->m_hProcess); this->m_dwProcessId = dwProcessId; @@ -895,15 +895,15 @@ StackWalker::~StackWalker() this->m_sw = NULL; } -BOOL StackWalker::LoadModules() +bool StackWalker::LoadModules() { if (this->m_sw == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } if (m_modulesLoaded != FALSE) - return TRUE; + return true; // Build the sym-path: char *szSymPath = NULL; @@ -914,7 +914,7 @@ BOOL StackWalker::LoadModules() if (szSymPath == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return FALSE; + return false; } szSymPath[0] = 0; // Now first add the (optional) provided sympath: @@ -994,30 +994,30 @@ BOOL StackWalker::LoadModules() } // if SymBuildPath // First Init the whole stuff... - BOOL bRet = this->m_sw->Init(szSymPath); + bool bRet = this->m_sw->Init(szSymPath); if (szSymPath != NULL) free(szSymPath); szSymPath = NULL; - if (bRet == FALSE) + if (!bRet) { this->OnDbgHelpErr("Error while initializing dbghelp.dll", 0, 0); SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId); - if (bRet != FALSE) - m_modulesLoaded = TRUE; + if (bRet) + m_modulesLoaded = true; return bRet; } // The following is used to pass the "userData"-Pointer to the user-provided readMemoryFunction // This has to be done due to a problem with the "hProcess"-parameter in x64... -// Because this class is in no case multi-threading-enabled (because of the limitations +// Because this class is in no case multi-threading-enabled (because of the limitations // of dbghelp.dll) it is "safe" to use a static-variable static StackWalker::PReadProcessMemoryRoutine s_readMemoryFunction = NULL; static LPVOID s_readMemoryFunction_UserData = NULL; -BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData) +bool StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *context, PReadProcessMemoryRoutine readMemoryFunction, LPVOID pUserData) { m_verbose = verbose; CONTEXT c; @@ -1029,13 +1029,13 @@ BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con bool bLastEntryCalled = true; int curRecursionCount = 0; - if (m_modulesLoaded == FALSE) + if (!m_modulesLoaded) this->LoadModules(); // ignore the result... if (this->m_sw->m_hDbhHelp == NULL) { SetLastError(ERROR_DLL_INIT_FAILED); - return FALSE; + return false; } s_readMemoryFunction = readMemoryFunction; @@ -1062,7 +1062,7 @@ BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con if (GetThreadContext(hThread, &c) == FALSE) { ResumeThread(hThread); - return FALSE; + return false; } } } @@ -1222,7 +1222,7 @@ BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con csEntry.symTypeString = NULL; break; } - + MyStrCpy(csEntry.moduleName, STACKWALK_MAX_NAMELEN, Module.ModuleName); csEntry.baseOfImage = Module.BaseOfImage; MyStrCpy(csEntry.loadedImageName, STACKWALK_MAX_NAMELEN, Module.LoadedImageName); @@ -1243,7 +1243,7 @@ BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con et = firstEntry; bLastEntryCalled = false; this->OnCallstackEntry(et, csEntry); - + if (s.AddrReturn.Offset == 0) { bLastEntryCalled = true; @@ -1256,13 +1256,13 @@ BOOL StackWalker::ShowCallstack(bool verbose, HANDLE hThread, const CONTEXT *con cleanup: if (pSym) free( pSym ); - if (bLastEntryCalled == false) + if (!bLastEntryCalled) this->OnCallstackEntry(lastEntry, csEntry); if (context == NULL) ResumeThread(hThread); - return TRUE; + return true; } BOOL __stdcall StackWalker::myReadProcMem( @@ -1358,7 +1358,7 @@ void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUser ver.dwOSVersionInfoSize = sizeof(ver); if (GetVersionExA(&ver) != FALSE) { - _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n", + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion); if (m_verbose) @@ -1372,7 +1372,7 @@ void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUser ver.dwOSVersionInfoSize = sizeof(ver); if (GetVersionExA( (OSVERSIONINFOA*) &ver) != FALSE) { - _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion, ver.wSuiteMask, ver.wProductType); if (m_verbose) diff --git a/indra/llcommon/StackWalker.h b/indra/llcommon/StackWalker.h index 91cd55bbaf..28b10950e0 100644 --- a/indra/llcommon/StackWalker.h +++ b/indra/llcommon/StackWalker.h @@ -1,226 +1,226 @@ -/********************************************************************** - * - * StackWalker.h - * - * - * - * $LicenseInfo:firstyear=2016&license=bsd$ - * - * Linden notes: Small modifications from the original source at https://stackwalker.codeplex.com/ - * - * LICENSE (http://www.opensource.org/licenses/bsd-license.php) - * - * Copyright (c) 2005-2009, Jochen Kalmbach - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of Jochen Kalmbach nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * **********************************************************************/ - -#if LL_WINDOWS - -// #pragma once is supported starting with _MCS_VER 1000, -// so we need not to check the version (because we only support _MSC_VER >= 1100)! -#pragma once - -#include <windows.h> - -// special defines for VC5/6 (if no actual PSDK is installed): -#if _MSC_VER < 1300 -typedef unsigned __int64 DWORD64, *PDWORD64; -#if defined(_WIN64) -typedef unsigned __int64 SIZE_T, *PSIZE_T; -#else -typedef unsigned long SIZE_T, *PSIZE_T; -#endif -#endif // _MSC_VER < 1300 - -class StackWalkerInternal; // forward -class StackWalker -{ -public: - typedef enum StackWalkOptions - { - // No addition info will be retrived - // (only the address is available) - RetrieveNone = 0, - - // Try to get the symbol-name - RetrieveSymbol = 1, - - // Try to get the line for this symbol - RetrieveLine = 2, - - // Try to retrieve the module-infos - RetrieveModuleInfo = 4, - - // Also retrieve the version for the DLL/EXE - RetrieveFileVersion = 8, - - // Contains all the abouve - RetrieveVerbose = 0xF, - - // Generate a "good" symbol-search-path - SymBuildPath = 0x10, - - // Also use the public Microsoft-Symbol-Server - SymUseSymSrv = 0x20, - - // Contains all the abouve "Sym"-options - SymAll = 0x30, - - // Contains all options (default) - OptionsAll = 0x3F - } StackWalkOptions; - - StackWalker( - bool verbose = true, - int options = OptionsAll, // 'int' is by design, to combine the enum-flags - LPCSTR szSymPath = NULL, - DWORD dwProcessId = GetCurrentProcessId(), - HANDLE hProcess = GetCurrentProcess() - ); - StackWalker(DWORD dwProcessId, HANDLE hProcess); - virtual ~StackWalker(); - - typedef BOOL (__stdcall *PReadProcessMemoryRoutine)( - HANDLE hProcess, - DWORD64 qwBaseAddress, - PVOID lpBuffer, - DWORD nSize, - LPDWORD lpNumberOfBytesRead, - LPVOID pUserData // optional data, which was passed in "ShowCallstack" - ); - - BOOL LoadModules(); - - BOOL ShowCallstack( - bool verbose, - HANDLE hThread = GetCurrentThread(), - const CONTEXT *context = NULL, - PReadProcessMemoryRoutine readMemoryFunction = NULL, - LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback - ); - -#if _MSC_VER >= 1300 -// due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public" -// in older compilers in order to use it... starting with VC7 we can declare it as "protected" -protected: -#endif - enum { STACKWALK_MAX_NAMELEN = 4096 }; // max name length for found symbols - -protected: - // Entry for each Callstack-Entry - typedef struct CallstackEntry - { - DWORD64 offset; // if 0, we have no valid entry - CHAR name[STACKWALK_MAX_NAMELEN]; - CHAR undName[STACKWALK_MAX_NAMELEN]; - CHAR undFullName[STACKWALK_MAX_NAMELEN]; - DWORD64 offsetFromSmybol; - DWORD offsetFromLine; - DWORD lineNumber; - CHAR lineFileName[STACKWALK_MAX_NAMELEN]; - DWORD symType; - LPCSTR symTypeString; - CHAR moduleName[STACKWALK_MAX_NAMELEN]; - DWORD64 baseOfImage; - CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; - } CallstackEntry; - - enum CallstackEntryType {firstEntry, nextEntry, lastEntry}; - - virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName); - virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion); - virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry); - virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr); - virtual void OnOutput(LPCSTR szText); - - StackWalkerInternal *m_sw; - HANDLE m_hProcess; - DWORD m_dwProcessId; - BOOL m_modulesLoaded; - LPSTR m_szSymPath; - - bool m_verbose; - int m_options; - int m_MaxRecursionCount; - - static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead); - - friend StackWalkerInternal; -}; // class StackWalker - - -// The "ugly" assembler-implementation is needed for systems before XP -// If you have a new PSDK and you only compile for XP and later, then you can use -// the "RtlCaptureContext" -// Currently there is no define which determines the PSDK-Version... -// So we just use the compiler-version (and assumes that the PSDK is -// the one which was installed by the VS-IDE) - -// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later... -// But I currently use it in x64/IA64 environments... -//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400) - -#if defined(_M_IX86) -#ifdef CURRENT_THREAD_VIA_EXCEPTION -// TODO: The following is not a "good" implementation, -// because the callstack is only valid in the "__except" block... -#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ - do { \ - memset(&c, 0, sizeof(CONTEXT)); \ - EXCEPTION_POINTERS *pExp = NULL; \ - __try { \ - throw 0; \ - } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \ - if (pExp != NULL) \ - memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \ - c.ContextFlags = contextFlags; \ - } while(0); -#else -// The following should be enough for walking the callstack... -#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ - do { \ - memset(&c, 0, sizeof(CONTEXT)); \ - c.ContextFlags = contextFlags; \ - __asm call x \ - __asm x: pop eax \ - __asm mov c.Eip, eax \ - __asm mov c.Ebp, ebp \ - __asm mov c.Esp, esp \ - } while(0); -#endif - -#else - -// The following is defined for x86 (XP and higher), x64 and IA64: -#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ - do { \ - memset(&c, 0, sizeof(CONTEXT)); \ - c.ContextFlags = contextFlags; \ - RtlCaptureContext(&c); \ -} while(0); -#endif - -#endif // LL_WINDOWS +/**********************************************************************
+ *
+ * StackWalker.h
+ *
+ *
+ *
+ * $LicenseInfo:firstyear=2016&license=bsd$
+ *
+ * Linden notes: Small modifications from the original source at https://stackwalker.codeplex.com/
+ *
+ * LICENSE (http://www.opensource.org/licenses/bsd-license.php)
+ *
+ * Copyright (c) 2005-2009, Jochen Kalmbach
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * Neither the name of Jochen Kalmbach nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * **********************************************************************/
+
+#if LL_WINDOWS
+
+// #pragma once is supported starting with _MCS_VER 1000,
+// so we need not to check the version (because we only support _MSC_VER >= 1100)!
+#pragma once
+
+#include <windows.h>
+
+// special defines for VC5/6 (if no actual PSDK is installed):
+#if _MSC_VER < 1300
+typedef unsigned __int64 DWORD64, *PDWORD64;
+#if defined(_WIN64)
+typedef unsigned __int64 SIZE_T, *PSIZE_T;
+#else
+typedef unsigned long SIZE_T, *PSIZE_T;
+#endif
+#endif // _MSC_VER < 1300
+
+class StackWalkerInternal; // forward
+class StackWalker
+{
+public:
+ typedef enum StackWalkOptions
+ {
+ // No addition info will be retrived
+ // (only the address is available)
+ RetrieveNone = 0,
+
+ // Try to get the symbol-name
+ RetrieveSymbol = 1,
+
+ // Try to get the line for this symbol
+ RetrieveLine = 2,
+
+ // Try to retrieve the module-infos
+ RetrieveModuleInfo = 4,
+
+ // Also retrieve the version for the DLL/EXE
+ RetrieveFileVersion = 8,
+
+ // Contains all the abouve
+ RetrieveVerbose = 0xF,
+
+ // Generate a "good" symbol-search-path
+ SymBuildPath = 0x10,
+
+ // Also use the public Microsoft-Symbol-Server
+ SymUseSymSrv = 0x20,
+
+ // Contains all the abouve "Sym"-options
+ SymAll = 0x30,
+
+ // Contains all options (default)
+ OptionsAll = 0x3F
+ } StackWalkOptions;
+
+ StackWalker(
+ bool verbose = true,
+ int options = OptionsAll, // 'int' is by design, to combine the enum-flags
+ LPCSTR szSymPath = NULL,
+ DWORD dwProcessId = GetCurrentProcessId(),
+ HANDLE hProcess = GetCurrentProcess()
+ );
+ StackWalker(DWORD dwProcessId, HANDLE hProcess);
+ virtual ~StackWalker();
+
+ typedef BOOL (__stdcall *PReadProcessMemoryRoutine)(
+ HANDLE hProcess,
+ DWORD64 qwBaseAddress,
+ PVOID lpBuffer,
+ DWORD nSize,
+ LPDWORD lpNumberOfBytesRead,
+ LPVOID pUserData // optional data, which was passed in "ShowCallstack"
+ );
+
+ bool LoadModules();
+
+ bool ShowCallstack(
+ bool verbose,
+ HANDLE hThread = GetCurrentThread(),
+ const CONTEXT *context = NULL,
+ PReadProcessMemoryRoutine readMemoryFunction = NULL,
+ LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback
+ );
+
+#if _MSC_VER >= 1300
+// due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public"
+// in older compilers in order to use it... starting with VC7 we can declare it as "protected"
+protected:
+#endif
+ enum { STACKWALK_MAX_NAMELEN = 4096 }; // max name length for found symbols
+
+protected:
+ // Entry for each Callstack-Entry
+ typedef struct CallstackEntry
+ {
+ DWORD64 offset; // if 0, we have no valid entry
+ CHAR name[STACKWALK_MAX_NAMELEN];
+ CHAR undName[STACKWALK_MAX_NAMELEN];
+ CHAR undFullName[STACKWALK_MAX_NAMELEN];
+ DWORD64 offsetFromSmybol;
+ DWORD offsetFromLine;
+ DWORD lineNumber;
+ CHAR lineFileName[STACKWALK_MAX_NAMELEN];
+ DWORD symType;
+ LPCSTR symTypeString;
+ CHAR moduleName[STACKWALK_MAX_NAMELEN];
+ DWORD64 baseOfImage;
+ CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
+ } CallstackEntry;
+
+ enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
+
+ virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
+ virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);
+ virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry);
+ virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
+ virtual void OnOutput(LPCSTR szText);
+
+ StackWalkerInternal *m_sw;
+ HANDLE m_hProcess;
+ DWORD m_dwProcessId;
+ bool m_modulesLoaded;
+ LPSTR m_szSymPath;
+
+ bool m_verbose;
+ int m_options;
+ int m_MaxRecursionCount;
+
+ static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);
+
+ friend StackWalkerInternal;
+}; // class StackWalker
+
+
+// The "ugly" assembler-implementation is needed for systems before XP
+// If you have a new PSDK and you only compile for XP and later, then you can use
+// the "RtlCaptureContext"
+// Currently there is no define which determines the PSDK-Version...
+// So we just use the compiler-version (and assumes that the PSDK is
+// the one which was installed by the VS-IDE)
+
+// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later...
+// But I currently use it in x64/IA64 environments...
+//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400)
+
+#if defined(_M_IX86)
+#ifdef CURRENT_THREAD_VIA_EXCEPTION
+// TODO: The following is not a "good" implementation,
+// because the callstack is only valid in the "__except" block...
+#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
+ do { \
+ memset(&c, 0, sizeof(CONTEXT)); \
+ EXCEPTION_POINTERS *pExp = NULL; \
+ __try { \
+ throw 0; \
+ } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \
+ if (pExp != NULL) \
+ memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \
+ c.ContextFlags = contextFlags; \
+ } while(0);
+#else
+// The following should be enough for walking the callstack...
+#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
+ do { \
+ memset(&c, 0, sizeof(CONTEXT)); \
+ c.ContextFlags = contextFlags; \
+ __asm call x \
+ __asm x: pop eax \
+ __asm mov c.Eip, eax \
+ __asm mov c.Ebp, ebp \
+ __asm mov c.Esp, esp \
+ } while(0);
+#endif
+
+#else
+
+// The following is defined for x86 (XP and higher), x64 and IA64:
+#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
+ do { \
+ memset(&c, 0, sizeof(CONTEXT)); \
+ c.ContextFlags = contextFlags; \
+ RtlCaptureContext(&c); \
+} while(0);
+#endif
+
+#endif // LL_WINDOWS
diff --git a/indra/llcommon/is_approx_equal_fraction.h b/indra/llcommon/is_approx_equal_fraction.h index 79f4f5ebbd..732d168986 100644 --- a/indra/llcommon/is_approx_equal_fraction.h +++ b/indra/llcommon/is_approx_equal_fraction.h @@ -5,25 +5,25 @@ * @brief lltut.h uses is_approx_equal_fraction(). Moved to this header * file in llcommon so we can use lltut.h for llcommon tests without * making llcommon depend on llmath. - * + * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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$ */ @@ -43,9 +43,9 @@ * signatures. */ template <typename FTYPE> -inline BOOL is_approx_equal_fraction_impl(FTYPE x, FTYPE y, U32 frac_bits) +inline bool is_approx_equal_fraction_impl(FTYPE x, FTYPE y, U32 frac_bits) { - BOOL ret = TRUE; + bool ret = true; FTYPE diff = (FTYPE) fabs(x - y); S32 diffInt = (S32) diff; @@ -58,20 +58,20 @@ inline BOOL is_approx_equal_fraction_impl(FTYPE x, FTYPE y, U32 frac_bits) // based on the number of bits used for packing decimal portion. if (diffInt != 0 || diffFracTolerance > 1) { - ret = FALSE; + ret = false; } return ret; } /// F32 flavor -inline BOOL is_approx_equal_fraction(F32 x, F32 y, U32 frac_bits) +inline bool is_approx_equal_fraction(F32 x, F32 y, U32 frac_bits) { return is_approx_equal_fraction_impl<F32>(x, y, frac_bits); } /// F64 flavor -inline BOOL is_approx_equal_fraction(F64 x, F64 y, U32 frac_bits) +inline bool is_approx_equal_fraction(F64 x, F64 y, U32 frac_bits) { return is_approx_equal_fraction_impl<F64>(x, y, frac_bits); } diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 9729f68d23..b354a60bb0 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -1,854 +1,746 @@ -/** - * @file llapp.cpp - * @brief Implementation of the LLApp class. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llapp.h" - -#include <cstdlib> - -#ifdef LL_DARWIN -#include <sys/types.h> -#include <unistd.h> -#include <sys/sysctl.h> -#endif - -#include "llcommon.h" -#include "llapr.h" -#include "llerrorcontrol.h" -#include "llframetimer.h" -#include "lllivefile.h" -#include "llmemory.h" -#include "llstl.h" // for DeletePointer() -#include "llstring.h" -#include "lleventtimer.h" -#include "stringize.h" -#include "llcleanup.h" -#include "llevents.h" -#include "llsdutil.h" - -// -// Signal handling -// -// Windows uses structured exceptions, so it's handled a bit differently. -// -#if LL_WINDOWS -#include "windows.h" - -LONG WINAPI default_windows_exception_handler(struct _EXCEPTION_POINTERS *exception_infop); -BOOL ConsoleCtrlHandler(DWORD fdwCtrlType); -#else -# include <signal.h> -# include <unistd.h> // for fork() -void setup_signals(); -void default_unix_signal_handler(int signum, siginfo_t *info, void *); - -#if LL_LINUX -#else -// Called by breakpad exception handler after the minidump has been generated. -bool unix_post_minidump_callback(const char *dump_dir, - const char *minidump_id, - void *context, bool succeeded); -#endif - -# if LL_DARWIN -/* OSX doesn't support SIGRT* */ -S32 LL_SMACKDOWN_SIGNAL = SIGUSR1; -S32 LL_HEARTBEAT_SIGNAL = SIGUSR2; -# else // linux or (assumed) other similar unixoid -/* We want reliable delivery of our signals - SIGRT* is it. */ -/* Old LinuxThreads versions eat SIGRTMIN+0 to SIGRTMIN+2, avoid those. */ -/* Note that SIGRTMIN/SIGRTMAX may expand to a glibc function call with a - nonconstant result so these are not consts and cannot be used in constant- - expressions. SIGRTMAX may return -1 on rare broken setups. */ -S32 LL_SMACKDOWN_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-1) : SIGUSR1; -S32 LL_HEARTBEAT_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-0) : SIGUSR2; -# endif // LL_DARWIN -#endif // LL_WINDOWS - -// the static application instance -LLApp* LLApp::sApplication = NULL; - -// Allows the generation of core files for post mortem under gdb -// and disables crashlogger -BOOL LLApp::sDisableCrashlogger = FALSE; - -// Local flag for whether or not to do logging in signal handlers. -//static -BOOL LLApp::sLogInSignal = FALSE; - -// static -// Keeps track of application status -LLScalarCond<LLApp::EAppStatus> LLApp::sStatus{LLApp::APP_STATUS_STOPPED}; -LLAppErrorHandler LLApp::sErrorHandler = NULL; - - -LLApp::LLApp() -{ - // Set our status to running - setStatus(APP_STATUS_RUNNING); - - LLCommon::initClass(); - - // initialize the options structure. We need to make this an array - // because the structured data will not auto-allocate if we - // reference an invalid location with the [] operator. - mOptions = LLSD::emptyArray(); - LLSD sd; - for(int i = 0; i < PRIORITY_COUNT; ++i) - { - mOptions.append(sd); - } - - // Make sure we clean up APR when we exit - // Don't need to do this if we're cleaning up APR in the destructor - //atexit(ll_cleanup_apr); - - // Set the application to this instance. - sApplication = this; - - // initialize the buffer to write the minidump filename to - // (this is used to avoid allocating memory in the crash handler) - memset(mMinidumpPath, 0, MAX_MINDUMP_PATH_LENGTH); - mCrashReportPipeStr = L"\\\\.\\pipe\\LLCrashReporterPipe"; -} - - -LLApp::~LLApp() -{ - - // reclaim live file memory - std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer()); - mLiveFiles.clear(); - - setStopped(); - - SUBSYSTEM_CLEANUP_DBG(LLCommon); -} - -// static -LLApp* LLApp::instance() -{ - return sApplication; -} - - -LLSD LLApp::getOption(const std::string& name) const -{ - LLSD rv; - LLSD::array_const_iterator iter = mOptions.beginArray(); - LLSD::array_const_iterator end = mOptions.endArray(); - for(; iter != end; ++iter) - { - rv = (*iter)[name]; - if(rv.isDefined()) break; - } - return rv; -} - -bool LLApp::parseCommandOptions(int argc, char** argv) -{ - LLSD commands; - std::string name; - std::string value; - for(int ii = 1; ii < argc; ++ii) - { - if(argv[ii][0] != '-') - { - LL_INFOS() << "Did not find option identifier while parsing token: " - << argv[ii] << LL_ENDL; - return false; - } - int offset = 1; - if(argv[ii][1] == '-') ++offset; - name.assign(&argv[ii][offset]); - if(((ii+1) >= argc) || (argv[ii+1][0] == '-')) - { - // we found another option after this one or we have - // reached the end. simply record that this option was - // found and continue. - int flag = name.compare("logfile"); - if (0 == flag) - { - commands[name] = "log"; - } - else - { - commands[name] = true; - } - - continue; - } - ++ii; - value.assign(argv[ii]); - -#if LL_WINDOWS - //Windows changed command line parsing. Deal with it. - S32 slen = value.length() - 1; - S32 start = 0; - S32 end = slen; - if (argv[ii][start]=='"')start++; - if (argv[ii][end]=='"')end--; - if (start!=0 || end!=slen) - { - value = value.substr (start,end); - } -#endif - - commands[name] = value; - } - setOptionData(PRIORITY_COMMAND_LINE, commands); - return true; -} - -bool LLApp::parseCommandOptions(int argc, wchar_t** wargv) -{ - LLSD commands; - std::string name; - std::string value; - for(int ii = 1; ii < argc; ++ii) - { - if(wargv[ii][0] != '-') - { - LL_INFOS() << "Did not find option identifier while parsing token: " - << wargv[ii] << LL_ENDL; - return false; - } - int offset = 1; - if(wargv[ii][1] == '-') ++offset; - -#if LL_WINDOWS - name.assign(utf16str_to_utf8str(&wargv[ii][offset])); -#else - name.assign(wstring_to_utf8str(&wargv[ii][offset])); -#endif - if(((ii+1) >= argc) || (wargv[ii+1][0] == '-')) - { - // we found another option after this one or we have - // reached the end. simply record that this option was - // found and continue. - int flag = name.compare("logfile"); - if (0 == flag) - { - commands[name] = "log"; - } - else - { - commands[name] = true; - } - - continue; - } - ++ii; - -#if LL_WINDOWS - value.assign(utf16str_to_utf8str((wargv[ii]))); -#else - value.assign(wstring_to_utf8str((wargv[ii]))); -#endif - -#if LL_WINDOWS - //Windows changed command line parsing. Deal with it. - S32 slen = value.length() - 1; - S32 start = 0; - S32 end = slen; - if (wargv[ii][start]=='"')start++; - if (wargv[ii][end]=='"')end--; - if (start!=0 || end!=slen) - { - value = value.substr (start,end); - } -#endif - - commands[name] = value; - } - setOptionData(PRIORITY_COMMAND_LINE, commands); - return true; -} - -void LLApp::manageLiveFile(LLLiveFile* livefile) -{ - if(!livefile) return; - livefile->checkAndReload(); - livefile->addToEventTimer(); - mLiveFiles.push_back(livefile); -} - -bool LLApp::setOptionData(OptionPriority level, LLSD data) -{ - if((level < 0) - || (level >= PRIORITY_COUNT) - || (data.type() != LLSD::TypeMap)) - { - return false; - } - mOptions[level] = data; - return true; -} - -LLSD LLApp::getOptionData(OptionPriority level) -{ - if((level < 0) || (level >= PRIORITY_COUNT)) - { - return LLSD(); - } - return mOptions[level]; -} - -void LLApp::stepFrame() -{ - LLFrameTimer::updateFrameTime(); - LLFrameTimer::updateFrameCount(); - LLEventTimer::updateClass(); - mRunner.run(); -} - -#if LL_WINDOWS -//The following code is needed for 32-bit apps on 64-bit windows to keep it from eating -//crashes. It is a lovely undocumented 'feature' in SP1 of Windows 7. An excellent -//in-depth article on the issue may be found here: http://randomascii.wordpress.com/2012/07/05/when-even-crashing-doesn-work/ -void EnableCrashingOnCrashes() -{ - typedef BOOL (WINAPI *tGetPolicy)(LPDWORD lpFlags); - typedef BOOL (WINAPI *tSetPolicy)(DWORD dwFlags); - const DWORD EXCEPTION_SWALLOWING = 0x1; - - HMODULE kernel32 = LoadLibraryA("kernel32.dll"); - tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32, - "GetProcessUserModeExceptionPolicy"); - tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32, - "SetProcessUserModeExceptionPolicy"); - if (pGetPolicy && pSetPolicy) - { - DWORD dwFlags; - if (pGetPolicy(&dwFlags)) - { - // Turn off the filter - pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING); - } - } -} -#endif - -void LLApp::setupErrorHandling(bool second_instance) -{ - // Error handling is done by starting up an error handling thread, which just sleeps and - // occasionally checks to see if the app is in an error state, and sees if it needs to be run. - -#if LL_WINDOWS - -#else // ! LL_WINDOWS - -#if ! defined(LL_BUGSPLAT) - // - // Start up signal handling. - // - // There are two different classes of signals. Synchronous signals are delivered to a specific - // thread, asynchronous signals can be delivered to any thread (in theory) - // - setup_signals(); -#endif // ! LL_BUGSPLAT - -#endif // ! LL_WINDOWS -} - -void LLApp::setErrorHandler(LLAppErrorHandler handler) -{ - LLApp::sErrorHandler = handler; -} - -// static -void LLApp::runErrorHandler() -{ - if (LLApp::sErrorHandler) - { - LLApp::sErrorHandler(); - } - - //LL_INFOS() << "App status now STOPPED" << LL_ENDL; - LLApp::setStopped(); -} - -namespace -{ - -static std::map<LLApp::EAppStatus, const char*> statusDesc -{ - { LLApp::APP_STATUS_RUNNING, "running" }, - { LLApp::APP_STATUS_QUITTING, "quitting" }, - { LLApp::APP_STATUS_STOPPED, "stopped" }, - { LLApp::APP_STATUS_ERROR, "error" } -}; - -} // anonymous namespace - -// static -void LLApp::setStatus(EAppStatus status) -{ - // notify everyone waiting on sStatus any time its value changes - sStatus.set_all(status); - - // This can also happen very late in the application lifecycle -- don't - // resurrect a deleted LLSingleton - if (! LLEventPumps::wasDeleted()) - { - // notify interested parties of status change - LLSD statsd; - auto found = statusDesc.find(status); - if (found != statusDesc.end()) - { - statsd = found->second; - } - else - { - // unknown status? at least report value - statsd = LLSD::Integer(status); - } - LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd)); - } -} - - -// static -void LLApp::setError() -{ - // set app status to ERROR - setStatus(APP_STATUS_ERROR); -} - -void LLApp::setDebugFileNames(const std::string &path) -{ - mStaticDebugFileName = path + "static_debug_info.log"; - mDynamicDebugFileName = path + "dynamic_debug_info.log"; -} - -void LLApp::writeMiniDump() -{ -} - -// static -void LLApp::setQuitting() -{ - if (!isExiting()) - { - // If we're already exiting, we don't want to reset our state back to quitting. - LL_INFOS() << "Setting app state to QUITTING" << LL_ENDL; - setStatus(APP_STATUS_QUITTING); - } -} - - -// static -void LLApp::setStopped() -{ - setStatus(APP_STATUS_STOPPED); -} - - -// static -bool LLApp::isStopped() -{ - return (APP_STATUS_STOPPED == sStatus.get()); -} - - -// static -bool LLApp::isRunning() -{ - return (APP_STATUS_RUNNING == sStatus.get()); -} - - -// static -bool LLApp::isError() -{ - return (APP_STATUS_ERROR == sStatus.get()); -} - - -// static -bool LLApp::isQuitting() -{ - return (APP_STATUS_QUITTING == sStatus.get()); -} - -// static -bool LLApp::isExiting() -{ - return isQuitting() || isError(); -} - -void LLApp::disableCrashlogger() -{ - sDisableCrashlogger = TRUE; -} - -// static -bool LLApp::isCrashloggerDisabled() -{ - return (sDisableCrashlogger == TRUE); -} - -// static -int LLApp::getPid() -{ -#if LL_WINDOWS - return GetCurrentProcessId(); -#else - return getpid(); -#endif -} - -#if LL_WINDOWS -LONG WINAPI default_windows_exception_handler(struct _EXCEPTION_POINTERS *exception_infop) -{ - // Translate the signals/exceptions into cross-platform stuff - // Windows implementation - - // Make sure the user sees something to indicate that the app crashed. - LONG retval; - - if (LLApp::isError()) - { - LL_WARNS() << "Got another fatal signal while in the error handler, die now!" << LL_ENDL; - retval = EXCEPTION_EXECUTE_HANDLER; - return retval; - } - - // Flag status to error, so thread_error starts its work - LLApp::setError(); - - // Block in the exception handler until the app has stopped - // This is pretty sketchy, but appears to work just fine - while (!LLApp::isStopped()) - { - ms_sleep(10); - } - - // - // Generate a minidump if we can. - // - // TODO: This needs to be ported over form the viewer-specific - // LLWinDebug class - - // - // At this point, we always want to exit the app. There's no graceful - // recovery for an unhandled exception. - // - // Just kill the process. - retval = EXCEPTION_EXECUTE_HANDLER; - return retval; -} - -// Win32 doesn't support signals. This is used instead. -BOOL ConsoleCtrlHandler(DWORD fdwCtrlType) -{ - switch (fdwCtrlType) - { - case CTRL_BREAK_EVENT: - case CTRL_LOGOFF_EVENT: - case CTRL_SHUTDOWN_EVENT: - case CTRL_CLOSE_EVENT: // From end task or the window close button. - case CTRL_C_EVENT: // from CTRL-C on the keyboard - // Just set our state to quitting, not error - if (LLApp::isQuitting() || LLApp::isError()) - { - // We're already trying to die, just ignore this signal - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL; - } - return TRUE; - } - LLApp::setQuitting(); - return TRUE; - - default: - return FALSE; - } -} - -#else //!LL_WINDOWS - -void setup_signals() -{ - // - // Set up signal handlers that may result in program termination - // - struct sigaction act; - act.sa_sigaction = default_unix_signal_handler; - sigemptyset( &act.sa_mask ); - act.sa_flags = SA_SIGINFO; - - // Synchronous signals -# ifndef LL_BUGSPLAT - sigaction(SIGABRT, &act, NULL); -# endif - sigaction(SIGALRM, &act, NULL); - sigaction(SIGBUS, &act, NULL); - sigaction(SIGFPE, &act, NULL); - sigaction(SIGHUP, &act, NULL); - sigaction(SIGILL, &act, NULL); - sigaction(SIGPIPE, &act, NULL); - sigaction(SIGSEGV, &act, NULL); - sigaction(SIGSYS, &act, NULL); - - sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL); - sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); - - // Asynchronous signals that are normally ignored -#ifndef LL_IGNORE_SIGCHLD - sigaction(SIGCHLD, &act, NULL); -#endif // LL_IGNORE_SIGCHLD - sigaction(SIGUSR2, &act, NULL); - - // Asynchronous signals that result in attempted graceful exit - sigaction(SIGHUP, &act, NULL); - sigaction(SIGTERM, &act, NULL); - sigaction(SIGINT, &act, NULL); - - // Asynchronous signals that result in core - sigaction(SIGQUIT, &act, NULL); - -} - -void clear_signals() -{ - struct sigaction act; - act.sa_handler = SIG_DFL; - sigemptyset( &act.sa_mask ); - act.sa_flags = SA_SIGINFO; - - // Synchronous signals -# ifndef LL_BUGSPLAT - sigaction(SIGABRT, &act, NULL); -# endif - sigaction(SIGALRM, &act, NULL); - sigaction(SIGBUS, &act, NULL); - sigaction(SIGFPE, &act, NULL); - sigaction(SIGHUP, &act, NULL); - sigaction(SIGILL, &act, NULL); - sigaction(SIGPIPE, &act, NULL); - sigaction(SIGSEGV, &act, NULL); - sigaction(SIGSYS, &act, NULL); - - sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL); - sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); - - // Asynchronous signals that are normally ignored -#ifndef LL_IGNORE_SIGCHLD - sigaction(SIGCHLD, &act, NULL); -#endif // LL_IGNORE_SIGCHLD - - // Asynchronous signals that result in attempted graceful exit - sigaction(SIGHUP, &act, NULL); - sigaction(SIGTERM, &act, NULL); - sigaction(SIGINT, &act, NULL); - - // Asynchronous signals that result in core - sigaction(SIGUSR2, &act, NULL); - sigaction(SIGQUIT, &act, NULL); -} - - - -void default_unix_signal_handler(int signum, siginfo_t *info, void *) -{ - // Unix implementation of synchronous signal handler - // This runs in the thread that threw the signal. - // We do the somewhat sketchy operation of blocking in here until the error handler - // has gracefully stopped the app. - - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL; - } - - - switch (signum) - { - case SIGCHLD: - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL; - } - - return; - case SIGABRT: - // Note that this handler is not set for SIGABRT when using Bugsplat - // Abort just results in termination of the app, no funky error handling. - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Got SIGABRT, terminating" << LL_ENDL; - } - clear_signals(); - raise(signum); - return; - case SIGINT: - case SIGHUP: - case SIGTERM: - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL; - } - // Graceful exit - // Just set our state to quitting, not error - if (LLApp::isQuitting() || LLApp::isError()) - { - // We're already trying to die, just ignore this signal - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL; - } - return; - } - LLApp::setQuitting(); - return; - case SIGALRM: - case SIGPIPE: - case SIGUSR2: - default: - if (signum == LL_SMACKDOWN_SIGNAL || - signum == SIGBUS || - signum == SIGILL || - signum == SIGFPE || - signum == SIGSEGV || - signum == SIGQUIT) - { - if (signum == LL_SMACKDOWN_SIGNAL) - { - // Smackdown treated just like any other app termination, for now - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Handling smackdown signal!" << LL_ENDL; - } - else - { - // Don't log anything, even errors - this is because this signal could happen anywhere. - LLError::setDefaultLevel(LLError::LEVEL_NONE); - } - - // Change the signal that we reraise to SIGABRT, so we generate a core dump. - signum = SIGABRT; - } - - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL; - } - if (LLApp::isError()) - { - // Received second fatal signal while handling first, just die right now - // Set the signal handlers back to default before handling the signal - this makes the next signal wipe out the app. - clear_signals(); - - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Got another fatal signal while in the error handler, die now!" << LL_ENDL; - } - raise(signum); - return; - } - - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - Flagging error status and waiting for shutdown" << LL_ENDL; - } - - if (LLApp::isCrashloggerDisabled()) // Don't gracefully handle any signal, crash and core for a gdb post mortem - { - clear_signals(); - LL_WARNS() << "Fatal signal received, not handling the crash here, passing back to operating system" << LL_ENDL; - raise(signum); - return; - } - - // Flag status to ERROR - LLApp::setError(); - - if (LLApp::sLogInSignal) - { - LL_WARNS() << "Signal handler - App is stopped, reraising signal" << LL_ENDL; - } - clear_signals(); - raise(signum); - return; - } else { - if (LLApp::sLogInSignal) - { - LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL; - } - } - } -} - -#if LL_LINUX -#endif - -bool unix_post_minidump_callback(const char *dump_dir, - const char *minidump_id, - void *context, bool succeeded) -{ - // Copy minidump file path into fixed buffer in the app instance to avoid - // heap allocations in a crash handler. - - // path format: <dump_dir>/<minidump_id>.dmp - auto dirPathLength = strlen(dump_dir); - auto idLength = strlen(minidump_id); - - // The path must not be truncated. - llassert((dirPathLength + idLength + 5) <= LLApp::MAX_MINDUMP_PATH_LENGTH); - - char * path = LLApp::instance()->getMiniDumpFilename(); - auto remaining = LLApp::MAX_MINDUMP_PATH_LENGTH; - strncpy(path, dump_dir, remaining); - remaining -= dirPathLength; - path += dirPathLength; - if (remaining > 0 && dirPathLength > 0 && path[-1] != '/') - { - *path++ = '/'; - --remaining; - } - if (remaining > 0) - { - strncpy(path, minidump_id, remaining); - remaining -= idLength; - path += idLength; - strncpy(path, ".dmp", remaining); - } - - LL_INFOS("CRASHREPORT") << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << LL_ENDL; - LLApp::runErrorHandler(); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - clear_signals(); - return false; -#else - return true; -#endif -} -#endif // !WINDOWS - +/**
+ * @file llapp.cpp
+ * @brief Implementation of the LLApp class.
+ *
+ * $LicenseInfo:firstyear=2003&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llapp.h"
+
+#include <cstdlib>
+
+#ifdef LL_DARWIN
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/sysctl.h>
+#endif
+
+#include "llcommon.h"
+#include "llapr.h"
+#include "llerrorcontrol.h"
+#include "llframetimer.h"
+#include "lllivefile.h"
+#include "llmemory.h"
+#include "llstl.h" // for DeletePointer()
+#include "llstring.h"
+#include "lleventtimer.h"
+#include "stringize.h"
+#include "llcleanup.h"
+#include "llevents.h"
+#include "llsdutil.h"
+
+//
+// Signal handling
+#ifndef LL_WINDOWS
+# include <signal.h>
+# include <unistd.h> // for fork()
+void setup_signals();
+void default_unix_signal_handler(int signum, siginfo_t *info, void *);
+
+#if LL_LINUX
+#else
+// Called by breakpad exception handler after the minidump has been generated.
+bool unix_post_minidump_callback(const char *dump_dir,
+ const char *minidump_id,
+ void *context, bool succeeded);
+#endif
+
+# if LL_DARWIN
+/* OSX doesn't support SIGRT* */
+S32 LL_SMACKDOWN_SIGNAL = SIGUSR1;
+S32 LL_HEARTBEAT_SIGNAL = SIGUSR2;
+# else // linux or (assumed) other similar unixoid
+/* We want reliable delivery of our signals - SIGRT* is it. */
+/* Old LinuxThreads versions eat SIGRTMIN+0 to SIGRTMIN+2, avoid those. */
+/* Note that SIGRTMIN/SIGRTMAX may expand to a glibc function call with a
+ nonconstant result so these are not consts and cannot be used in constant-
+ expressions. SIGRTMAX may return -1 on rare broken setups. */
+S32 LL_SMACKDOWN_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-1) : SIGUSR1;
+S32 LL_HEARTBEAT_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-0) : SIGUSR2;
+# endif // LL_DARWIN
+#endif // !LL_WINDOWS
+
+// the static application instance
+LLApp* LLApp::sApplication = NULL;
+
+// Allows the generation of core files for post mortem under gdb
+// and disables crashlogger
+bool LLApp::sDisableCrashlogger = false;
+
+// Local flag for whether or not to do logging in signal handlers.
+//static
+bool LLApp::sLogInSignal = false;
+
+// static
+// Keeps track of application status
+LLScalarCond<LLApp::EAppStatus> LLApp::sStatus{LLApp::APP_STATUS_STOPPED};
+LLAppErrorHandler LLApp::sErrorHandler = NULL;
+
+
+LLApp::LLApp()
+{
+ // Set our status to running
+ setStatus(APP_STATUS_RUNNING);
+
+ LLCommon::initClass();
+
+ // initialize the options structure. We need to make this an array
+ // because the structured data will not auto-allocate if we
+ // reference an invalid location with the [] operator.
+ mOptions = LLSD::emptyArray();
+ LLSD sd;
+ for(int i = 0; i < PRIORITY_COUNT; ++i)
+ {
+ mOptions.append(sd);
+ }
+
+ // Make sure we clean up APR when we exit
+ // Don't need to do this if we're cleaning up APR in the destructor
+ //atexit(ll_cleanup_apr);
+
+ // Set the application to this instance.
+ sApplication = this;
+
+ // initialize the buffer to write the minidump filename to
+ // (this is used to avoid allocating memory in the crash handler)
+ memset(mMinidumpPath, 0, MAX_MINDUMP_PATH_LENGTH);
+ mCrashReportPipeStr = L"\\\\.\\pipe\\LLCrashReporterPipe";
+}
+
+
+LLApp::~LLApp()
+{
+
+ // reclaim live file memory
+ std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer());
+ mLiveFiles.clear();
+
+ setStopped();
+
+ SUBSYSTEM_CLEANUP_DBG(LLCommon);
+}
+
+// static
+LLApp* LLApp::instance()
+{
+ return sApplication;
+}
+
+
+LLSD LLApp::getOption(const std::string& name) const
+{
+ LLSD rv;
+ LLSD::array_const_iterator iter = mOptions.beginArray();
+ LLSD::array_const_iterator end = mOptions.endArray();
+ for(; iter != end; ++iter)
+ {
+ rv = (*iter)[name];
+ if(rv.isDefined()) break;
+ }
+ return rv;
+}
+
+bool LLApp::parseCommandOptions(int argc, char** argv)
+{
+ LLSD commands;
+ std::string name;
+ std::string value;
+ for(int ii = 1; ii < argc; ++ii)
+ {
+ if(argv[ii][0] != '-')
+ {
+ LL_INFOS() << "Did not find option identifier while parsing token: "
+ << argv[ii] << LL_ENDL;
+ return false;
+ }
+ int offset = 1;
+ if(argv[ii][1] == '-') ++offset;
+ name.assign(&argv[ii][offset]);
+ if(((ii+1) >= argc) || (argv[ii+1][0] == '-'))
+ {
+ // we found another option after this one or we have
+ // reached the end. simply record that this option was
+ // found and continue.
+ int flag = name.compare("logfile");
+ if (0 == flag)
+ {
+ commands[name] = "log";
+ }
+ else
+ {
+ commands[name] = true;
+ }
+
+ continue;
+ }
+ ++ii;
+ value.assign(argv[ii]);
+
+#if LL_WINDOWS
+ //Windows changed command line parsing. Deal with it.
+ S32 slen = value.length() - 1;
+ S32 start = 0;
+ S32 end = slen;
+ if (argv[ii][start]=='"')start++;
+ if (argv[ii][end]=='"')end--;
+ if (start!=0 || end!=slen)
+ {
+ value = value.substr (start,end);
+ }
+#endif
+
+ commands[name] = value;
+ }
+ setOptionData(PRIORITY_COMMAND_LINE, commands);
+ return true;
+}
+
+bool LLApp::parseCommandOptions(int argc, wchar_t** wargv)
+{
+ LLSD commands;
+ std::string name;
+ std::string value;
+ for(int ii = 1; ii < argc; ++ii)
+ {
+ if(wargv[ii][0] != '-')
+ {
+ LL_INFOS() << "Did not find option identifier while parsing token: "
+ << wargv[ii] << LL_ENDL;
+ return false;
+ }
+ int offset = 1;
+ if(wargv[ii][1] == '-') ++offset;
+
+#if LL_WINDOWS
+ name.assign(utf16str_to_utf8str(&wargv[ii][offset]));
+#else
+ name.assign(wstring_to_utf8str(&wargv[ii][offset]));
+#endif
+ if(((ii+1) >= argc) || (wargv[ii+1][0] == '-'))
+ {
+ // we found another option after this one or we have
+ // reached the end. simply record that this option was
+ // found and continue.
+ int flag = name.compare("logfile");
+ if (0 == flag)
+ {
+ commands[name] = "log";
+ }
+ else
+ {
+ commands[name] = true;
+ }
+
+ continue;
+ }
+ ++ii;
+
+#if LL_WINDOWS
+ value.assign(utf16str_to_utf8str((wargv[ii])));
+#else
+ value.assign(wstring_to_utf8str((wargv[ii])));
+#endif
+
+#if LL_WINDOWS
+ //Windows changed command line parsing. Deal with it.
+ S32 slen = value.length() - 1;
+ S32 start = 0;
+ S32 end = slen;
+ if (wargv[ii][start]=='"')start++;
+ if (wargv[ii][end]=='"')end--;
+ if (start!=0 || end!=slen)
+ {
+ value = value.substr (start,end);
+ }
+#endif
+
+ commands[name] = value;
+ }
+ setOptionData(PRIORITY_COMMAND_LINE, commands);
+ return true;
+}
+
+void LLApp::manageLiveFile(LLLiveFile* livefile)
+{
+ if(!livefile) return;
+ livefile->checkAndReload();
+ livefile->addToEventTimer();
+ mLiveFiles.push_back(livefile);
+}
+
+bool LLApp::setOptionData(OptionPriority level, LLSD data)
+{
+ if((level < 0)
+ || (level >= PRIORITY_COUNT)
+ || (data.type() != LLSD::TypeMap))
+ {
+ return false;
+ }
+ mOptions[level] = data;
+ return true;
+}
+
+LLSD LLApp::getOptionData(OptionPriority level)
+{
+ if((level < 0) || (level >= PRIORITY_COUNT))
+ {
+ return LLSD();
+ }
+ return mOptions[level];
+}
+
+void LLApp::stepFrame()
+{
+ LLFrameTimer::updateFrameTime();
+ LLFrameTimer::updateFrameCount();
+ LLEventTimer::updateClass();
+ mRunner.run();
+}
+
+void LLApp::setupErrorHandling(bool second_instance)
+{
+ // Error handling is done by starting up an error handling thread, which just sleeps and
+ // occasionally checks to see if the app is in an error state, and sees if it needs to be run.
+
+#if LL_WINDOWS
+
+#else // ! LL_WINDOWS
+
+#if ! defined(LL_BUGSPLAT)
+ //
+ // Start up signal handling.
+ //
+ // There are two different classes of signals. Synchronous signals are delivered to a specific
+ // thread, asynchronous signals can be delivered to any thread (in theory)
+ //
+ setup_signals();
+#endif // ! LL_BUGSPLAT
+
+#endif // ! LL_WINDOWS
+}
+
+void LLApp::setErrorHandler(LLAppErrorHandler handler)
+{
+ LLApp::sErrorHandler = handler;
+}
+
+// static
+void LLApp::runErrorHandler()
+{
+ if (LLApp::sErrorHandler)
+ {
+ LLApp::sErrorHandler();
+ }
+
+ //LL_INFOS() << "App status now STOPPED" << LL_ENDL;
+ LLApp::setStopped();
+}
+
+namespace
+{
+
+static std::map<LLApp::EAppStatus, const char*> statusDesc
+{
+ { LLApp::APP_STATUS_RUNNING, "running" },
+ { LLApp::APP_STATUS_QUITTING, "quitting" },
+ { LLApp::APP_STATUS_STOPPED, "stopped" },
+ { LLApp::APP_STATUS_ERROR, "error" }
+};
+
+} // anonymous namespace
+
+// static
+void LLApp::setStatus(EAppStatus status)
+{
+ // notify everyone waiting on sStatus any time its value changes
+ sStatus.set_all(status);
+
+ // This can also happen very late in the application lifecycle -- don't
+ // resurrect a deleted LLSingleton
+ if (! LLEventPumps::wasDeleted())
+ {
+ // notify interested parties of status change
+ LLSD statsd;
+ auto found = statusDesc.find(status);
+ if (found != statusDesc.end())
+ {
+ statsd = found->second;
+ }
+ else
+ {
+ // unknown status? at least report value
+ statsd = LLSD::Integer(status);
+ }
+ LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd));
+ }
+}
+
+
+// static
+void LLApp::setError()
+{
+ // set app status to ERROR
+ setStatus(APP_STATUS_ERROR);
+}
+
+void LLApp::setDebugFileNames(const std::string &path)
+{
+ mStaticDebugFileName = path + "static_debug_info.log";
+ mDynamicDebugFileName = path + "dynamic_debug_info.log";
+}
+
+void LLApp::writeMiniDump()
+{
+}
+
+// static
+void LLApp::setQuitting()
+{
+ if (!isExiting())
+ {
+ // If we're already exiting, we don't want to reset our state back to quitting.
+ LL_INFOS() << "Setting app state to QUITTING" << LL_ENDL;
+ setStatus(APP_STATUS_QUITTING);
+ }
+}
+
+
+// static
+void LLApp::setStopped()
+{
+ setStatus(APP_STATUS_STOPPED);
+}
+
+
+// static
+bool LLApp::isStopped()
+{
+ return (APP_STATUS_STOPPED == sStatus.get());
+}
+
+
+// static
+bool LLApp::isRunning()
+{
+ return (APP_STATUS_RUNNING == sStatus.get());
+}
+
+
+// static
+bool LLApp::isError()
+{
+ return (APP_STATUS_ERROR == sStatus.get());
+}
+
+
+// static
+bool LLApp::isQuitting()
+{
+ return (APP_STATUS_QUITTING == sStatus.get());
+}
+
+// static
+bool LLApp::isExiting()
+{
+ return isQuitting() || isError();
+}
+
+void LLApp::disableCrashlogger()
+{
+ sDisableCrashlogger = true;
+}
+
+// static
+bool LLApp::isCrashloggerDisabled()
+{
+ return sDisableCrashlogger;
+}
+
+// static
+int LLApp::getPid()
+{
+#if LL_WINDOWS
+ return GetCurrentProcessId();
+#else
+ return getpid();
+#endif
+}
+
+#ifndef LL_WINDOWS
+void setup_signals()
+{
+ //
+ // Set up signal handlers that may result in program termination
+ //
+ struct sigaction act;
+ act.sa_sigaction = default_unix_signal_handler;
+ sigemptyset( &act.sa_mask );
+ act.sa_flags = SA_SIGINFO;
+
+ // Synchronous signals
+# ifndef LL_BUGSPLAT
+ sigaction(SIGABRT, &act, NULL);
+# endif
+ sigaction(SIGALRM, &act, NULL);
+ sigaction(SIGBUS, &act, NULL);
+ sigaction(SIGFPE, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGILL, &act, NULL);
+ sigaction(SIGPIPE, &act, NULL);
+ sigaction(SIGSEGV, &act, NULL);
+ sigaction(SIGSYS, &act, NULL);
+
+ sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL);
+ sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL);
+
+ // Asynchronous signals that are normally ignored
+#ifndef LL_IGNORE_SIGCHLD
+ sigaction(SIGCHLD, &act, NULL);
+#endif // LL_IGNORE_SIGCHLD
+ sigaction(SIGUSR2, &act, NULL);
+
+ // Asynchronous signals that result in attempted graceful exit
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGINT, &act, NULL);
+
+ // Asynchronous signals that result in core
+ sigaction(SIGQUIT, &act, NULL);
+
+}
+
+void clear_signals()
+{
+ struct sigaction act;
+ act.sa_handler = SIG_DFL;
+ sigemptyset( &act.sa_mask );
+ act.sa_flags = SA_SIGINFO;
+
+ // Synchronous signals
+# ifndef LL_BUGSPLAT
+ sigaction(SIGABRT, &act, NULL);
+# endif
+ sigaction(SIGALRM, &act, NULL);
+ sigaction(SIGBUS, &act, NULL);
+ sigaction(SIGFPE, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGILL, &act, NULL);
+ sigaction(SIGPIPE, &act, NULL);
+ sigaction(SIGSEGV, &act, NULL);
+ sigaction(SIGSYS, &act, NULL);
+
+ sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL);
+ sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL);
+
+ // Asynchronous signals that are normally ignored
+#ifndef LL_IGNORE_SIGCHLD
+ sigaction(SIGCHLD, &act, NULL);
+#endif // LL_IGNORE_SIGCHLD
+
+ // Asynchronous signals that result in attempted graceful exit
+ sigaction(SIGHUP, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGINT, &act, NULL);
+
+ // Asynchronous signals that result in core
+ sigaction(SIGUSR2, &act, NULL);
+ sigaction(SIGQUIT, &act, NULL);
+}
+
+
+
+void default_unix_signal_handler(int signum, siginfo_t *info, void *)
+{
+ // Unix implementation of synchronous signal handler
+ // This runs in the thread that threw the signal.
+ // We do the somewhat sketchy operation of blocking in here until the error handler
+ // has gracefully stopped the app.
+
+ if (LLApp::sLogInSignal)
+ {
+ LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL;
+ }
+
+
+ switch (signum)
+ {
+ case SIGCHLD:
+ if (LLApp::sLogInSignal)
+ {
+ LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL;
+ }
+
+ return;
+ case SIGABRT:
+ // Note that this handler is not set for SIGABRT when using Bugsplat
+ // Abort just results in termination of the app, no funky error handling.
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Got SIGABRT, terminating" << LL_ENDL;
+ }
+ clear_signals();
+ raise(signum);
+ return;
+ case SIGINT:
+ case SIGHUP:
+ case SIGTERM:
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL;
+ }
+ // Graceful exit
+ // Just set our state to quitting, not error
+ if (LLApp::isQuitting() || LLApp::isError())
+ {
+ // We're already trying to die, just ignore this signal
+ if (LLApp::sLogInSignal)
+ {
+ LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL;
+ }
+ return;
+ }
+ LLApp::setQuitting();
+ return;
+ case SIGALRM:
+ case SIGPIPE:
+ case SIGUSR2:
+ default:
+ if (signum == LL_SMACKDOWN_SIGNAL ||
+ signum == SIGBUS ||
+ signum == SIGILL ||
+ signum == SIGFPE ||
+ signum == SIGSEGV ||
+ signum == SIGQUIT)
+ {
+ if (signum == LL_SMACKDOWN_SIGNAL)
+ {
+ // Smackdown treated just like any other app termination, for now
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Handling smackdown signal!" << LL_ENDL;
+ }
+ else
+ {
+ // Don't log anything, even errors - this is because this signal could happen anywhere.
+ LLError::setDefaultLevel(LLError::LEVEL_NONE);
+ }
+
+ // Change the signal that we reraise to SIGABRT, so we generate a core dump.
+ signum = SIGABRT;
+ }
+
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL;
+ }
+ if (LLApp::isError())
+ {
+ // Received second fatal signal while handling first, just die right now
+ // Set the signal handlers back to default before handling the signal - this makes the next signal wipe out the app.
+ clear_signals();
+
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Got another fatal signal while in the error handler, die now!" << LL_ENDL;
+ }
+ raise(signum);
+ return;
+ }
+
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - Flagging error status and waiting for shutdown" << LL_ENDL;
+ }
+
+ if (LLApp::isCrashloggerDisabled()) // Don't gracefully handle any signal, crash and core for a gdb post mortem
+ {
+ clear_signals();
+ LL_WARNS() << "Fatal signal received, not handling the crash here, passing back to operating system" << LL_ENDL;
+ raise(signum);
+ return;
+ }
+
+ // Flag status to ERROR
+ LLApp::setError();
+
+ if (LLApp::sLogInSignal)
+ {
+ LL_WARNS() << "Signal handler - App is stopped, reraising signal" << LL_ENDL;
+ }
+ clear_signals();
+ raise(signum);
+ return;
+ } else {
+ if (LLApp::sLogInSignal)
+ {
+ LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL;
+ }
+ }
+ }
+}
+
+bool unix_post_minidump_callback(const char *dump_dir,
+ const char *minidump_id,
+ void *context, bool succeeded)
+{
+ // Copy minidump file path into fixed buffer in the app instance to avoid
+ // heap allocations in a crash handler.
+
+ // path format: <dump_dir>/<minidump_id>.dmp
+ auto dirPathLength = strlen(dump_dir);
+ auto idLength = strlen(minidump_id);
+
+ // The path must not be truncated.
+ llassert((dirPathLength + idLength + 5) <= LLApp::MAX_MINDUMP_PATH_LENGTH);
+
+ char * path = LLApp::instance()->getMiniDumpFilename();
+ auto remaining = LLApp::MAX_MINDUMP_PATH_LENGTH;
+ strncpy(path, dump_dir, remaining);
+ remaining -= dirPathLength;
+ path += dirPathLength;
+ if (remaining > 0 && dirPathLength > 0 && path[-1] != '/')
+ {
+ *path++ = '/';
+ --remaining;
+ }
+ if (remaining > 0)
+ {
+ strncpy(path, minidump_id, remaining);
+ remaining -= idLength;
+ path += idLength;
+ strncpy(path, ".dmp", remaining);
+ }
+
+ LL_INFOS("CRASHREPORT") << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << LL_ENDL;
+ LLApp::runErrorHandler();
+
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ clear_signals();
+ return false;
+#else
+ return true;
+#endif
+}
+#endif // !WINDOWS
+
diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index 93bf4dd929..79b6e03581 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -1,342 +1,342 @@ -/** - * @file llapp.h - * @brief Declaration of the LLApp class. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLAPP_H -#define LL_LLAPP_H - -#include <map> -#include "llcond.h" -#include "llrun.h" -#include "llsd.h" -#include <atomic> -#include <chrono> -// Forward declarations -class LLLiveFile; -#if LL_LINUX -#include <signal.h> -#endif - -typedef void (*LLAppErrorHandler)(); - -#if !LL_WINDOWS -extern S32 LL_SMACKDOWN_SIGNAL; -extern S32 LL_HEARTBEAT_SIGNAL; - -// Clear all of the signal handlers (which we want to do for the child process when we fork -void clear_signals(); - -#endif - -class LL_COMMON_API LLApp -{ -public: - typedef enum e_app_status - { - APP_STATUS_RUNNING, // The application is currently running - the default status - APP_STATUS_QUITTING, // The application is currently quitting - threads should listen for this and clean up - APP_STATUS_STOPPED, // The application is no longer running - tells the error thread it can exit - APP_STATUS_ERROR // The application had a fatal error occur - tells the error thread to run - } EAppStatus; - - - LLApp(); - virtual ~LLApp(); - - /** - * @brief Return the static app instance if one was created. - */ - static LLApp* instance(); - - /** @name Runtime options */ - //@{ - /** - * @brief Enumeration to specify option priorities in highest to - * lowest order. - */ - enum OptionPriority - { - PRIORITY_RUNTIME_OVERRIDE, - PRIORITY_COMMAND_LINE, - PRIORITY_SPECIFIC_CONFIGURATION, - PRIORITY_GENERAL_CONFIGURATION, - PRIORITY_DEFAULT, - PRIORITY_COUNT - }; - - /** - * @brief Get the application option at the highest priority. - * - * If the return value is undefined, the option does not exist. - * @param name The name of the option. - * @return Returns the option data. - */ - LLSD getOption(const std::string& name) const; - - /** - * @brief Parse ASCII command line options and insert them into - * application command line options. - * - * The name inserted into the option will have leading option - * identifiers (a minus or double minus) stripped. All options - * with values will be stored as a string, while all options - * without values will be stored as true. - * @param argc The argc passed into main(). - * @param argv The argv passed into main(). - * @return Returns true if the parse succeeded. - */ - bool parseCommandOptions(int argc, char** argv); - - /** - * @brief Parse Unicode command line options and insert them into - * application command line options. - * - * The name inserted into the option will have leading option - * identifiers (a minus or double minus) stripped. All options - * with values will be stored as a string, while all options - * without values will be stored as true. - * @param argc The argc passed into main(). - * @param wargv The wargv passed into main(). - * @return Returns true if the parse succeeded. - */ - bool parseCommandOptions(int argc, wchar_t** wargv); - - /** - * @brief Keep track of live files automatically. - * - * *TODO: it currently uses the <code>addToEventTimer()</code> API - * instead of the runner. I should probalby use the runner. - * - * *NOTE: DO NOT add the livefile instance to any kind of check loop. - * - * @param livefile A valid instance of an LLLiveFile. This LLApp - * instance will delete the livefile instance. - */ - void manageLiveFile(LLLiveFile* livefile); - - /** - * @brief Set the options at the specified priority. - * - * This function completely replaces the options at the priority - * level with the data specified. This function will make sure - * level and data might be valid before doing the replace. - * @param level The priority level of the data. - * @param data The data to set. - * @return Returns true if the option was set. - */ - bool setOptionData(OptionPriority level, LLSD data); - - /** - * @brief Get the option data at the specified priority. - * - * This method is probably not so useful except when merging - * information. - * @param level The priority level of the data. - * @return Returns The data (if any) at the level priority. - */ - LLSD getOptionData(OptionPriority level); - //@} - - - - // - // Main application logic - // - virtual bool init() = 0; // Override to do application initialization - - // - // cleanup() - // - // It's currently assumed that the cleanup() method will only get - // called from the main thread or the error handling thread, as it will - // likely do thread shutdown, among other things. - // - virtual bool cleanup() = 0; // Override to do application cleanup - - // - // frame() - // - // Pass control to the application for a single frame. Returns 'done' - // flag: if frame() returns false, it expects to be called again. - // - virtual bool frame() = 0; // Override for application body logic - - // - // Crash logging - // - void disableCrashlogger(); // Let the OS handle the crashes - static bool isCrashloggerDisabled(); // Get the here above set value - - // - // Application status - // - static void setQuitting(); // Set status to QUITTING, the app is now shutting down - static void setStopped(); // Set status to STOPPED, the app is done running and should exit - static void setError(); // Set status to ERROR, the error handler should run - static bool isStopped(); - static bool isRunning(); - static bool isQuitting(); - static bool isError(); - static bool isExiting(); // Either quitting or error (app is exiting, cleanly or not) - static int getPid(); - - // - // Sleep for specified time while still running - // - // For use by a coroutine or thread that performs some maintenance on a - // periodic basis. (See also LLEventTimer.) This method supports the - // pattern of an "infinite" loop that sleeps for some time, performs some - // action, then sleeps again. The trouble with literally sleeping a worker - // thread is that it could potentially sleep right through attempted - // application shutdown. This method avoids that by returning false as - // soon as the application status changes away from APP_STATUS_RUNNING - // (isRunning()). - // - // sleep() returns true if it sleeps undisturbed for the entire specified - // duration. The idea is that you can code 'while sleep(duration) ...', - // which will break the loop once shutdown begins. - // - // Since any time-based LLUnit should be implicitly convertible to - // F32Milliseconds, accept that specific type as a proxy. - static bool sleep(F32Milliseconds duration); - // Allow any duration defined in terms of <chrono>. - // One can imagine a wonderfully general bidirectional conversion system - // between any type derived from LLUnits::LLUnit<T, LLUnits::Seconds> and - // any std::chrono::duration -- but that doesn't yet exist. - template <typename Rep, typename Period> - bool sleep(const std::chrono::duration<Rep, Period>& duration) - { - // wait_for_unequal() has the opposite bool return convention - return ! sStatus.wait_for_unequal(duration, APP_STATUS_RUNNING); - } - - /** @name Error handling methods */ - //@{ - /** - * @brief Do our generic platform-specific error-handling setup -- - * signals on unix, structured exceptions on windows. - * - * DO call this method if your app will either spawn children or be - * spawned by a launcher. - * Call just after app object construction. - * (Otherwise your app will crash when getting signals, - * and will not core dump.) - * - * DO NOT call this method if your application has specialized - * error handling code. - */ - void setupErrorHandling(bool mSecondInstance=false); - - void setErrorHandler(LLAppErrorHandler handler); - static void runErrorHandler(); // run shortly after we detect an error - //@} - - // the maximum length of the minidump filename returned by getMiniDumpFilename() - static const U32 MAX_MINDUMP_PATH_LENGTH = 256; - - // change the directory where Breakpad minidump files are written to - void setDebugFileNames(const std::string &path); - - // Return the Google Breakpad minidump filename after a crash. - char *getMiniDumpFilename() { return mMinidumpPath; } - std::string* getStaticDebugFile() { return &mStaticDebugFileName; } - std::string* getDynamicDebugFile() { return &mDynamicDebugFileName; } - - // Write out a Google Breakpad minidump file. - void writeMiniDump(); - - - /** - * @brief Get a reference to the application runner - * - * Please use the runner with caution. Since the Runner usage - * pattern is not yet clear, this method just gives access to it - * to add and remove runnables. - * @return Returns the application runner. Do not save the - * pointer past the caller's stack frame. - */ - LLRunner& getRunner() { return mRunner; } - -#ifdef LL_WINDOWS - virtual void reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { } -#endif - -public: - typedef std::map<std::string, std::string> string_map; - string_map mOptionMap; // Contains all command-line options and arguments in a map - -protected: - - static void setStatus(EAppStatus status); // Use this to change the application status. - static LLScalarCond<EAppStatus> sStatus; // Reflects current application status - static BOOL sDisableCrashlogger; // Let the OS handle crashes for us. - std::wstring mCrashReportPipeStr; //Name of pipe to use for crash reporting. - - std::string mDumpPath; //output path for google breakpad. Dependency workaround. - - /** - * @brief This method is called once a frame to do once a frame tasks. - */ - void stepFrame(); - -private: - // Contains the filename of the minidump file after a crash. - char mMinidumpPath[MAX_MINDUMP_PATH_LENGTH]; - - std::string mStaticDebugFileName; - std::string mDynamicDebugFileName; - - // *NOTE: On Windows, we need a routine to reset the structured - // exception handler when some evil driver has taken it over for - // their own purposes - typedef int(*signal_handler_func)(int signum); - static LLAppErrorHandler sErrorHandler; - - // This is the application level runnable scheduler. - LLRunner mRunner; - - /** @name Runtime option implementation */ - //@{ - - // The application options. - LLSD mOptions; - - // The live files for this application - std::vector<LLLiveFile*> mLiveFiles; - //@} - -private: - // the static application instance if it was created. - static LLApp* sApplication; - -#if !LL_WINDOWS - friend void default_unix_signal_handler(int signum, siginfo_t *info, void *); -#endif - -public: - static BOOL sLogInSignal; -}; - -#endif // LL_LLAPP_H +/**
+ * @file llapp.h
+ * @brief Declaration of the LLApp class.
+ *
+ * $LicenseInfo:firstyear=2003&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLAPP_H
+#define LL_LLAPP_H
+
+#include <map>
+#include "llcond.h"
+#include "llrun.h"
+#include "llsd.h"
+#include <atomic>
+#include <chrono>
+// Forward declarations
+class LLLiveFile;
+#if LL_LINUX
+#include <signal.h>
+#endif
+
+typedef void (*LLAppErrorHandler)();
+
+#if !LL_WINDOWS
+extern S32 LL_SMACKDOWN_SIGNAL;
+extern S32 LL_HEARTBEAT_SIGNAL;
+
+// Clear all of the signal handlers (which we want to do for the child process when we fork
+void clear_signals();
+
+#endif
+
+class LL_COMMON_API LLApp
+{
+public:
+ typedef enum e_app_status
+ {
+ APP_STATUS_RUNNING, // The application is currently running - the default status
+ APP_STATUS_QUITTING, // The application is currently quitting - threads should listen for this and clean up
+ APP_STATUS_STOPPED, // The application is no longer running - tells the error thread it can exit
+ APP_STATUS_ERROR // The application had a fatal error occur - tells the error thread to run
+ } EAppStatus;
+
+
+ LLApp();
+ virtual ~LLApp();
+
+ /**
+ * @brief Return the static app instance if one was created.
+ */
+ static LLApp* instance();
+
+ /** @name Runtime options */
+ //@{
+ /**
+ * @brief Enumeration to specify option priorities in highest to
+ * lowest order.
+ */
+ enum OptionPriority
+ {
+ PRIORITY_RUNTIME_OVERRIDE,
+ PRIORITY_COMMAND_LINE,
+ PRIORITY_SPECIFIC_CONFIGURATION,
+ PRIORITY_GENERAL_CONFIGURATION,
+ PRIORITY_DEFAULT,
+ PRIORITY_COUNT
+ };
+
+ /**
+ * @brief Get the application option at the highest priority.
+ *
+ * If the return value is undefined, the option does not exist.
+ * @param name The name of the option.
+ * @return Returns the option data.
+ */
+ LLSD getOption(const std::string& name) const;
+
+ /**
+ * @brief Parse ASCII command line options and insert them into
+ * application command line options.
+ *
+ * The name inserted into the option will have leading option
+ * identifiers (a minus or double minus) stripped. All options
+ * with values will be stored as a string, while all options
+ * without values will be stored as true.
+ * @param argc The argc passed into main().
+ * @param argv The argv passed into main().
+ * @return Returns true if the parse succeeded.
+ */
+ bool parseCommandOptions(int argc, char** argv);
+
+ /**
+ * @brief Parse Unicode command line options and insert them into
+ * application command line options.
+ *
+ * The name inserted into the option will have leading option
+ * identifiers (a minus or double minus) stripped. All options
+ * with values will be stored as a string, while all options
+ * without values will be stored as true.
+ * @param argc The argc passed into main().
+ * @param wargv The wargv passed into main().
+ * @return Returns true if the parse succeeded.
+ */
+ bool parseCommandOptions(int argc, wchar_t** wargv);
+
+ /**
+ * @brief Keep track of live files automatically.
+ *
+ * *TODO: it currently uses the <code>addToEventTimer()</code> API
+ * instead of the runner. I should probalby use the runner.
+ *
+ * *NOTE: DO NOT add the livefile instance to any kind of check loop.
+ *
+ * @param livefile A valid instance of an LLLiveFile. This LLApp
+ * instance will delete the livefile instance.
+ */
+ void manageLiveFile(LLLiveFile* livefile);
+
+ /**
+ * @brief Set the options at the specified priority.
+ *
+ * This function completely replaces the options at the priority
+ * level with the data specified. This function will make sure
+ * level and data might be valid before doing the replace.
+ * @param level The priority level of the data.
+ * @param data The data to set.
+ * @return Returns true if the option was set.
+ */
+ bool setOptionData(OptionPriority level, LLSD data);
+
+ /**
+ * @brief Get the option data at the specified priority.
+ *
+ * This method is probably not so useful except when merging
+ * information.
+ * @param level The priority level of the data.
+ * @return Returns The data (if any) at the level priority.
+ */
+ LLSD getOptionData(OptionPriority level);
+ //@}
+
+
+
+ //
+ // Main application logic
+ //
+ virtual bool init() = 0; // Override to do application initialization
+
+ //
+ // cleanup()
+ //
+ // It's currently assumed that the cleanup() method will only get
+ // called from the main thread or the error handling thread, as it will
+ // likely do thread shutdown, among other things.
+ //
+ virtual bool cleanup() = 0; // Override to do application cleanup
+
+ //
+ // frame()
+ //
+ // Pass control to the application for a single frame. Returns 'done'
+ // flag: if frame() returns false, it expects to be called again.
+ //
+ virtual bool frame() = 0; // Override for application body logic
+
+ //
+ // Crash logging
+ //
+ void disableCrashlogger(); // Let the OS handle the crashes
+ static bool isCrashloggerDisabled(); // Get the here above set value
+
+ //
+ // Application status
+ //
+ static void setQuitting(); // Set status to QUITTING, the app is now shutting down
+ static void setStopped(); // Set status to STOPPED, the app is done running and should exit
+ static void setError(); // Set status to ERROR, the error handler should run
+ static bool isStopped();
+ static bool isRunning();
+ static bool isQuitting();
+ static bool isError();
+ static bool isExiting(); // Either quitting or error (app is exiting, cleanly or not)
+ static int getPid();
+
+ //
+ // Sleep for specified time while still running
+ //
+ // For use by a coroutine or thread that performs some maintenance on a
+ // periodic basis. (See also LLEventTimer.) This method supports the
+ // pattern of an "infinite" loop that sleeps for some time, performs some
+ // action, then sleeps again. The trouble with literally sleeping a worker
+ // thread is that it could potentially sleep right through attempted
+ // application shutdown. This method avoids that by returning false as
+ // soon as the application status changes away from APP_STATUS_RUNNING
+ // (isRunning()).
+ //
+ // sleep() returns true if it sleeps undisturbed for the entire specified
+ // duration. The idea is that you can code 'while sleep(duration) ...',
+ // which will break the loop once shutdown begins.
+ //
+ // Since any time-based LLUnit should be implicitly convertible to
+ // F32Milliseconds, accept that specific type as a proxy.
+ static bool sleep(F32Milliseconds duration);
+ // Allow any duration defined in terms of <chrono>.
+ // One can imagine a wonderfully general bidirectional conversion system
+ // between any type derived from LLUnits::LLUnit<T, LLUnits::Seconds> and
+ // any std::chrono::duration -- but that doesn't yet exist.
+ template <typename Rep, typename Period>
+ bool sleep(const std::chrono::duration<Rep, Period>& duration)
+ {
+ // wait_for_unequal() has the opposite bool return convention
+ return ! sStatus.wait_for_unequal(duration, APP_STATUS_RUNNING);
+ }
+
+ /** @name Error handling methods */
+ //@{
+ /**
+ * @brief Do our generic platform-specific error-handling setup --
+ * signals on unix, structured exceptions on windows.
+ *
+ * DO call this method if your app will either spawn children or be
+ * spawned by a launcher.
+ * Call just after app object construction.
+ * (Otherwise your app will crash when getting signals,
+ * and will not core dump.)
+ *
+ * DO NOT call this method if your application has specialized
+ * error handling code.
+ */
+ void setupErrorHandling(bool mSecondInstance=false);
+
+ void setErrorHandler(LLAppErrorHandler handler);
+ static void runErrorHandler(); // run shortly after we detect an error
+ //@}
+
+ // the maximum length of the minidump filename returned by getMiniDumpFilename()
+ static const U32 MAX_MINDUMP_PATH_LENGTH = 256;
+
+ // change the directory where Breakpad minidump files are written to
+ void setDebugFileNames(const std::string &path);
+
+ // Return the Google Breakpad minidump filename after a crash.
+ char *getMiniDumpFilename() { return mMinidumpPath; }
+ std::string* getStaticDebugFile() { return &mStaticDebugFileName; }
+ std::string* getDynamicDebugFile() { return &mDynamicDebugFileName; }
+
+ // Write out a Google Breakpad minidump file.
+ void writeMiniDump();
+
+
+ /**
+ * @brief Get a reference to the application runner
+ *
+ * Please use the runner with caution. Since the Runner usage
+ * pattern is not yet clear, this method just gives access to it
+ * to add and remove runnables.
+ * @return Returns the application runner. Do not save the
+ * pointer past the caller's stack frame.
+ */
+ LLRunner& getRunner() { return mRunner; }
+
+#ifdef LL_WINDOWS
+ virtual void reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { }
+#endif
+
+public:
+ typedef std::map<std::string, std::string> string_map;
+ string_map mOptionMap; // Contains all command-line options and arguments in a map
+
+protected:
+
+ static void setStatus(EAppStatus status); // Use this to change the application status.
+ static LLScalarCond<EAppStatus> sStatus; // Reflects current application status
+ static bool sDisableCrashlogger; // Let the OS handle crashes for us.
+ std::wstring mCrashReportPipeStr; //Name of pipe to use for crash reporting.
+
+ std::string mDumpPath; //output path for google breakpad. Dependency workaround.
+
+ /**
+ * @brief This method is called once a frame to do once a frame tasks.
+ */
+ void stepFrame();
+
+private:
+ // Contains the filename of the minidump file after a crash.
+ char mMinidumpPath[MAX_MINDUMP_PATH_LENGTH];
+
+ std::string mStaticDebugFileName;
+ std::string mDynamicDebugFileName;
+
+ // *NOTE: On Windows, we need a routine to reset the structured
+ // exception handler when some evil driver has taken it over for
+ // their own purposes
+ typedef int(*signal_handler_func)(int signum);
+ static LLAppErrorHandler sErrorHandler;
+
+ // This is the application level runnable scheduler.
+ LLRunner mRunner;
+
+ /** @name Runtime option implementation */
+ //@{
+
+ // The application options.
+ LLSD mOptions;
+
+ // The live files for this application
+ std::vector<LLLiveFile*> mLiveFiles;
+ //@}
+
+private:
+ // the static application instance if it was created.
+ static LLApp* sApplication;
+
+#if !LL_WINDOWS
+ friend void default_unix_signal_handler(int signum, siginfo_t *info, void *);
+#endif
+
+public:
+ static bool sLogInSignal;
+};
+
+#endif // LL_LLAPP_H
diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index c907a8c073..0aa68f28cb 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -1,747 +1,747 @@ -/** - * @file llapr.cpp - * @author Phoenix - * @date 2004-11-28 - * @brief Helper functions for using the apache portable runtime library. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" -#include "llapr.h" -#include "llmutex.h" -#include "apr_dso.h" - -apr_pool_t *gAPRPoolp = NULL; // Global APR memory pool -LLVolatileAPRPool *LLAPRFile::sAPRFilePoolp = NULL ; //global volatile APR memory pool. - -const S32 FULL_VOLATILE_APR_POOL = 1024 ; //number of references to LLVolatileAPRPool - -bool gAPRInitialized = false; - -int abortfunc(int retcode) -{ - LL_WARNS("APR") << "Allocation failure in apr pool with code " << (S32)retcode << LL_ENDL; - return 0; -} - -void ll_init_apr() -{ - // Initialize APR and create the global pool - apr_initialize(); - - if (!gAPRPoolp) - { - apr_pool_create_ex(&gAPRPoolp, NULL, abortfunc, NULL); - } - - if(!LLAPRFile::sAPRFilePoolp) - { - LLAPRFile::sAPRFilePoolp = new LLVolatileAPRPool(FALSE) ; - } - - gAPRInitialized = true; -} - - -bool ll_apr_is_initialized() -{ - return gAPRInitialized; -} - -void ll_cleanup_apr() -{ - gAPRInitialized = false; - - LL_DEBUGS("APR") << "Cleaning up APR" << LL_ENDL; - - if (gAPRPoolp) - { - apr_pool_destroy(gAPRPoolp); - gAPRPoolp = NULL; - } - if (LLAPRFile::sAPRFilePoolp) - { - delete LLAPRFile::sAPRFilePoolp ; - LLAPRFile::sAPRFilePoolp = NULL ; - } - apr_terminate(); -} - -// -// -//LLAPRPool -// -LLAPRPool::LLAPRPool(apr_pool_t *parent, apr_size_t size, BOOL releasePoolFlag) - : mParent(parent), - mReleasePoolFlag(releasePoolFlag), - mMaxSize(size), - mPool(NULL) -{ - createAPRPool() ; -} - -LLAPRPool::~LLAPRPool() -{ - releaseAPRPool() ; -} - -void LLAPRPool::createAPRPool() -{ - if(mPool) - { - return ; - } - - mStatus = apr_pool_create(&mPool, mParent); - ll_apr_warn_status(mStatus) ; - - if(mMaxSize > 0) //size is the number of blocks (which is usually 4K), NOT bytes. - { - apr_allocator_t *allocator = apr_pool_allocator_get(mPool); - if (allocator) - { - apr_allocator_max_free_set(allocator, mMaxSize) ; - } - } -} - -void LLAPRPool::releaseAPRPool() -{ - if(!mPool) - { - return ; - } - - if(!mParent || mReleasePoolFlag) - { - apr_pool_destroy(mPool) ; - mPool = NULL ; - } -} - -//virtual -apr_pool_t* LLAPRPool::getAPRPool() -{ - return mPool ; -} - -LLVolatileAPRPool::LLVolatileAPRPool(BOOL is_local, apr_pool_t *parent, apr_size_t size, BOOL releasePoolFlag) - : LLAPRPool(parent, size, releasePoolFlag), - mNumActiveRef(0), - mNumTotalRef(0) -{ - //create mutex - if(!is_local) //not a local apr_pool, that is: shared by multiple threads. - { - mMutexp.reset(new std::mutex()); - } -} - -LLVolatileAPRPool::~LLVolatileAPRPool() -{ - //delete mutex - mMutexp.reset(); -} - -// -//define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool(). -// -//virtual -apr_pool_t* LLVolatileAPRPool::getAPRPool() -{ - return LLVolatileAPRPool::getVolatileAPRPool() ; -} - -apr_pool_t* LLVolatileAPRPool::getVolatileAPRPool() -{ - LLScopedLock lock(mMutexp.get()) ; - - mNumTotalRef++ ; - mNumActiveRef++ ; - - if(!mPool) - { - createAPRPool() ; - } - - return mPool ; -} - -void LLVolatileAPRPool::clearVolatileAPRPool() -{ - LLScopedLock lock(mMutexp.get()); - - if(mNumActiveRef > 0) - { - mNumActiveRef--; - if(mNumActiveRef < 1) - { - if(isFull()) - { - mNumTotalRef = 0 ; - - //destroy the apr_pool. - releaseAPRPool() ; - } - else - { - //This does not actually free the memory, - //it just allows the pool to re-use this memory for the next allocation. - apr_pool_clear(mPool) ; - } - } - } - else - { - llassert_always(mNumActiveRef > 0) ; - } - - llassert(mNumTotalRef <= (FULL_VOLATILE_APR_POOL << 2)) ; -} - -BOOL LLVolatileAPRPool::isFull() -{ - return mNumTotalRef > FULL_VOLATILE_APR_POOL ; -} - -//--------------------------------------------------------------------- - -bool _ll_apr_warn_status(apr_status_t status, const char* file, int line) -{ - if(APR_SUCCESS == status) return false; -#if !LL_LINUX - char buf[MAX_STRING]; /* Flawfinder: ignore */ - apr_strerror(status, buf, sizeof(buf)); - LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << LL_ENDL; -#endif - return true; -} - -void _ll_apr_assert_status(apr_status_t status, const char* file, int line) -{ - llassert(! _ll_apr_warn_status(status, file, line)); -} - -//--------------------------------------------------------------------- -// -// Scope based pool access -// -//--------------------------------------------------------------------- - -class LLAPRFilePoolScope -{ -public: - LLAPRFilePoolScope() : pPool(NULL), mInitialized(false) {} - LLAPRFilePoolScope(LLVolatileAPRPool* poolp) : mInitialized(false) - { - setFilePool(poolp); - } - ~LLAPRFilePoolScope() - { - reset(); - } - apr_pool_t* getVolatileAPRPool(LLVolatileAPRPool* poolp = NULL) - { - if (!pPool) - { - setFilePool(poolp); - } - if (mInitialized) - { - // We need one clear per one get - // At the moment no need to support multiple calls - LL_ERRS() << "LLAPRFilePoolScope is not supposed to be initialized twice" << LL_ENDL; - } - mInitialized = true; - return pPool->getVolatileAPRPool(); - } - void reset() - { - if (mInitialized) - { - pPool->clearVolatileAPRPool(); - } - } - -private: - void setFilePool(LLVolatileAPRPool* poolp = NULL) - { - if (poolp) - { - pPool = poolp; - } - else - { - pPool = LLAPRFile::sAPRFilePoolp; - } - } - - LLVolatileAPRPool *pPool; - bool mInitialized; -}; - -//--------------------------------------------------------------------- -// -// LLAPRFile functions -// -LLAPRFile::LLAPRFile() - : mFile(NULL), - mCurrentFilePoolp(NULL) -{ -} - -LLAPRFile::LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool) - : mFile(NULL), - mCurrentFilePoolp(NULL) -{ - open(filename, flags, pool); -} - -LLAPRFile::~LLAPRFile() -{ - close() ; -} - -apr_status_t LLAPRFile::close() -{ - apr_status_t ret = APR_SUCCESS ; - if(mFile) - { - ret = apr_file_close(mFile); - mFile = NULL ; - } - - if(mCurrentFilePoolp) - { - mCurrentFilePoolp->clearVolatileAPRPool() ; - mCurrentFilePoolp = NULL ; - } - - return ret ; -} - -apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool, S32* sizep) -{ - apr_status_t s ; - - //check if already open some file - llassert_always(!mFile) ; - llassert_always(!mCurrentFilePoolp) ; - - mCurrentFilePoolp = pool ? pool : sAPRFilePoolp; - apr_pool_t* apr_pool = mCurrentFilePoolp->getVolatileAPRPool(); //paired with clear in close() - s = apr_file_open(&mFile, filename.c_str(), flags, APR_OS_DEFAULT, apr_pool); - - if (s != APR_SUCCESS || !mFile) - { - mFile = NULL ; - - if (sizep) - { - *sizep = 0; - } - } - else if (sizep) - { - S32 file_size = 0; - apr_off_t offset = 0; - if (apr_file_seek(mFile, APR_END, &offset) == APR_SUCCESS) - { - llassert_always(offset <= 0x7fffffff); - file_size = (S32)offset; - offset = 0; - apr_file_seek(mFile, APR_SET, &offset); - } - *sizep = file_size; - } - - if (!mFile) - { - // It will clean pool - close() ; - } - - return s ; -} - -//use gAPRPoolp. -apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, BOOL use_global_pool) -{ - apr_status_t s; - - //check if already open some file - llassert_always(!mFile) ; - llassert_always(!mCurrentFilePoolp) ; - llassert_always(use_global_pool) ; //be aware of using gAPRPoolp. - - s = apr_file_open(&mFile, filename.c_str(), flags, APR_OS_DEFAULT, gAPRPoolp); - if (s != APR_SUCCESS || !mFile) - { - mFile = NULL ; - close() ; - return s; - } - - return s; -} - -// File I/O -S32 LLAPRFile::read(void *buf, S32 nbytes) -{ - if(!mFile) - { - LL_WARNS() << "apr mFile is removed by somebody else. Can not read." << LL_ENDL ; - return 0; - } - - apr_size_t sz = nbytes; - apr_status_t s = apr_file_read(mFile, buf, &sz); - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - return 0; - } - else - { - llassert_always(sz <= 0x7fffffff); - return (S32)sz; - } -} - -S32 LLAPRFile::write(const void *buf, S32 nbytes) -{ - if(!mFile) - { - LL_WARNS() << "apr mFile is removed by somebody else. Can not write." << LL_ENDL ; - return 0; - } - - apr_size_t sz = nbytes; - apr_status_t s = apr_file_write(mFile, buf, &sz); - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - return 0; - } - else - { - llassert_always(sz <= 0x7fffffff); - return (S32)sz; - } -} - -S32 LLAPRFile::seek(apr_seek_where_t where, S32 offset) -{ - return LLAPRFile::seek(mFile, where, offset) ; -} - -// -//******************************************************************************************************************************* -//static components of LLAPRFile -// - -//static -apr_status_t LLAPRFile::close(apr_file_t* file_handle) -{ - apr_status_t ret = APR_SUCCESS ; - if(file_handle) - { - ret = apr_file_close(file_handle); - file_handle = NULL ; - } - - return ret ; -} - -//static -apr_file_t* LLAPRFile::open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags) -{ - apr_status_t s; - apr_file_t* file_handle ; - - - s = apr_file_open(&file_handle, filename.c_str(), flags, APR_OS_DEFAULT, apr_pool); - if (s != APR_SUCCESS || !file_handle) - { - ll_apr_warn_status(s); - LL_WARNS("APR") << " Attempting to open filename: " << filename << LL_ENDL; - file_handle = NULL ; - close(file_handle) ; - return NULL; - } - - return file_handle ; -} - -//static -S32 LLAPRFile::seek(apr_file_t* file_handle, apr_seek_where_t where, S32 offset) -{ - if(!file_handle) - { - return -1 ; - } - - apr_status_t s; - apr_off_t apr_offset; - if (offset >= 0) - { - apr_offset = (apr_off_t)offset; - s = apr_file_seek(file_handle, where, &apr_offset); - } - else - { - apr_offset = 0; - s = apr_file_seek(file_handle, APR_END, &apr_offset); - } - if (s != APR_SUCCESS) - { - ll_apr_warn_status(s); - return -1; - } - else - { - llassert_always(apr_offset <= 0x7fffffff); - return (S32)apr_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, 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; - 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 -//******************************************************************************************************************************* -// +/**
+ * @file llapr.cpp
+ * @author Phoenix
+ * @date 2004-11-28
+ * @brief Helper functions for using the apache portable runtime library.
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+#include "llapr.h"
+#include "llmutex.h"
+#include "apr_dso.h"
+
+apr_pool_t *gAPRPoolp = NULL; // Global APR memory pool
+LLVolatileAPRPool *LLAPRFile::sAPRFilePoolp = NULL ; //global volatile APR memory pool.
+
+const S32 FULL_VOLATILE_APR_POOL = 1024 ; //number of references to LLVolatileAPRPool
+
+bool gAPRInitialized = false;
+
+int abortfunc(int retcode)
+{
+ LL_WARNS("APR") << "Allocation failure in apr pool with code " << (S32)retcode << LL_ENDL;
+ return 0;
+}
+
+void ll_init_apr()
+{
+ // Initialize APR and create the global pool
+ apr_initialize();
+
+ if (!gAPRPoolp)
+ {
+ apr_pool_create_ex(&gAPRPoolp, NULL, abortfunc, NULL);
+ }
+
+ if(!LLAPRFile::sAPRFilePoolp)
+ {
+ LLAPRFile::sAPRFilePoolp = new LLVolatileAPRPool(false) ;
+ }
+
+ gAPRInitialized = true;
+}
+
+
+bool ll_apr_is_initialized()
+{
+ return gAPRInitialized;
+}
+
+void ll_cleanup_apr()
+{
+ gAPRInitialized = false;
+
+ LL_DEBUGS("APR") << "Cleaning up APR" << LL_ENDL;
+
+ if (gAPRPoolp)
+ {
+ apr_pool_destroy(gAPRPoolp);
+ gAPRPoolp = NULL;
+ }
+ if (LLAPRFile::sAPRFilePoolp)
+ {
+ delete LLAPRFile::sAPRFilePoolp ;
+ LLAPRFile::sAPRFilePoolp = NULL ;
+ }
+ apr_terminate();
+}
+
+//
+//
+//LLAPRPool
+//
+LLAPRPool::LLAPRPool(apr_pool_t *parent, apr_size_t size, bool releasePoolFlag)
+ : mParent(parent),
+ mReleasePoolFlag(releasePoolFlag),
+ mMaxSize(size),
+ mPool(NULL)
+{
+ createAPRPool() ;
+}
+
+LLAPRPool::~LLAPRPool()
+{
+ releaseAPRPool() ;
+}
+
+void LLAPRPool::createAPRPool()
+{
+ if(mPool)
+ {
+ return ;
+ }
+
+ mStatus = apr_pool_create(&mPool, mParent);
+ ll_apr_warn_status(mStatus) ;
+
+ if(mMaxSize > 0) //size is the number of blocks (which is usually 4K), NOT bytes.
+ {
+ apr_allocator_t *allocator = apr_pool_allocator_get(mPool);
+ if (allocator)
+ {
+ apr_allocator_max_free_set(allocator, mMaxSize) ;
+ }
+ }
+}
+
+void LLAPRPool::releaseAPRPool()
+{
+ if(!mPool)
+ {
+ return ;
+ }
+
+ if(!mParent || mReleasePoolFlag)
+ {
+ apr_pool_destroy(mPool) ;
+ mPool = NULL ;
+ }
+}
+
+//virtual
+apr_pool_t* LLAPRPool::getAPRPool()
+{
+ return mPool ;
+}
+
+LLVolatileAPRPool::LLVolatileAPRPool(bool is_local, apr_pool_t *parent, apr_size_t size, bool releasePoolFlag)
+ : LLAPRPool(parent, size, releasePoolFlag),
+ mNumActiveRef(0),
+ mNumTotalRef(0)
+{
+ //create mutex
+ if(!is_local) //not a local apr_pool, that is: shared by multiple threads.
+ {
+ mMutexp.reset(new std::mutex());
+ }
+}
+
+LLVolatileAPRPool::~LLVolatileAPRPool()
+{
+ //delete mutex
+ mMutexp.reset();
+}
+
+//
+//define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool().
+//
+//virtual
+apr_pool_t* LLVolatileAPRPool::getAPRPool()
+{
+ return LLVolatileAPRPool::getVolatileAPRPool() ;
+}
+
+apr_pool_t* LLVolatileAPRPool::getVolatileAPRPool()
+{
+ LLScopedLock lock(mMutexp.get()) ;
+
+ mNumTotalRef++ ;
+ mNumActiveRef++ ;
+
+ if(!mPool)
+ {
+ createAPRPool() ;
+ }
+
+ return mPool ;
+}
+
+void LLVolatileAPRPool::clearVolatileAPRPool()
+{
+ LLScopedLock lock(mMutexp.get());
+
+ if(mNumActiveRef > 0)
+ {
+ mNumActiveRef--;
+ if(mNumActiveRef < 1)
+ {
+ if(isFull())
+ {
+ mNumTotalRef = 0 ;
+
+ //destroy the apr_pool.
+ releaseAPRPool() ;
+ }
+ else
+ {
+ //This does not actually free the memory,
+ //it just allows the pool to re-use this memory for the next allocation.
+ apr_pool_clear(mPool) ;
+ }
+ }
+ }
+ else
+ {
+ llassert_always(mNumActiveRef > 0) ;
+ }
+
+ llassert(mNumTotalRef <= (FULL_VOLATILE_APR_POOL << 2)) ;
+}
+
+bool LLVolatileAPRPool::isFull()
+{
+ return mNumTotalRef > FULL_VOLATILE_APR_POOL ;
+}
+
+//---------------------------------------------------------------------
+
+bool _ll_apr_warn_status(apr_status_t status, const char* file, int line)
+{
+ if(APR_SUCCESS == status) return false;
+#if !LL_LINUX
+ char buf[MAX_STRING]; /* Flawfinder: ignore */
+ apr_strerror(status, buf, sizeof(buf));
+ LL_WARNS("APR") << "APR: " << file << ":" << line << " " << buf << LL_ENDL;
+#endif
+ return true;
+}
+
+void _ll_apr_assert_status(apr_status_t status, const char* file, int line)
+{
+ llassert(! _ll_apr_warn_status(status, file, line));
+}
+
+//---------------------------------------------------------------------
+//
+// Scope based pool access
+//
+//---------------------------------------------------------------------
+
+class LLAPRFilePoolScope
+{
+public:
+ LLAPRFilePoolScope() : pPool(NULL), mInitialized(false) {}
+ LLAPRFilePoolScope(LLVolatileAPRPool* poolp) : mInitialized(false)
+ {
+ setFilePool(poolp);
+ }
+ ~LLAPRFilePoolScope()
+ {
+ reset();
+ }
+ apr_pool_t* getVolatileAPRPool(LLVolatileAPRPool* poolp = NULL)
+ {
+ if (!pPool)
+ {
+ setFilePool(poolp);
+ }
+ if (mInitialized)
+ {
+ // We need one clear per one get
+ // At the moment no need to support multiple calls
+ LL_ERRS() << "LLAPRFilePoolScope is not supposed to be initialized twice" << LL_ENDL;
+ }
+ mInitialized = true;
+ return pPool->getVolatileAPRPool();
+ }
+ void reset()
+ {
+ if (mInitialized)
+ {
+ pPool->clearVolatileAPRPool();
+ }
+ }
+
+private:
+ void setFilePool(LLVolatileAPRPool* poolp = NULL)
+ {
+ if (poolp)
+ {
+ pPool = poolp;
+ }
+ else
+ {
+ pPool = LLAPRFile::sAPRFilePoolp;
+ }
+ }
+
+ LLVolatileAPRPool *pPool;
+ bool mInitialized;
+};
+
+//---------------------------------------------------------------------
+//
+// LLAPRFile functions
+//
+LLAPRFile::LLAPRFile()
+ : mFile(NULL),
+ mCurrentFilePoolp(NULL)
+{
+}
+
+LLAPRFile::LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool)
+ : mFile(NULL),
+ mCurrentFilePoolp(NULL)
+{
+ open(filename, flags, pool);
+}
+
+LLAPRFile::~LLAPRFile()
+{
+ close() ;
+}
+
+apr_status_t LLAPRFile::close()
+{
+ apr_status_t ret = APR_SUCCESS ;
+ if(mFile)
+ {
+ ret = apr_file_close(mFile);
+ mFile = NULL ;
+ }
+
+ if(mCurrentFilePoolp)
+ {
+ mCurrentFilePoolp->clearVolatileAPRPool() ;
+ mCurrentFilePoolp = NULL ;
+ }
+
+ return ret ;
+}
+
+apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool, S32* sizep)
+{
+ apr_status_t s ;
+
+ //check if already open some file
+ llassert_always(!mFile) ;
+ llassert_always(!mCurrentFilePoolp) ;
+
+ mCurrentFilePoolp = pool ? pool : sAPRFilePoolp;
+ apr_pool_t* apr_pool = mCurrentFilePoolp->getVolatileAPRPool(); //paired with clear in close()
+ s = apr_file_open(&mFile, filename.c_str(), flags, APR_OS_DEFAULT, apr_pool);
+
+ if (s != APR_SUCCESS || !mFile)
+ {
+ mFile = NULL ;
+
+ if (sizep)
+ {
+ *sizep = 0;
+ }
+ }
+ else if (sizep)
+ {
+ S32 file_size = 0;
+ apr_off_t offset = 0;
+ if (apr_file_seek(mFile, APR_END, &offset) == APR_SUCCESS)
+ {
+ llassert_always(offset <= 0x7fffffff);
+ file_size = (S32)offset;
+ offset = 0;
+ apr_file_seek(mFile, APR_SET, &offset);
+ }
+ *sizep = file_size;
+ }
+
+ if (!mFile)
+ {
+ // It will clean pool
+ close() ;
+ }
+
+ return s ;
+}
+
+//use gAPRPoolp.
+apr_status_t LLAPRFile::open(const std::string& filename, apr_int32_t flags, bool use_global_pool)
+{
+ apr_status_t s;
+
+ //check if already open some file
+ llassert_always(!mFile) ;
+ llassert_always(!mCurrentFilePoolp) ;
+ llassert_always(use_global_pool) ; //be aware of using gAPRPoolp.
+
+ s = apr_file_open(&mFile, filename.c_str(), flags, APR_OS_DEFAULT, gAPRPoolp);
+ if (s != APR_SUCCESS || !mFile)
+ {
+ mFile = NULL ;
+ close() ;
+ return s;
+ }
+
+ return s;
+}
+
+// File I/O
+S32 LLAPRFile::read(void *buf, S32 nbytes)
+{
+ if(!mFile)
+ {
+ LL_WARNS() << "apr mFile is removed by somebody else. Can not read." << LL_ENDL ;
+ return 0;
+ }
+
+ apr_size_t sz = nbytes;
+ apr_status_t s = apr_file_read(mFile, buf, &sz);
+ if (s != APR_SUCCESS)
+ {
+ ll_apr_warn_status(s);
+ return 0;
+ }
+ else
+ {
+ llassert_always(sz <= 0x7fffffff);
+ return (S32)sz;
+ }
+}
+
+S32 LLAPRFile::write(const void *buf, S32 nbytes)
+{
+ if(!mFile)
+ {
+ LL_WARNS() << "apr mFile is removed by somebody else. Can not write." << LL_ENDL ;
+ return 0;
+ }
+
+ apr_size_t sz = nbytes;
+ apr_status_t s = apr_file_write(mFile, buf, &sz);
+ if (s != APR_SUCCESS)
+ {
+ ll_apr_warn_status(s);
+ return 0;
+ }
+ else
+ {
+ llassert_always(sz <= 0x7fffffff);
+ return (S32)sz;
+ }
+}
+
+S32 LLAPRFile::seek(apr_seek_where_t where, S32 offset)
+{
+ return LLAPRFile::seek(mFile, where, offset) ;
+}
+
+//
+//*******************************************************************************************************************************
+//static components of LLAPRFile
+//
+
+//static
+apr_status_t LLAPRFile::close(apr_file_t* file_handle)
+{
+ apr_status_t ret = APR_SUCCESS ;
+ if(file_handle)
+ {
+ ret = apr_file_close(file_handle);
+ file_handle = NULL ;
+ }
+
+ return ret ;
+}
+
+//static
+apr_file_t* LLAPRFile::open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags)
+{
+ apr_status_t s;
+ apr_file_t* file_handle ;
+
+
+ s = apr_file_open(&file_handle, filename.c_str(), flags, APR_OS_DEFAULT, apr_pool);
+ if (s != APR_SUCCESS || !file_handle)
+ {
+ ll_apr_warn_status(s);
+ LL_WARNS("APR") << " Attempting to open filename: " << filename << LL_ENDL;
+ file_handle = NULL ;
+ close(file_handle) ;
+ return NULL;
+ }
+
+ return file_handle ;
+}
+
+//static
+S32 LLAPRFile::seek(apr_file_t* file_handle, apr_seek_where_t where, S32 offset)
+{
+ if(!file_handle)
+ {
+ return -1 ;
+ }
+
+ apr_status_t s;
+ apr_off_t apr_offset;
+ if (offset >= 0)
+ {
+ apr_offset = (apr_off_t)offset;
+ s = apr_file_seek(file_handle, where, &apr_offset);
+ }
+ else
+ {
+ apr_offset = 0;
+ s = apr_file_seek(file_handle, APR_END, &apr_offset);
+ }
+ if (s != APR_SUCCESS)
+ {
+ ll_apr_warn_status(s);
+ return -1;
+ }
+ else
+ {
+ llassert_always(apr_offset <= 0x7fffffff);
+ return (S32)apr_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;
+ 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 2f88fdcd59..3a43339ca3 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -1,201 +1,201 @@ -/** - * @file llapr.h - * @author Phoenix - * @date 2004-11-28 - * @brief Helper functions for using the apache portable runtime library. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLAPR_H -#define LL_LLAPR_H - -#if LL_LINUX -#include <sys/param.h> // Need PATH_MAX in APR headers... -#endif - -#include <boost/noncopyable.hpp> -#include "llwin32headerslean.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; -/** - * @brief Function which appropriately logs error or remains quiet on - * APR_SUCCESS. - * @return Returns <code>true</code> if status is an error condition. - */ -#define ll_apr_warn_status(status) _ll_apr_warn_status(status, __FILE__, __LINE__) -bool LL_COMMON_API _ll_apr_warn_status(apr_status_t status, const char* file, int line); - -#define ll_apr_assert_status(status) _ll_apr_assert_status(status, __FILE__, __LINE__) -void LL_COMMON_API _ll_apr_assert_status(apr_status_t status, const char* file, int line); - -extern "C" LL_COMMON_API apr_pool_t* gAPRPoolp; // Global APR memory pool - -/** - * @brief initialize the common apr constructs -- apr itself, the - * global pool, and a mutex. - */ -void LL_COMMON_API ll_init_apr(); - -/** - * @brief Cleanup those common apr constructs. - */ -void LL_COMMON_API ll_cleanup_apr(); - -bool LL_COMMON_API ll_apr_is_initialized(); - - -// -//LL apr_pool -//manage apr_pool_t, destroy allocated apr_pool in the destruction function. -// -class LL_COMMON_API LLAPRPool -{ -public: - LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE) ; - virtual ~LLAPRPool() ; - - virtual apr_pool_t* getAPRPool() ; - apr_status_t getStatus() {return mStatus ; } - -protected: - void releaseAPRPool() ; - void createAPRPool() ; - -protected: - apr_pool_t* mPool ; //pointing to an apr_pool - apr_pool_t* mParent ; //parent pool - apr_size_t mMaxSize ; //max size of mPool, mPool should return memory to system if allocated memory beyond this limit. However it seems not to work. - apr_status_t mStatus ; //status when creating the pool - BOOL mReleasePoolFlag ; //if set, mPool is destroyed when LLAPRPool is deleted. default value is true. -}; - -// -//volatile LL apr_pool -//which clears memory automatically. -//so it can not hold static data or data after memory is cleared -// -class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool -{ -public: - LLVolatileAPRPool(BOOL is_local = TRUE, apr_pool_t *parent = NULL, apr_size_t size = 0, BOOL releasePoolFlag = TRUE); - virtual ~LLVolatileAPRPool(); - - /*virtual*/ apr_pool_t* getAPRPool() ; //define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool(). - apr_pool_t* getVolatileAPRPool() ; - void clearVolatileAPRPool() ; - - BOOL isFull() ; - -private: - S32 mNumActiveRef ; //number of active pointers pointing to the apr_pool. - S32 mNumTotalRef ; //number of total pointers pointing to the apr_pool since last creating. - - std::unique_ptr<std::mutex> mMutexp; -} ; - -// File IO convenience functions. -// Returns NULL if the file fails to open, sets *sizep to file size if not NULL -// abbreviated flags -#define LL_APR_R (APR_READ) // "r" -#define LL_APR_W (APR_CREATE|APR_TRUNCATE|APR_WRITE) // "w" -#define LL_APR_A (APR_CREATE|APR_WRITE|APR_APPEND) // "w" -#define LL_APR_RB (APR_READ|APR_BINARY) // "rb" -#define LL_APR_WB (APR_CREATE|APR_TRUNCATE|APR_WRITE|APR_BINARY) // "wb" -#define LL_APR_AB (APR_CREATE|APR_WRITE|APR_BINARY|APR_APPEND) -#define LL_APR_RPB (APR_READ|APR_WRITE|APR_BINARY) // "r+b" -#define LL_APR_WPB (APR_CREATE|APR_TRUNCATE|APR_READ|APR_WRITE|APR_BINARY) // "w+b" - -// -//apr_file manager -//which: 1)only keeps one file open; -// 2)closes the open file in the destruction function -// 3)informs the apr_pool to clean the memory when the file is closed. -//Note: please close an open file at the earliest convenience. -// especially do not put some time-costly operations between open() and close(). -// otherwise it might lock the APRFilePool. -//there are two different apr_pools the APRFile can use: -// 1, a temporary pool passed to an APRFile function, which is used within this function and only once. -// 2, a global pool. -// - -class LL_COMMON_API LLAPRFile : boost::noncopyable -{ - // make this non copyable since a copy closes the file -private: - apr_file_t* mFile ; - LLVolatileAPRPool *mCurrentFilePoolp ; //currently in use apr_pool, could be one of them: sAPRFilePoolp, or a temp pool. - -public: - LLAPRFile() ; - LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL); - ~LLAPRFile() ; - - apr_status_t open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL, S32* sizep = NULL); - apr_status_t open(const std::string& filename, apr_int32_t flags, BOOL use_global_pool); //use gAPRPoolp. - apr_status_t close() ; - - // Returns actual offset, -1 if seek fails - S32 seek(apr_seek_where_t where, S32 offset); - apr_status_t eof() { return apr_file_eof(mFile);} - - // Returns bytes read/written, 0 if read/write fails: - S32 read(void* buf, S32 nbytes); - S32 write(const void* buf, S32 nbytes); - - apr_file_t* getFileHandle() {return mFile;} - -// -//******************************************************************************************************************************* -//static components -// -public: - static LLVolatileAPRPool *sAPRFilePoolp ; //a global apr_pool for APRFile, which is used only when local pool does not exist. - -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, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); // offset<0 means append -//******************************************************************************************************************************* -}; - - -#endif // LL_LLAPR_H +/**
+ * @file llapr.h
+ * @author Phoenix
+ * @date 2004-11-28
+ * @brief Helper functions for using the apache portable runtime library.
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLAPR_H
+#define LL_LLAPR_H
+
+#if LL_LINUX
+#include <sys/param.h> // Need PATH_MAX in APR headers...
+#endif
+
+#include <boost/noncopyable.hpp>
+#include "llwin32headerslean.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;
+/**
+ * @brief Function which appropriately logs error or remains quiet on
+ * APR_SUCCESS.
+ * @return Returns <code>true</code> if status is an error condition.
+ */
+#define ll_apr_warn_status(status) _ll_apr_warn_status(status, __FILE__, __LINE__)
+bool LL_COMMON_API _ll_apr_warn_status(apr_status_t status, const char* file, int line);
+
+#define ll_apr_assert_status(status) _ll_apr_assert_status(status, __FILE__, __LINE__)
+void LL_COMMON_API _ll_apr_assert_status(apr_status_t status, const char* file, int line);
+
+extern "C" LL_COMMON_API apr_pool_t* gAPRPoolp; // Global APR memory pool
+
+/**
+ * @brief initialize the common apr constructs -- apr itself, the
+ * global pool, and a mutex.
+ */
+void LL_COMMON_API ll_init_apr();
+
+/**
+ * @brief Cleanup those common apr constructs.
+ */
+void LL_COMMON_API ll_cleanup_apr();
+
+bool LL_COMMON_API ll_apr_is_initialized();
+
+
+//
+//LL apr_pool
+//manage apr_pool_t, destroy allocated apr_pool in the destruction function.
+//
+class LL_COMMON_API LLAPRPool
+{
+public:
+ LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true) ;
+ virtual ~LLAPRPool() ;
+
+ virtual apr_pool_t* getAPRPool() ;
+ apr_status_t getStatus() {return mStatus ; }
+
+protected:
+ void releaseAPRPool() ;
+ void createAPRPool() ;
+
+protected:
+ apr_pool_t* mPool ; //pointing to an apr_pool
+ apr_pool_t* mParent ; //parent pool
+ apr_size_t mMaxSize ; //max size of mPool, mPool should return memory to system if allocated memory beyond this limit. However it seems not to work.
+ apr_status_t mStatus ; //status when creating the pool
+ bool mReleasePoolFlag ; //if set, mPool is destroyed when LLAPRPool is deleted. default value is true.
+};
+
+//
+//volatile LL apr_pool
+//which clears memory automatically.
+//so it can not hold static data or data after memory is cleared
+//
+class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool
+{
+public:
+ LLVolatileAPRPool(bool is_local = true, apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true);
+ virtual ~LLVolatileAPRPool();
+
+ /*virtual*/ apr_pool_t* getAPRPool() ; //define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool().
+ apr_pool_t* getVolatileAPRPool() ;
+ void clearVolatileAPRPool() ;
+
+ bool isFull() ;
+
+private:
+ S32 mNumActiveRef ; //number of active pointers pointing to the apr_pool.
+ S32 mNumTotalRef ; //number of total pointers pointing to the apr_pool since last creating.
+
+ std::unique_ptr<std::mutex> mMutexp;
+} ;
+
+// File IO convenience functions.
+// Returns NULL if the file fails to open, sets *sizep to file size if not NULL
+// abbreviated flags
+#define LL_APR_R (APR_READ) // "r"
+#define LL_APR_W (APR_CREATE|APR_TRUNCATE|APR_WRITE) // "w"
+#define LL_APR_A (APR_CREATE|APR_WRITE|APR_APPEND) // "w"
+#define LL_APR_RB (APR_READ|APR_BINARY) // "rb"
+#define LL_APR_WB (APR_CREATE|APR_TRUNCATE|APR_WRITE|APR_BINARY) // "wb"
+#define LL_APR_AB (APR_CREATE|APR_WRITE|APR_BINARY|APR_APPEND)
+#define LL_APR_RPB (APR_READ|APR_WRITE|APR_BINARY) // "r+b"
+#define LL_APR_WPB (APR_CREATE|APR_TRUNCATE|APR_READ|APR_WRITE|APR_BINARY) // "w+b"
+
+//
+//apr_file manager
+//which: 1)only keeps one file open;
+// 2)closes the open file in the destruction function
+// 3)informs the apr_pool to clean the memory when the file is closed.
+//Note: please close an open file at the earliest convenience.
+// especially do not put some time-costly operations between open() and close().
+// otherwise it might lock the APRFilePool.
+//there are two different apr_pools the APRFile can use:
+// 1, a temporary pool passed to an APRFile function, which is used within this function and only once.
+// 2, a global pool.
+//
+
+class LL_COMMON_API LLAPRFile : boost::noncopyable
+{
+ // make this non copyable since a copy closes the file
+private:
+ apr_file_t* mFile ;
+ LLVolatileAPRPool *mCurrentFilePoolp ; //currently in use apr_pool, could be one of them: sAPRFilePoolp, or a temp pool.
+
+public:
+ LLAPRFile() ;
+ LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL);
+ ~LLAPRFile() ;
+
+ apr_status_t open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL, S32* sizep = NULL);
+ apr_status_t open(const std::string& filename, apr_int32_t flags, bool use_global_pool); //use gAPRPoolp.
+ apr_status_t close() ;
+
+ // Returns actual offset, -1 if seek fails
+ S32 seek(apr_seek_where_t where, S32 offset);
+ apr_status_t eof() { return apr_file_eof(mFile);}
+
+ // Returns bytes read/written, 0 if read/write fails:
+ S32 read(void* buf, S32 nbytes);
+ S32 write(const void* buf, S32 nbytes);
+
+ apr_file_t* getFileHandle() {return mFile;}
+
+//
+//*******************************************************************************************************************************
+//static components
+//
+public:
+ static LLVolatileAPRPool *sAPRFilePoolp ; //a global apr_pool for APRFile, which is used only when local pool does not exist.
+
+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/llassettype.cpp b/indra/llcommon/llassettype.cpp index 3e46bde954..3041c1f354 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -1,246 +1,246 @@ -/** - * @file llassettype.cpp - * @brief Implementatino of LLAssetType functionality. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llassettype.h" -#include "lldictionary.h" -#include "llmemory.h" -#include "llsingleton.h" - -///---------------------------------------------------------------------------- -/// Class LLAssetType -///---------------------------------------------------------------------------- -struct AssetEntry : public LLDictionaryEntry -{ - AssetEntry(const char *desc_name, - const char *type_name, // 8 character limit! - const char *human_name, // for decoding to human readable form; put any and as many printable characters you want in each one - bool can_link, // can you create a link to this type? - bool can_fetch, // can you fetch this asset by ID? - bool can_know) // can you see this asset's ID? - : - LLDictionaryEntry(desc_name), - mTypeName(type_name), - mHumanName(human_name), - mCanLink(can_link), - mCanFetch(can_fetch), - mCanKnow(can_know) - { - llassert(strlen(mTypeName) <= 8); - } - - const char *mTypeName; - const char *mHumanName; - bool mCanLink; - bool mCanFetch; - bool mCanKnow; -}; - -class LLAssetDictionary : public LLSingleton<LLAssetDictionary>, - public LLDictionary<LLAssetType::EType, AssetEntry> -{ - LLSINGLETON(LLAssetDictionary); -}; - -LLAssetDictionary::LLAssetDictionary() -{ - // DESCRIPTION TYPE NAME HUMAN NAME CAN LINK? CAN FETCH? CAN KNOW? - // |--------------------|-----------|-------------------|-----------|-----------|---------| - addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", true, false, true)); - addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", true, true, true)); - addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", true, false, false)); - addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", true, true, true)); - addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", true, false, false)); - addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", true, true, true)); - addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", true, false, false)); - addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", true, false, true)); - addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", true, false, false)); - addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", true, false, false)); - addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", true, false, false)); - addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", true, false, false)); - addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", true, true, true)); - addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", true, false, false)); - addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", true, false, false)); - addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", true, false, false)); - addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", true, true, true)); - addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", true, true, true)); - addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", false, false, false)); - - addEntry(LLAssetType::AT_LINK, new AssetEntry("LINK", "link", "sym link", false, false, true)); - addEntry(LLAssetType::AT_LINK_FOLDER, new AssetEntry("FOLDER_LINK", "link_f", "sym folder link", false, false, true)); - addEntry(LLAssetType::AT_MESH, new AssetEntry("MESH", "mesh", "mesh", false, false, false)); - addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false)); - addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false)); - addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true)); - addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true)); - addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); - addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, FALSE, FALSE, FALSE)); - -}; - -const std::string LLAssetType::BADLOOKUP("llassettype_bad_lookup"); - -// static -LLAssetType::EType LLAssetType::getType(const std::string& desc_name) -{ - std::string s = desc_name; - LLStringUtil::toUpper(s); - return LLAssetDictionary::getInstance()->lookup(s); -} - -// static -const std::string &LLAssetType::getDesc(LLAssetType::EType asset_type) -{ - const AssetEntry *entry = LLAssetDictionary::getInstance()->lookup(asset_type); - if (entry) - { - return entry->mName; - } - else - { - return BADLOOKUP; - } -} - -// static -const char *LLAssetType::lookup(LLAssetType::EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mTypeName; - } - else - { - return BADLOOKUP.c_str(); - } -} - -// static -LLAssetType::EType LLAssetType::lookup(const char* name) -{ - return lookup(ll_safe_string(name)); -} - -// static -LLAssetType::EType LLAssetType::lookup(const std::string& type_name) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - for (const LLAssetDictionary::value_type& pair : *dict) - { - const AssetEntry *entry = pair.second; - if (type_name == entry->mTypeName) - { - return pair.first; - } - } - return AT_UNKNOWN; -} - -// static -const char *LLAssetType::lookupHumanReadable(LLAssetType::EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mHumanName; - } - else - { - return BADLOOKUP.c_str(); - } -} - -// static -LLAssetType::EType LLAssetType::lookupHumanReadable(const char* name) -{ - return lookupHumanReadable(ll_safe_string(name)); -} - -// static -LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_name) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - for (const LLAssetDictionary::value_type& pair : *dict) - { - const AssetEntry *entry = pair.second; - if (entry->mHumanName && (readable_name == entry->mHumanName)) - { - return pair.first; - } - } - return AT_NONE; -} - -// static -bool LLAssetType::lookupCanLink(EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mCanLink; - } - return false; -} - -// static -// Not adding this to dictionary since we probably will only have these two types -bool LLAssetType::lookupIsLinkType(EType asset_type) -{ - if (asset_type == AT_LINK || asset_type == AT_LINK_FOLDER) - { - return true; - } - return false; -} - -// static -bool LLAssetType::lookupIsAssetFetchByIDAllowed(EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mCanFetch; - } - return false; -} - -// static -bool LLAssetType::lookupIsAssetIDKnowable(EType asset_type) -{ - const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); - const AssetEntry *entry = dict->lookup(asset_type); - if (entry) - { - return entry->mCanKnow; - } - return false; -} +/**
+ * @file llassettype.cpp
+ * @brief Implementatino of LLAssetType functionality.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llassettype.h"
+#include "lldictionary.h"
+#include "llmemory.h"
+#include "llsingleton.h"
+
+///----------------------------------------------------------------------------
+/// Class LLAssetType
+///----------------------------------------------------------------------------
+struct AssetEntry : public LLDictionaryEntry
+{
+ AssetEntry(const char *desc_name,
+ const char *type_name, // 8 character limit!
+ const char *human_name, // for decoding to human readable form; put any and as many printable characters you want in each one
+ bool can_link, // can you create a link to this type?
+ bool can_fetch, // can you fetch this asset by ID?
+ bool can_know) // can you see this asset's ID?
+ :
+ LLDictionaryEntry(desc_name),
+ mTypeName(type_name),
+ mHumanName(human_name),
+ mCanLink(can_link),
+ mCanFetch(can_fetch),
+ mCanKnow(can_know)
+ {
+ llassert(strlen(mTypeName) <= 8);
+ }
+
+ const char *mTypeName;
+ const char *mHumanName;
+ bool mCanLink;
+ bool mCanFetch;
+ bool mCanKnow;
+};
+
+class LLAssetDictionary : public LLSingleton<LLAssetDictionary>,
+ public LLDictionary<LLAssetType::EType, AssetEntry>
+{
+ LLSINGLETON(LLAssetDictionary);
+};
+
+LLAssetDictionary::LLAssetDictionary()
+{
+ // DESCRIPTION TYPE NAME HUMAN NAME CAN LINK? CAN FETCH? CAN KNOW?
+ // |--------------------|-----------|-------------------|-----------|-----------|---------|
+ addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", true, false, true));
+ addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", true, true, true));
+ addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", true, false, false));
+ addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", true, true, true));
+ addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", true, false, false));
+ addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", true, true, true));
+ addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", true, false, false));
+ addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", true, false, true));
+ addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", true, false, false));
+ addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", true, false, false));
+ addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", true, false, false));
+ addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", true, false, false));
+ addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", true, true, true));
+ addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", true, false, false));
+ addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", true, false, false));
+ addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", true, false, false));
+ addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", true, true, true));
+ addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", true, true, true));
+ addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", false, false, false));
+
+ addEntry(LLAssetType::AT_LINK, new AssetEntry("LINK", "link", "sym link", false, false, true));
+ addEntry(LLAssetType::AT_LINK_FOLDER, new AssetEntry("FOLDER_LINK", "link_f", "sym folder link", false, false, true));
+ addEntry(LLAssetType::AT_MESH, new AssetEntry("MESH", "mesh", "mesh", false, false, false));
+ addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false));
+ addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false));
+ addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true));
+ addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true));
+ addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false));
+ addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, false, false, false));
+
+};
+
+const std::string LLAssetType::BADLOOKUP("llassettype_bad_lookup");
+
+// static
+LLAssetType::EType LLAssetType::getType(const std::string& desc_name)
+{
+ std::string s = desc_name;
+ LLStringUtil::toUpper(s);
+ return LLAssetDictionary::getInstance()->lookup(s);
+}
+
+// static
+const std::string &LLAssetType::getDesc(LLAssetType::EType asset_type)
+{
+ const AssetEntry *entry = LLAssetDictionary::getInstance()->lookup(asset_type);
+ if (entry)
+ {
+ return entry->mName;
+ }
+ else
+ {
+ return BADLOOKUP;
+ }
+}
+
+// static
+const char *LLAssetType::lookup(LLAssetType::EType asset_type)
+{
+ const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
+ const AssetEntry *entry = dict->lookup(asset_type);
+ if (entry)
+ {
+ return entry->mTypeName;
+ }
+ else
+ {
+ return BADLOOKUP.c_str();
+ }
+}
+
+// static
+LLAssetType::EType LLAssetType::lookup(const char* name)
+{
+ return lookup(ll_safe_string(name));
+}
+
+// static
+LLAssetType::EType LLAssetType::lookup(const std::string& type_name)
+{
+ const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
+ for (const LLAssetDictionary::value_type& pair : *dict)
+ {
+ const AssetEntry *entry = pair.second;
+ if (type_name == entry->mTypeName)
+ {
+ return pair.first;
+ }
+ }
+ return AT_UNKNOWN;
+}
+
+// static
+const char *LLAssetType::lookupHumanReadable(LLAssetType::EType asset_type)
+{
+ const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
+ const AssetEntry *entry = dict->lookup(asset_type);
+ if (entry)
+ {
+ return entry->mHumanName;
+ }
+ else
+ {
+ return BADLOOKUP.c_str();
+ }
+}
+
+// static
+LLAssetType::EType LLAssetType::lookupHumanReadable(const char* name)
+{
+ return lookupHumanReadable(ll_safe_string(name));
+}
+
+// static
+LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_name)
+{
+ const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
+ for (const LLAssetDictionary::value_type& pair : *dict)
+ {
+ const AssetEntry *entry = pair.second;
+ if (entry->mHumanName && (readable_name == entry->mHumanName))
+ {
+ return pair.first;
+ }
+ }
+ return AT_NONE;
+}
+
+// static
+bool LLAssetType::lookupCanLink(EType asset_type)
+{
+ const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
+ const AssetEntry *entry = dict->lookup(asset_type);
+ if (entry)
+ {
+ return entry->mCanLink;
+ }
+ return false;
+}
+
+// static
+// Not adding this to dictionary since we probably will only have these two types
+bool LLAssetType::lookupIsLinkType(EType asset_type)
+{
+ if (asset_type == AT_LINK || asset_type == AT_LINK_FOLDER)
+ {
+ return true;
+ }
+ return false;
+}
+
+// static
+bool LLAssetType::lookupIsAssetFetchByIDAllowed(EType asset_type)
+{
+ const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
+ const AssetEntry *entry = dict->lookup(asset_type);
+ if (entry)
+ {
+ return entry->mCanFetch;
+ }
+ return false;
+}
+
+// static
+bool LLAssetType::lookupIsAssetIDKnowable(EType asset_type)
+{
+ const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
+ const AssetEntry *entry = dict->lookup(asset_type);
+ if (entry)
+ {
+ return entry->mCanKnow;
+ }
+ return false;
+}
diff --git a/indra/llcommon/llbase64.cpp b/indra/llcommon/llbase64.cpp index dbbbec9813..144b878db3 100644 --- a/indra/llcommon/llbase64.cpp +++ b/indra/llcommon/llbase64.cpp @@ -1,61 +1,77 @@ -/** - * @file llbase64.cpp - * @brief Wrapper for apr base64 encoding that returns a std::string - * @author James Cook - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llbase64.h" - -#include <string> - -#include "apr_base64.h" - - -// static -std::string LLBase64::encode(const U8* input, size_t input_size) -{ - std::string output; - if (input - && input_size > 0) - { - // Yes, it returns int. - int b64_buffer_length = apr_base64_encode_len(narrow<size_t>(input_size)); - char* b64_buffer = new char[b64_buffer_length]; - - // This is faster than apr_base64_encode() if you know - // you're not on an EBCDIC machine. Also, the output is - // null terminated, even though the documentation doesn't - // specify. See apr_base64.c for details. JC - b64_buffer_length = apr_base64_encode_binary( - b64_buffer, - input, - narrow<size_t>(input_size)); - output.assign(b64_buffer); - delete[] b64_buffer; - } - return output; -} - +/**
+ * @file llbase64.cpp
+ * @brief Wrapper for apr base64 encoding that returns a std::string
+ * @author James Cook
+ *
+ * $LicenseInfo:firstyear=2007&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llbase64.h"
+
+#include <string>
+
+#include "apr_base64.h"
+
+
+// static
+std::string LLBase64::encode(const U8* input, size_t input_size)
+{
+ std::string output;
+ if (input
+ && input_size > 0)
+ {
+ // Yes, it returns int.
+ int b64_buffer_length = apr_base64_encode_len(narrow<size_t>(input_size));
+ char* b64_buffer = new char[b64_buffer_length];
+
+ // This is faster than apr_base64_encode() if you know
+ // you're not on an EBCDIC machine. Also, the output is
+ // null terminated, even though the documentation doesn't
+ // specify. See apr_base64.c for details. JC
+ b64_buffer_length = apr_base64_encode_binary(
+ b64_buffer,
+ input,
+ narrow<size_t>(input_size));
+ output.assign(b64_buffer);
+ delete[] b64_buffer;
+ }
+ return output;
+}
+
+std::string LLBase64::decodeAsString(const std::string &input)
+{
+ int b64_buffer_length = apr_base64_decode_len(input.c_str());
+ char* b64_buffer = new char[b64_buffer_length];
+
+ // This is faster than apr_base64_encode() if you know
+ // you're not on an EBCDIC machine. Also, the output is
+ // null terminated, even though the documentation doesn't
+ // specify. See apr_base64.c for details. JC
+ b64_buffer_length = apr_base64_decode(b64_buffer, input.c_str());
+ std::string res;
+ res.assign(b64_buffer);
+ delete[] b64_buffer;
+ return res;
+}
+
diff --git a/indra/llcommon/llbase64.h b/indra/llcommon/llbase64.h index d4e9d97ea4..a64c4f1e54 100644 --- a/indra/llcommon/llbase64.h +++ b/indra/llcommon/llbase64.h @@ -1,37 +1,38 @@ -/** - * @file llbase64.h - * @brief Wrapper for apr base64 encoding that returns a std::string - * @author James Cook - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 LLBASE64_H -#define LLBASE64_H - -class LL_COMMON_API LLBase64 -{ -public: - static std::string encode(const U8* input, size_t input_size); -}; - -#endif +/**
+ * @file llbase64.h
+ * @brief Wrapper for apr base64 encoding that returns a std::string
+ * @author James Cook
+ *
+ * $LicenseInfo:firstyear=2007&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 LLBASE64_H
+#define LLBASE64_H
+
+class LL_COMMON_API LLBase64
+{
+public:
+ static std::string encode(const U8* input, size_t input_size);
+ static std::string decodeAsString(const std::string& input);
+};
+
+#endif
diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index b5a58e90b3..9705b69e11 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -1,230 +1,230 @@ -/** - * @file llcallbacklist.cpp - * @brief A simple list of callback functions to call. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "llcallbacklist.h" -#include "lleventtimer.h" -#include "llerrorlegacy.h" - -// Globals -// -LLCallbackList gIdleCallbacks; - -// -// Member functions -// - -LLCallbackList::LLCallbackList() -{ - // nothing -} - -LLCallbackList::~LLCallbackList() -{ -} - - -void LLCallbackList::addFunction( callback_t func, void *data) -{ - if (!func) - { - return; - } - - // only add one callback per func/data pair - // - if (containsFunction(func, data)) - { - return; - } - - callback_pair_t t(func, data); - mCallbackList.push_back(t); -} - -bool LLCallbackList::containsFunction( callback_t func, void *data) -{ - callback_pair_t t(func, data); - callback_list_t::iterator iter = find(func,data); - if (iter != mCallbackList.end()) - { - return TRUE; - } - else - { - return FALSE; - } -} - - -bool LLCallbackList::deleteFunction( callback_t func, void *data) -{ - callback_list_t::iterator iter = find(func,data); - if (iter != mCallbackList.end()) - { - mCallbackList.erase(iter); - return TRUE; - } - else - { - return FALSE; - } -} - -inline -LLCallbackList::callback_list_t::iterator -LLCallbackList::find(callback_t func, void *data) -{ - callback_pair_t t(func, data); - return std::find(mCallbackList.begin(), mCallbackList.end(), t); -} - -void LLCallbackList::deleteAllFunctions() -{ - mCallbackList.clear(); -} - - -void LLCallbackList::callFunctions() -{ - for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) - { - callback_list_t::iterator curiter = iter++; - curiter->first(curiter->second); - } -} - -// Shim class to allow arbitrary boost::bind -// expressions to be run as one-time idle callbacks. -class OnIdleCallbackOneTime -{ -public: - OnIdleCallbackOneTime(nullary_func_t callable): - mCallable(callable) - { - } - static void onIdle(void *data) - { - gIdleCallbacks.deleteFunction(onIdle, data); - OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data); - self->call(); - delete self; - } - void call() - { - mCallable(); - } -private: - nullary_func_t mCallable; -}; - -void doOnIdleOneTime(nullary_func_t callable) -{ - OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); -} - -// Shim class to allow generic boost functions to be run as -// recurring idle callbacks. Callable should return true when done, -// false to continue getting called. -class OnIdleCallbackRepeating -{ -public: - OnIdleCallbackRepeating(bool_func_t callable): - mCallable(callable) - { - } - // Will keep getting called until the callable returns true. - static void onIdle(void *data) - { - OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data); - bool done = self->call(); - if (done) - { - gIdleCallbacks.deleteFunction(onIdle, data); - delete self; - } - } - bool call() - { - return mCallable(); - } -private: - bool_func_t mCallable; -}; - -void doOnIdleRepeating(bool_func_t callable) -{ - OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); -} - -class NullaryFuncEventTimer: public LLEventTimer -{ -public: - NullaryFuncEventTimer(nullary_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } - -private: - BOOL tick() - { - mCallable(); - return TRUE; - } - - nullary_func_t mCallable; -}; - -// Call a given callable once after specified interval. -void doAfterInterval(nullary_func_t callable, F32 seconds) -{ - new NullaryFuncEventTimer(callable, seconds); -} - -class BoolFuncEventTimer: public LLEventTimer -{ -public: - BoolFuncEventTimer(bool_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } -private: - BOOL tick() - { - return mCallable(); - } - - bool_func_t mCallable; -}; - -// Call a given callable every specified number of seconds, until it returns true. -void doPeriodically(bool_func_t callable, F32 seconds) -{ - new BoolFuncEventTimer(callable, seconds); -} +/**
+ * @file llcallbacklist.cpp
+ * @brief A simple list of callback functions to call.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "llcallbacklist.h"
+#include "lleventtimer.h"
+#include "llerrorlegacy.h"
+
+// Globals
+//
+LLCallbackList gIdleCallbacks;
+
+//
+// Member functions
+//
+
+LLCallbackList::LLCallbackList()
+{
+ // nothing
+}
+
+LLCallbackList::~LLCallbackList()
+{
+}
+
+
+void LLCallbackList::addFunction( callback_t func, void *data)
+{
+ if (!func)
+ {
+ return;
+ }
+
+ // only add one callback per func/data pair
+ //
+ if (containsFunction(func, data))
+ {
+ return;
+ }
+
+ callback_pair_t t(func, data);
+ mCallbackList.push_back(t);
+}
+
+bool LLCallbackList::containsFunction( callback_t func, void *data)
+{
+ callback_pair_t t(func, data);
+ callback_list_t::iterator iter = find(func,data);
+ if (iter != mCallbackList.end())
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+bool LLCallbackList::deleteFunction( callback_t func, void *data)
+{
+ callback_list_t::iterator iter = find(func,data);
+ if (iter != mCallbackList.end())
+ {
+ mCallbackList.erase(iter);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+inline
+LLCallbackList::callback_list_t::iterator
+LLCallbackList::find(callback_t func, void *data)
+{
+ callback_pair_t t(func, data);
+ return std::find(mCallbackList.begin(), mCallbackList.end(), t);
+}
+
+void LLCallbackList::deleteAllFunctions()
+{
+ mCallbackList.clear();
+}
+
+
+void LLCallbackList::callFunctions()
+{
+ for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); )
+ {
+ callback_list_t::iterator curiter = iter++;
+ curiter->first(curiter->second);
+ }
+}
+
+// Shim class to allow arbitrary boost::bind
+// expressions to be run as one-time idle callbacks.
+class OnIdleCallbackOneTime
+{
+public:
+ OnIdleCallbackOneTime(nullary_func_t callable):
+ mCallable(callable)
+ {
+ }
+ static void onIdle(void *data)
+ {
+ gIdleCallbacks.deleteFunction(onIdle, data);
+ OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data);
+ self->call();
+ delete self;
+ }
+ void call()
+ {
+ mCallable();
+ }
+private:
+ nullary_func_t mCallable;
+};
+
+void doOnIdleOneTime(nullary_func_t callable)
+{
+ OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable);
+ gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor);
+}
+
+// Shim class to allow generic boost functions to be run as
+// recurring idle callbacks. Callable should return true when done,
+// false to continue getting called.
+class OnIdleCallbackRepeating
+{
+public:
+ OnIdleCallbackRepeating(bool_func_t callable):
+ mCallable(callable)
+ {
+ }
+ // Will keep getting called until the callable returns true.
+ static void onIdle(void *data)
+ {
+ OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data);
+ bool done = self->call();
+ if (done)
+ {
+ gIdleCallbacks.deleteFunction(onIdle, data);
+ delete self;
+ }
+ }
+ bool call()
+ {
+ return mCallable();
+ }
+private:
+ bool_func_t mCallable;
+};
+
+void doOnIdleRepeating(bool_func_t callable)
+{
+ OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable);
+ gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor);
+}
+
+class NullaryFuncEventTimer: public LLEventTimer
+{
+public:
+ NullaryFuncEventTimer(nullary_func_t callable, F32 seconds):
+ LLEventTimer(seconds),
+ mCallable(callable)
+ {
+ }
+
+private:
+ bool tick()
+ {
+ mCallable();
+ return true;
+ }
+
+ nullary_func_t mCallable;
+};
+
+// Call a given callable once after specified interval.
+void doAfterInterval(nullary_func_t callable, F32 seconds)
+{
+ new NullaryFuncEventTimer(callable, seconds);
+}
+
+class BoolFuncEventTimer: public LLEventTimer
+{
+public:
+ BoolFuncEventTimer(bool_func_t callable, F32 seconds):
+ LLEventTimer(seconds),
+ mCallable(callable)
+ {
+ }
+private:
+ bool tick()
+ {
+ return mCallable();
+ }
+
+ bool_func_t mCallable;
+};
+
+// Call a given callable every specified number of seconds, until it returns true.
+void doPeriodically(bool_func_t callable, F32 seconds)
+{
+ new BoolFuncEventTimer(callable, seconds);
+}
diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp index 14bdeb5c60..7a10662d3d 100644 --- a/indra/llcommon/llcommon.cpp +++ b/indra/llcommon/llcommon.cpp @@ -1,153 +1,153 @@ -/** - * @file llcommon.cpp - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llcommon.h" - -#include "llmemory.h" -#include "llthread.h" -#include "lltrace.h" -#include "lltracethreadrecorder.h" -#include "llcleanup.h" - -thread_local bool gProfilerEnabled = false; - -#if (TRACY_ENABLE) -// Override new/delete for tracy memory profiling - -void* ll_tracy_new(size_t size) -{ - void* ptr; - if (gProfilerEnabled) - { - //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - ptr = (malloc)(size); - } - else - { - ptr = (malloc)(size); - } - if (!ptr) - { - throw std::bad_alloc(); - } - TracyAlloc(ptr, size); - return ptr; -} - -void* operator new(size_t size) -{ - return ll_tracy_new(size); -} - -void* operator new[](std::size_t count) -{ - return ll_tracy_new(count); -} - -void ll_tracy_delete(void* ptr) -{ - TracyFree(ptr); - if (gProfilerEnabled) - { - //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - (free)(ptr); - } - else - { - (free)(ptr); - } -} - -void operator delete(void *ptr) noexcept -{ - ll_tracy_delete(ptr); -} - -void operator delete[](void* ptr) noexcept -{ - ll_tracy_delete(ptr); -} - -// C-style malloc/free can't be so easily overridden, so we define tracy versions and use -// a pre-processor #define in linden_common.h to redirect to them. The parens around the native -// functions below prevents recursive substitution by the preprocessor. -// -// Unaligned mallocs are rare in LL code but hooking them causes problems in 3p lib code (looking at -// you, Havok), so we'll only capture the aligned version. - -void *tracy_aligned_malloc(size_t size, size_t alignment) -{ - auto ptr = ll_aligned_malloc_fallback(size, alignment); - if (ptr) TracyAlloc(ptr, size); - return ptr; -} - -void tracy_aligned_free(void *memblock) -{ - TracyFree(memblock); - ll_aligned_free_fallback(memblock); -} - -#endif - -//static -BOOL LLCommon::sAprInitialized = FALSE; - -static LLTrace::ThreadRecorder* sMasterThreadRecorder = NULL; - -//static -void LLCommon::initClass() -{ - if (!sAprInitialized) - { - ll_init_apr(); - sAprInitialized = TRUE; - } - LLTimer::initClass(); - LLThreadSafeRefCount::initThreadSafeRefCount(); - assert_main_thread(); // Make sure we record the main thread - if (!sMasterThreadRecorder) - { - sMasterThreadRecorder = new LLTrace::ThreadRecorder(); - LLTrace::set_master_thread_recorder(sMasterThreadRecorder); - } -} - -//static -void LLCommon::cleanupClass() -{ - delete sMasterThreadRecorder; - sMasterThreadRecorder = NULL; - LLTrace::set_master_thread_recorder(NULL); - LLThreadSafeRefCount::cleanupThreadSafeRefCount(); - SUBSYSTEM_CLEANUP_DBG(LLTimer); - if (sAprInitialized) - { - ll_cleanup_apr(); - sAprInitialized = FALSE; - } -} +/**
+ * @file llcommon.cpp
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llcommon.h"
+
+#include "llmemory.h"
+#include "llthread.h"
+#include "lltrace.h"
+#include "lltracethreadrecorder.h"
+#include "llcleanup.h"
+
+thread_local bool gProfilerEnabled = false;
+
+#if (TRACY_ENABLE)
+// Override new/delete for tracy memory profiling
+
+void* ll_tracy_new(size_t size)
+{
+ void* ptr;
+ if (gProfilerEnabled)
+ {
+ //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ ptr = (malloc)(size);
+ }
+ else
+ {
+ ptr = (malloc)(size);
+ }
+ if (!ptr)
+ {
+ throw std::bad_alloc();
+ }
+ TracyAlloc(ptr, size);
+ return ptr;
+}
+
+void* operator new(size_t size)
+{
+ return ll_tracy_new(size);
+}
+
+void* operator new[](std::size_t count)
+{
+ return ll_tracy_new(count);
+}
+
+void ll_tracy_delete(void* ptr)
+{
+ TracyFree(ptr);
+ if (gProfilerEnabled)
+ {
+ //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ (free)(ptr);
+ }
+ else
+ {
+ (free)(ptr);
+ }
+}
+
+void operator delete(void *ptr) noexcept
+{
+ ll_tracy_delete(ptr);
+}
+
+void operator delete[](void* ptr) noexcept
+{
+ ll_tracy_delete(ptr);
+}
+
+// C-style malloc/free can't be so easily overridden, so we define tracy versions and use
+// a pre-processor #define in linden_common.h to redirect to them. The parens around the native
+// functions below prevents recursive substitution by the preprocessor.
+//
+// Unaligned mallocs are rare in LL code but hooking them causes problems in 3p lib code (looking at
+// you, Havok), so we'll only capture the aligned version.
+
+void *tracy_aligned_malloc(size_t size, size_t alignment)
+{
+ auto ptr = ll_aligned_malloc_fallback(size, alignment);
+ if (ptr) TracyAlloc(ptr, size);
+ return ptr;
+}
+
+void tracy_aligned_free(void *memblock)
+{
+ TracyFree(memblock);
+ ll_aligned_free_fallback(memblock);
+}
+
+#endif
+
+//static
+bool LLCommon::sAprInitialized = false;
+
+static LLTrace::ThreadRecorder* sMasterThreadRecorder = NULL;
+
+//static
+void LLCommon::initClass()
+{
+ if (!sAprInitialized)
+ {
+ ll_init_apr();
+ sAprInitialized = true;
+ }
+ LLTimer::initClass();
+ LLThreadSafeRefCount::initThreadSafeRefCount();
+ assert_main_thread(); // Make sure we record the main thread
+ if (!sMasterThreadRecorder)
+ {
+ sMasterThreadRecorder = new LLTrace::ThreadRecorder();
+ LLTrace::set_master_thread_recorder(sMasterThreadRecorder);
+ }
+}
+
+//static
+void LLCommon::cleanupClass()
+{
+ delete sMasterThreadRecorder;
+ sMasterThreadRecorder = NULL;
+ LLTrace::set_master_thread_recorder(NULL);
+ LLThreadSafeRefCount::cleanupThreadSafeRefCount();
+ SUBSYSTEM_CLEANUP_DBG(LLTimer);
+ if (sAprInitialized)
+ {
+ ll_cleanup_apr();
+ sAprInitialized = false;
+ }
+}
diff --git a/indra/llcommon/llcommon.h b/indra/llcommon/llcommon.h index 129e71f703..3e81bb1f79 100644 --- a/indra/llcommon/llcommon.h +++ b/indra/llcommon/llcommon.h @@ -1,43 +1,43 @@ -/** - * @file llcommon.h - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_COMMON_H -#define LL_COMMON_H - -// *TODO: remove these? -#include "lltimer.h" -#include "llfile.h" - -class LL_COMMON_API LLCommon -{ -public: - static void initClass(); - static void cleanupClass(); -private: - static BOOL sAprInitialized; -}; - -#endif - +/**
+ * @file llcommon.h
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_COMMON_H
+#define LL_COMMON_H
+
+// *TODO: remove these?
+#include "lltimer.h"
+#include "llfile.h"
+
+class LL_COMMON_API LLCommon
+{
+public:
+ static void initClass();
+ static void cleanupClass();
+private:
+ static bool sAprInitialized;
+};
+
+#endif
+
diff --git a/indra/llcommon/llcrc.cpp b/indra/llcommon/llcrc.cpp index 34aa7b46e8..c376269981 100644 --- a/indra/llcommon/llcrc.cpp +++ b/indra/llcommon/llcrc.cpp @@ -1,223 +1,223 @@ -/** - * @file llcrc.cpp - * @brief implementation of the crc class. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llcrc.h" -#include "llerror.h" - -/* Copyright (C) 1986 Gary S. Brown. You may use this program, or - code or tables extracted from it, as desired without restriction.*/ - -/* First, the polynomial itself and its table of feedback terms. The */ -/* polynomial is */ -/* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */ -/* Note that we take it "backwards" and put the highest-order term in */ -/* the lowest-order bit. The X^32 term is "implied"; the LSB is the */ -/* X^31 term, etc. The X^0 term (usually shown as "+1") results in */ -/* the MSB being 1. */ - -/* Note that the usual hardware shift register implementation, which */ -/* is what we're using (we're merely optimizing it by doing eight-bit */ -/* chunks at a time) shifts bits into the lowest-order term. In our */ -/* implementation, that means shifting towards the right. Why do we */ -/* do it this way? Because the calculated CRC must be transmitted in */ -/* order from highest-order term to lowest-order term. UARTs transmit */ -/* characters in order from LSB to MSB. By storing the CRC this way, */ -/* we hand it to the UART in the order low-byte to high-byte; the UART */ -/* sends each low-bit to hight-bit; and the result is transmission bit */ -/* by bit from highest- to lowest-order term without requiring any bit */ -/* shuffling on our part. Reception works similarly. */ - -/* The feedback terms table consists of 256, 32-bit entries. Notes: */ -/* */ -/* 1. The table can be generated at runtime if desired; code to do so */ -/* is shown later. It might not be obvious, but the feedback */ -/* terms simply represent the results of eight shift/xor opera- */ -/* tions for all combinations of data and CRC register values. */ -/* */ -/* 2. The CRC accumulation logic is the same for all CRC polynomials, */ -/* be they sixteen or thirty-two bits wide. You simply choose the */ -/* appropriate table. Alternatively, because the table can be */ -/* generated at runtime, you can start by generating the table for */ -/* the polynomial in question and use exactly the same "updcrc", */ -/* if your application needn't simultaneously handle two CRC */ -/* polynomials. (Note, however, that XMODEM is strange.) */ -/* */ -/* 3. For 16-bit CRCs, the table entries need be only 16 bits wide; */ -/* of course, 32-bit entries work OK if the high 16 bits are zero. */ -/* */ -/* 4. The values must be right-shifted by eight bits by the "updcrc" */ -/* logic; the shift must be unsigned (bring in zeroes). On some */ -/* hardware you could probably optimize the shift in assembler by */ -/* using byte-swap instructions. */ - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -#define UPDC32(octet,crc) (crc_32_tab[((crc) \ - ^ ((U8)octet)) & 0xff] ^ ((crc) >> 8)) - - -static U32 crc_32_tab[] = { /* CRC polynomial 0xedb88320 */ -0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, -0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, -0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, -0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, -0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, -0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, -0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, -0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, -0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, -0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, -0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, -0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, -0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, -0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, -0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, -0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, -0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, -0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, -0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, -0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, -0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, -0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, -0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, -0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, -0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, -0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, -0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, -0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, -0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, -0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, -0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, -0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, -0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, -0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, -0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, -0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, -0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, -0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, -0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, -0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, -0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, -0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, -0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d -}; - - -///---------------------------------------------------------------------------- -/// Class llcrc -///---------------------------------------------------------------------------- - -// Default constructor -LLCRC::LLCRC() : mCurrent(0xffffffff) -{ -} - - -U32 LLCRC::getCRC() const -{ - return ~mCurrent; -} - -void LLCRC::update(U8 next_byte) -{ - mCurrent = UPDC32(next_byte, mCurrent); -} - -void LLCRC::update(const U8* buffer, size_t buffer_size) -{ - for (size_t i = 0; i < buffer_size; i++) - { - mCurrent = UPDC32(buffer[i], mCurrent); - } -} - -void LLCRC::update(const std::string& filename) -{ - if (filename.empty()) - { - LL_ERRS() << "No filename specified" << LL_ENDL; - return; - } - - FILE* fp = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */ - - if (fp) - { - fseek(fp, 0, SEEK_END); - long size = ftell(fp); - - fseek(fp, 0, SEEK_SET); - - if (size > 0) - { - U8* data = new U8[size]; - size_t nread; - - nread = fread(data, 1, size, fp); - fclose(fp); - - if (nread < (size_t) size) - { - LL_WARNS() << "Short read on " << filename << LL_ENDL; - } - - update(data, nread); - delete[] data; - } - else - { - fclose(fp); - } - } -} - - -#ifdef _DEBUG -BOOL LLCRC::testHarness() -{ - const S32 TEST_BUFFER_SIZE = 16; - const char TEST_BUFFER[TEST_BUFFER_SIZE] = "hello &#$)$&Nd0"; /* Flawfinder: ignore */ - LLCRC c1, c2; - c1.update((U8*)TEST_BUFFER, TEST_BUFFER_SIZE - 1); - char* rh = (char*)TEST_BUFFER; - while(*rh != '\0') - { - c2.update(*rh); - ++rh; - } - return(c1.getCRC() == c2.getCRC()); -} -#endif - - - -///---------------------------------------------------------------------------- -/// Local function definitions -///---------------------------------------------------------------------------- +/**
+ * @file llcrc.cpp
+ * @brief implementation of the crc class.
+ *
+ * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llcrc.h"
+#include "llerror.h"
+
+/* Copyright (C) 1986 Gary S. Brown. You may use this program, or
+ code or tables extracted from it, as desired without restriction.*/
+
+/* First, the polynomial itself and its table of feedback terms. The */
+/* polynomial is */
+/* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */
+/* Note that we take it "backwards" and put the highest-order term in */
+/* the lowest-order bit. The X^32 term is "implied"; the LSB is the */
+/* X^31 term, etc. The X^0 term (usually shown as "+1") results in */
+/* the MSB being 1. */
+
+/* Note that the usual hardware shift register implementation, which */
+/* is what we're using (we're merely optimizing it by doing eight-bit */
+/* chunks at a time) shifts bits into the lowest-order term. In our */
+/* implementation, that means shifting towards the right. Why do we */
+/* do it this way? Because the calculated CRC must be transmitted in */
+/* order from highest-order term to lowest-order term. UARTs transmit */
+/* characters in order from LSB to MSB. By storing the CRC this way, */
+/* we hand it to the UART in the order low-byte to high-byte; the UART */
+/* sends each low-bit to hight-bit; and the result is transmission bit */
+/* by bit from highest- to lowest-order term without requiring any bit */
+/* shuffling on our part. Reception works similarly. */
+
+/* The feedback terms table consists of 256, 32-bit entries. Notes: */
+/* */
+/* 1. The table can be generated at runtime if desired; code to do so */
+/* is shown later. It might not be obvious, but the feedback */
+/* terms simply represent the results of eight shift/xor opera- */
+/* tions for all combinations of data and CRC register values. */
+/* */
+/* 2. The CRC accumulation logic is the same for all CRC polynomials, */
+/* be they sixteen or thirty-two bits wide. You simply choose the */
+/* appropriate table. Alternatively, because the table can be */
+/* generated at runtime, you can start by generating the table for */
+/* the polynomial in question and use exactly the same "updcrc", */
+/* if your application needn't simultaneously handle two CRC */
+/* polynomials. (Note, however, that XMODEM is strange.) */
+/* */
+/* 3. For 16-bit CRCs, the table entries need be only 16 bits wide; */
+/* of course, 32-bit entries work OK if the high 16 bits are zero. */
+/* */
+/* 4. The values must be right-shifted by eight bits by the "updcrc" */
+/* logic; the shift must be unsigned (bring in zeroes). On some */
+/* hardware you could probably optimize the shift in assembler by */
+/* using byte-swap instructions. */
+
+///----------------------------------------------------------------------------
+/// Local function declarations, constants, enums, and typedefs
+///----------------------------------------------------------------------------
+
+#define UPDC32(octet,crc) (crc_32_tab[((crc) \
+ ^ ((U8)octet)) & 0xff] ^ ((crc) >> 8))
+
+
+static U32 crc_32_tab[] = { /* CRC polynomial 0xedb88320 */
+0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+
+///----------------------------------------------------------------------------
+/// Class llcrc
+///----------------------------------------------------------------------------
+
+// Default constructor
+LLCRC::LLCRC() : mCurrent(0xffffffff)
+{
+}
+
+
+U32 LLCRC::getCRC() const
+{
+ return ~mCurrent;
+}
+
+void LLCRC::update(U8 next_byte)
+{
+ mCurrent = UPDC32(next_byte, mCurrent);
+}
+
+void LLCRC::update(const U8* buffer, size_t buffer_size)
+{
+ for (size_t i = 0; i < buffer_size; i++)
+ {
+ mCurrent = UPDC32(buffer[i], mCurrent);
+ }
+}
+
+void LLCRC::update(const std::string& filename)
+{
+ if (filename.empty())
+ {
+ LL_ERRS() << "No filename specified" << LL_ENDL;
+ return;
+ }
+
+ FILE* fp = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */
+
+ if (fp)
+ {
+ fseek(fp, 0, SEEK_END);
+ long size = ftell(fp);
+
+ fseek(fp, 0, SEEK_SET);
+
+ if (size > 0)
+ {
+ U8* data = new U8[size];
+ size_t nread;
+
+ nread = fread(data, 1, size, fp);
+ fclose(fp);
+
+ if (nread < (size_t) size)
+ {
+ LL_WARNS() << "Short read on " << filename << LL_ENDL;
+ }
+
+ update(data, nread);
+ delete[] data;
+ }
+ else
+ {
+ fclose(fp);
+ }
+ }
+}
+
+
+#ifdef _DEBUG
+bool LLCRC::testHarness()
+{
+ const S32 TEST_BUFFER_SIZE = 16;
+ const char TEST_BUFFER[TEST_BUFFER_SIZE] = "hello &#$)$&Nd0"; /* Flawfinder: ignore */
+ LLCRC c1, c2;
+ c1.update((U8*)TEST_BUFFER, TEST_BUFFER_SIZE - 1);
+ char* rh = (char*)TEST_BUFFER;
+ while(*rh != '\0')
+ {
+ c2.update(*rh);
+ ++rh;
+ }
+ return(c1.getCRC() == c2.getCRC());
+}
+#endif
+
+
+
+///----------------------------------------------------------------------------
+/// Local function definitions
+///----------------------------------------------------------------------------
diff --git a/indra/llcommon/llcrc.h b/indra/llcommon/llcrc.h index 3b48b778ff..d6fd008740 100644 --- a/indra/llcommon/llcrc.h +++ b/indra/llcommon/llcrc.h @@ -1,68 +1,68 @@ -/** - * @file llcrc.h - * @brief LLCRC class header file. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLCRC_H -#define LL_LLCRC_H - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class llcrc -// -// Simple 32 bit crc. To use, instantiate an LLCRC instance and feed -// it the bytes you want to check. It will update the internal crc as -// you go, and you can qery it at the end. As a horribly inefficient -// example (don't try this at work kids): -// -// LLCRC crc; -// FILE* fp = LLFile::fopen(filename,"rb"); -// while(!feof(fp)) { -// crc.update(fgetc(fp)); -// } -// fclose(fp); -// LL_INFOS() << "File crc: " << crc.getCRC() << LL_ENDL; -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LL_COMMON_API LLCRC -{ -protected: - U32 mCurrent; - -public: - LLCRC(); - - U32 getCRC() const; - void update(U8 next_byte); - void update(const U8* buffer, size_t buffer_size); - void update(const std::string& filename); - -#ifdef _DEBUG - // This function runs tests to make sure the crc is - // working. Returns TRUE if it is. - static BOOL testHarness(); -#endif -}; - - -#endif // LL_LLCRC_H +/**
+ * @file llcrc.h
+ * @brief LLCRC class header file.
+ *
+ * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLCRC_H
+#define LL_LLCRC_H
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class llcrc
+//
+// Simple 32 bit crc. To use, instantiate an LLCRC instance and feed
+// it the bytes you want to check. It will update the internal crc as
+// you go, and you can qery it at the end. As a horribly inefficient
+// example (don't try this at work kids):
+//
+// LLCRC crc;
+// FILE* fp = LLFile::fopen(filename,"rb");
+// while(!feof(fp)) {
+// crc.update(fgetc(fp));
+// }
+// fclose(fp);
+// LL_INFOS() << "File crc: " << crc.getCRC() << LL_ENDL;
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+class LL_COMMON_API LLCRC
+{
+protected:
+ U32 mCurrent;
+
+public:
+ LLCRC();
+
+ U32 getCRC() const;
+ void update(U8 next_byte);
+ void update(const U8* buffer, size_t buffer_size);
+ void update(const std::string& filename);
+
+#ifdef _DEBUG
+ // This function runs tests to make sure the crc is
+ // working. Returns true if it is.
+ static bool testHarness();
+#endif
+};
+
+
+#endif // LL_LLCRC_H
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index e4843a88eb..80fcaefad2 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1,1662 +1,1662 @@ -/** - * @file llerror.cpp - * @date December 2006 - * @brief error message system - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llerror.h" -#include "llerrorcontrol.h" -#include "llsdutil.h" - -#include <cctype> -#ifdef __GNUC__ -# include <cxxabi.h> -#endif // __GNUC__ -#include <sstream> -#if !LL_WINDOWS -# include <syslog.h> -# include <unistd.h> -# include <sys/stat.h> -#else -# include <io.h> -#endif // !LL_WINDOWS -#include <vector> -#include "string.h" - -#include "llapp.h" -#include "llapr.h" -#include "llfile.h" -#include "lllivefile.h" -#include "llsd.h" -#include "llsdserialize.h" -#include "llsingleton.h" -#include "llstl.h" -#include "lltimer.h" - -// On Mac, got: -// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define -// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if -// _Unwind_Backtrace is available without `_GNU_SOURCE`." -#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED -#include <boost/stacktrace.hpp> - -namespace { -#if LL_WINDOWS - void debugger_print(const std::string& s) - { - // Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C - // which works just fine under the windows debugger, but can cause users who - // have enabled SEHOP exception chain validation to crash due to interactions - // between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707 - // - if (IsDebuggerPresent()) - { - // Need UTF16 for Unicode OutputDebugString - // - if (s.size()) - { - OutputDebugString(utf8str_to_utf16str(s).c_str()); - OutputDebugString(TEXT("\n")); - } - } - } -#else - class RecordToSyslog : public LLError::Recorder - { - public: - RecordToSyslog(const std::string& identity) - : mIdentity(identity) - { - openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0); - // we need to set the string from a local copy of the string - // since apparanetly openlog expects the const char* to remain - // valid even after it returns (presumably until closelog) - } - - ~RecordToSyslog() - { - closelog(); - } - - virtual bool enabled() override - { - return LLError::getEnabledLogTypesMask() & 0x01; - } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - int syslogPriority = LOG_CRIT; - switch (level) { - case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break; - case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break; - case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break; - case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break; - default: syslogPriority = LOG_CRIT; - } - - syslog(syslogPriority, "%s", message.c_str()); - } - private: - std::string mIdentity; - }; -#endif - - class RecordToFile : public LLError::Recorder - { - public: - RecordToFile(const std::string& filename): - mName(filename) - { - mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app); - if (!mFile) - { - LL_INFOS() << "Error setting log file to " << filename << LL_ENDL; - } - else - { - if (!LLError::getAlwaysFlush()) - { - mFile.sync_with_stdio(false); - } - } - } - - ~RecordToFile() - { - mFile.close(); - } - - virtual bool enabled() override - { -#ifdef LL_RELEASE_FOR_DOWNLOAD - return 1; -#else - return LLError::getEnabledLogTypesMask() & 0x02; -#endif - } - - bool okay() const { return mFile.good(); } - - std::string getFilename() const { return mName; } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - if (LLError::getAlwaysFlush()) - { - mFile << message << std::endl; - } - else - { - mFile << message << "\n"; - } - } - - private: - const std::string mName; - llofstream mFile; - }; - - - class RecordToStderr : public LLError::Recorder - { - public: - RecordToStderr(bool timestamp) : mUseANSI(checkANSI()) - { - this->showMultiline(true); - } - - virtual bool enabled() override - { - return LLError::getEnabledLogTypesMask() & 0x04; - } - - LL_FORCE_INLINE std::string createBoldANSI() - { - std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += "1"; - ansi_code += "m"; - - return ansi_code; - } - - LL_FORCE_INLINE std::string createResetANSI() - { - std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += "0"; - ansi_code += "m"; - - return ansi_code; - } - - LL_FORCE_INLINE std::string createANSI(const std::string& color) - { - std::string ansi_code; - ansi_code += '\033'; - ansi_code += "["; - ansi_code += "38;5;"; - ansi_code += color; - ansi_code += "m"; - - return ansi_code; - } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - // The default colors for error, warn and debug are now a bit more pastel - // and easier to read on the default (black) terminal background but you - // now have the option to set the color of each via an environment variables: - // LL_ANSI_ERROR_COLOR_CODE (default is red) - // LL_ANSI_WARN_COLOR_CODE (default is blue) - // LL_ANSI_DEBUG_COLOR_CODE (default is magenta) - // The list of color codes can be found in many places but I used this page: - // https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors - // (Note: you may need to restart Visual Studio to pick environment changes) - char* val = nullptr; - std::string s_ansi_error_code = "160"; - if ((val = getenv("LL_ANSI_ERROR_COLOR_CODE")) != nullptr) s_ansi_error_code = std::string(val); - std::string s_ansi_warn_code = "33"; - if ((val = getenv("LL_ANSI_WARN_COLOR_CODE")) != nullptr) s_ansi_warn_code = std::string(val); - std::string s_ansi_debug_code = "177"; - if ((val = getenv("LL_ANSI_DEBUG_COLOR_CODE")) != nullptr) s_ansi_debug_code = std::string(val); - - static std::string s_ansi_error = createANSI(s_ansi_error_code); // default is red - static std::string s_ansi_warn = createANSI(s_ansi_warn_code); // default is blue - static std::string s_ansi_debug = createANSI(s_ansi_debug_code); // default is magenta - - if (mUseANSI) - { - writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error : - (level == LLError::LEVEL_WARN) ? s_ansi_warn : - s_ansi_debug, message); - } - else - { - LL_PROFILE_ZONE_NAMED("fprintf"); - fprintf(stderr, "%s\n", message.c_str()); - } - } - - private: - bool mUseANSI; - - LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - static std::string s_ansi_bold = createBoldANSI(); // bold text - static std::string s_ansi_reset = createResetANSI(); // reset - // ANSI color code escape sequence, message, and reset in one fprintf call - // Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries. - fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() ); - } - - static bool checkANSI(void) - { - // Check whether it's okay to use ANSI; if stderr is - // a tty then we assume yes. Can be turned off with - // the LL_NO_ANSI_COLOR env var. - return (0 != isatty(2)) && - (NULL == getenv("LL_NO_ANSI_COLOR")); - } - }; - - class RecordToFixedBuffer : public LLError::Recorder - { - public: - RecordToFixedBuffer(LLLineBuffer* buffer) - : mBuffer(buffer) - { - this->showMultiline(true); - this->showTags(false); - this->showLocation(false); - } - - virtual bool enabled() override - { - return LLError::getEnabledLogTypesMask() & 0x08; - } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - mBuffer->addLine(message); - } - - private: - LLLineBuffer* mBuffer; - }; - -#if LL_WINDOWS - class RecordToWinDebug: public LLError::Recorder - { - public: - RecordToWinDebug() - { - this->showMultiline(true); - this->showTags(false); - this->showLocation(false); - } - - virtual bool enabled() override - { - return LLError::getEnabledLogTypesMask() & 0x10; - } - - virtual void recordMessage(LLError::ELevel level, - const std::string& message) override - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - debugger_print(message); - } - }; -#endif -} - - -namespace -{ - std::string className(const std::type_info& type) - { - return LLError::Log::demangle(type.name()); - } -} // anonymous - -namespace LLError -{ - std::string Log::demangle(const char* mangled) - { -#ifdef __GNUC__ - // GCC: type_info::name() returns a mangled class name,st demangle - // passing nullptr, 0 forces allocation of a unique buffer we can free - // fixing MAINT-8724 on OSX 10.14 - int status = -1; - char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status); - std::string result(name ? name : mangled); - free(name); - return result; - -#elif LL_WINDOWS - // Visual Studio: type_info::name() includes the text "class " at the start - std::string name = mangled; - for (const auto& prefix : std::vector<std::string>{ "class ", "struct " }) - { - if (0 == name.compare(0, prefix.length(), prefix)) - { - return name.substr(prefix.length()); - } - } - // huh, that's odd, we should see one or the other prefix -- but don't - // try to log unless logging is already initialized - // in Python, " or ".join(vector) -- but in C++, a PITB - LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '" - << name << "'" << LL_ENDL; - return name; - -#else // neither GCC nor Visual Studio - return mangled; -#endif - } -} // LLError - -namespace -{ - std::string functionName(const std::string& preprocessor_name) - { -#if LL_WINDOWS - // DevStudio: the __FUNCTION__ macro string includes - // the type and/or namespace prefixes - - std::string::size_type p = preprocessor_name.rfind(':'); - if (p == std::string::npos) - { - return preprocessor_name; - } - return preprocessor_name.substr(p + 1); - -#else - return preprocessor_name; -#endif - } - - - class LogControlFile : public LLLiveFile - { - LOG_CLASS(LogControlFile); - - public: - static LogControlFile& fromDirectory(const std::string& user_dir, const std::string& app_dir); - - virtual bool loadFile(); - - private: - LogControlFile(const std::string &filename) - : LLLiveFile(filename) - { } - }; - - LogControlFile& LogControlFile::fromDirectory(const std::string& user_dir, const std::string& app_dir) - { - // NB: We have no abstraction in llcommon for the "proper" - // delimiter but it turns out that "/" works on all three platforms - - 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"; - } - return * new LogControlFile(file); - // NB: This instance is never freed - } - - bool LogControlFile::loadFile() - { - LLSD configuration; - - { - llifstream file(filename().c_str()); - if (!file.is_open()) - { - LL_WARNS() << filename() << " failed to open file; not changing configuration" << LL_ENDL; - return false; - } - - if (LLSDSerialize::fromXML(configuration, file) == LLSDParser::PARSE_FAILURE) - { - LL_WARNS() << filename() << " parcing error; not changing configuration" << LL_ENDL; - return false; - } - - if (! configuration || !configuration.isMap()) - { - LL_WARNS() << filename() << " missing, ill-formed, or simply undefined" - " content; not changing configuration" - << LL_ENDL; - return false; - } - } - - LLError::configure(configuration); - LL_INFOS("LogControlFile") << "logging reconfigured from " << filename() << LL_ENDL; - return true; - } - - - typedef std::map<std::string, LLError::ELevel> LevelMap; - typedef std::vector<LLError::RecorderPtr> Recorders; - typedef std::vector<LLError::CallSite*> CallSiteVector; - - class SettingsConfig : public LLRefCount - { - friend class Globals; - - public: - virtual ~SettingsConfig(); - - LLError::ELevel mDefaultLevel; - - bool mLogAlwaysFlush; - - U32 mEnabledLogTypesMask; - - LevelMap mFunctionLevelMap; - LevelMap mClassLevelMap; - LevelMap mFileLevelMap; - LevelMap mTagLevelMap; - std::map<std::string, unsigned int> mUniqueLogMessages; - - LLError::FatalFunction mCrashFunction; - LLError::TimeFunction mTimeFunction; - - Recorders mRecorders; - LLMutex mRecorderMutex; - - int mShouldLogCallCounter; - - private: - SettingsConfig(); - }; - - typedef LLPointer<SettingsConfig> SettingsConfigPtr; - - SettingsConfig::SettingsConfig() - : LLRefCount(), - mDefaultLevel(LLError::LEVEL_DEBUG), - mLogAlwaysFlush(true), - mEnabledLogTypesMask(255), - mFunctionLevelMap(), - mClassLevelMap(), - mFileLevelMap(), - mTagLevelMap(), - mUniqueLogMessages(), - mCrashFunction(NULL), - mTimeFunction(NULL), - mRecorders(), - mRecorderMutex(), - mShouldLogCallCounter(0) - { - } - - SettingsConfig::~SettingsConfig() - { - mRecorders.clear(); - } - - class Globals - { - public: - static Globals* getInstance(); - protected: - Globals(); - public: - std::string mFatalMessage; - - void addCallSite(LLError::CallSite&); - void invalidateCallSites(); - - SettingsConfigPtr getSettingsConfig(); - - void resetSettingsConfig(); - LLError::SettingsStoragePtr saveAndResetSettingsConfig(); - void restore(LLError::SettingsStoragePtr pSettingsStorage); - private: - CallSiteVector callSites; - SettingsConfigPtr mSettingsConfig; - }; - - Globals::Globals() - : - callSites(), - mSettingsConfig(new SettingsConfig()) - { - } - - - Globals* Globals::getInstance() - { - // According to C++11 Function-Local Initialization - // of static variables is supposed to be thread safe - // without risk of deadlocks. - static Globals inst; - - return &inst; - } - - void Globals::addCallSite(LLError::CallSite& site) - { - callSites.push_back(&site); - } - - void Globals::invalidateCallSites() - { - for (LLError::CallSite* site : callSites) - { - site->invalidate(); - } - - callSites.clear(); - } - - SettingsConfigPtr Globals::getSettingsConfig() - { - return mSettingsConfig; - } - - void Globals::resetSettingsConfig() - { - invalidateCallSites(); - mSettingsConfig = new SettingsConfig(); - } - - LLError::SettingsStoragePtr Globals::saveAndResetSettingsConfig() - { - LLError::SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get()); - resetSettingsConfig(); - return oldSettingsConfig; - } - - void Globals::restore(LLError::SettingsStoragePtr pSettingsStorage) - { - invalidateCallSites(); - SettingsConfigPtr newSettingsConfig(dynamic_cast<SettingsConfig *>(pSettingsStorage.get())); - mSettingsConfig = newSettingsConfig; - } -} - -namespace LLError -{ - CallSite::CallSite(ELevel level, - const char* file, - int line, - const std::type_info& class_info, - const char* function, - bool printOnce, - const char** tags, - size_t tag_count) - : mLevel(level), - mFile(file), - mLine(line), - mClassInfo(class_info), - mFunction(function), - mCached(false), - mShouldLog(false), - mPrintOnce(printOnce), - mTags(new const char* [tag_count]), - mTagCount(tag_count) - { - switch (mLevel) - { - case LEVEL_DEBUG: mLevelString = "DEBUG"; break; - case LEVEL_INFO: mLevelString = "INFO"; break; - case LEVEL_WARN: mLevelString = "WARNING"; break; - case LEVEL_ERROR: mLevelString = "ERROR"; break; - default: mLevelString = "XXX"; break; - }; - - mLocationString = llformat("%s(%d)", abbreviateFile(mFile).c_str(), mLine); -#if LL_WINDOWS - // DevStudio: __FUNCTION__ already includes the full class name -#else -#if LL_LINUX - // gross, but typeid comparison seems to always fail here with gcc4.1 - if (0 != strcmp(mClassInfo.name(), typeid(NoClassInfo).name())) -#else - if (mClassInfo != typeid(NoClassInfo)) -#endif // LL_LINUX - { - mFunctionString = className(mClassInfo) + "::"; - } -#endif - mFunctionString += std::string(mFunction); - - for (int i = 0; i < tag_count; i++) - { - if (strchr(tags[i], ' ')) - { - LL_ERRS() << "Space is not allowed in a log tag at " << mLocationString << LL_ENDL; - } - mTags[i] = tags[i]; - } - - mTagString.append("#"); - // always construct a tag sequence; will be just a single # if no tag - for (size_t i = 0; i < mTagCount; i++) - { - mTagString.append(mTags[i]); - mTagString.append("#"); - } - } - - CallSite::~CallSite() - { - delete []mTags; - } - - void CallSite::invalidate() - { - mCached = false; - } -} - -namespace -{ - bool shouldLogToStderr() - { -#if LL_DARWIN - // On Mac OS X, stderr from apps launched from the Finder goes to the - // console log. It's generally considered bad form to spam too much - // there. That scenario can be detected by noticing that stderr is a - // character device (S_IFCHR). - - // If stderr is a tty or a pipe, assume the user launched from the - // command line or debugger and therefore wants to see stderr. - if (isatty(STDERR_FILENO)) - return true; - // not a tty, but might still be a pipe -- check - struct stat st; - if (fstat(STDERR_FILENO, &st) < 0) - { - // capture errno right away, before engaging any other operations - auto errno_save = errno; - // this gets called during log-system setup -- can't log yet! - std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno " - << errno_save << std::endl; - // if we can't tell, err on the safe side and don't write stderr - return false; - } - - // fstat() worked: return true only if stderr is a pipe - return ((st.st_mode & S_IFMT) == S_IFIFO); -#else - return true; -#endif - } - - bool stderrLogWantsTime() - { -#if LL_WINDOWS - return false; -#else - return true; -#endif - } - - - void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true) - { - Globals::getInstance()->resetSettingsConfig(); - - LLError::setDefaultLevel(LLError::LEVEL_INFO); - LLError::setAlwaysFlush(true); - LLError::setEnabledLogTypesMask(0xFFFFFFFF); - LLError::setTimeFunction(LLError::utcTime); - - // log_to_stderr is only false in the unit and integration tests to keep builds quieter - if (log_to_stderr && shouldLogToStderr()) - { - LLError::logToStderr(); - } - -#if LL_WINDOWS - LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug()); - LLError::addRecorder(recordToWinDebug); -#endif - - LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir); - - // NOTE: We want to explicitly load the file before we add it to the event timer - // that checks for changes to the file. Else, we're not actually loading the file yet, - // and most of the initialization happens without any attention being paid to the - // log control file. Not to mention that when it finally gets checked later, - // all log statements that have been evaluated already become dirty and need to be - // evaluated for printing again. So, make sure to call checkAndReload() - // before addToEventTimer(). - e.checkAndReload(); - e.addToEventTimer(); - } -} - -namespace LLError -{ - void initForApplication(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr) - { - commonInit(user_dir, app_dir, log_to_stderr); - } - - void setFatalFunction(const FatalFunction& f) - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - s->mCrashFunction = f; - } - - FatalFunction getFatalFunction() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mCrashFunction; - } - - std::string getFatalMessage() - { - return Globals::getInstance()->mFatalMessage; - } - - void setTimeFunction(TimeFunction f) - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - s->mTimeFunction = f; - } - - void setDefaultLevel(ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mDefaultLevel = level; - } - - ELevel getDefaultLevel() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mDefaultLevel; - } - - void setAlwaysFlush(bool flush) - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - s->mLogAlwaysFlush = flush; - } - - bool getAlwaysFlush() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mLogAlwaysFlush; - } - - void setEnabledLogTypesMask(U32 mask) - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - s->mEnabledLogTypesMask = mask; - } - - U32 getEnabledLogTypesMask() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mEnabledLogTypesMask; - } - - void setFunctionLevel(const std::string& function_name, ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mFunctionLevelMap[function_name] = level; - } - - void setClassLevel(const std::string& class_name, ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mClassLevelMap[class_name] = level; - } - - void setFileLevel(const std::string& file_name, ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mFileLevelMap[file_name] = level; - } - - void setTagLevel(const std::string& tag_name, ELevel level) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - s->mTagLevelMap[tag_name] = level; - } - - LLError::ELevel decodeLevel(std::string name) - { - static LevelMap level_names; - if (level_names.empty()) - { - level_names["ALL"] = LLError::LEVEL_ALL; - level_names["DEBUG"] = LLError::LEVEL_DEBUG; - level_names["INFO"] = LLError::LEVEL_INFO; - level_names["WARN"] = LLError::LEVEL_WARN; - level_names["ERROR"] = LLError::LEVEL_ERROR; - level_names["NONE"] = LLError::LEVEL_NONE; - } - - std::transform(name.begin(), name.end(), name.begin(), toupper); - - LevelMap::const_iterator i = level_names.find(name); - if (i == level_names.end()) - { - LL_WARNS() << "unrecognized logging level: '" << name << "'" << LL_ENDL; - return LLError::LEVEL_INFO; - } - - return i->second; - } -} - -namespace { - void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level) - { - LLSD::array_const_iterator i, end; - for (i = list.beginArray(), end = list.endArray(); i != end; ++i) - { - map[*i] = level; - } - } -} - -namespace LLError -{ - void configure(const LLSD& config) - { - Globals *g = Globals::getInstance(); - g->invalidateCallSites(); - SettingsConfigPtr s = g->getSettingsConfig(); - - s->mFunctionLevelMap.clear(); - s->mClassLevelMap.clear(); - s->mFileLevelMap.clear(); - s->mTagLevelMap.clear(); - s->mUniqueLogMessages.clear(); - - setDefaultLevel(decodeLevel(config["default-level"])); - if (config.has("log-always-flush")) - { - setAlwaysFlush(config["log-always-flush"]); - } - if (config.has("enabled-log-types-mask")) - { - setEnabledLogTypesMask(config["enabled-log-types-mask"].asInteger()); - } - - if (config.has("settings") && config["settings"].isArray()) - { - LLSD sets = config["settings"]; - LLSD::array_const_iterator a, end; - for (a = sets.beginArray(), end = sets.endArray(); a != end; ++a) - { - const LLSD& entry = *a; - if (entry.isMap() && entry.size() != 0) - { - ELevel level = decodeLevel(entry["level"]); - - setLevels(s->mFunctionLevelMap, entry["functions"], level); - setLevels(s->mClassLevelMap, entry["classes"], level); - setLevels(s->mFileLevelMap, entry["files"], level); - setLevels(s->mTagLevelMap, entry["tags"], level); - } - } - } - } -} - - -namespace LLError -{ - Recorder::Recorder() - : mWantsTime(true) - , mWantsTags(true) - , mWantsLevel(true) - , mWantsLocation(true) - , mWantsFunctionName(true) - , mWantsMultiline(false) - { - } - - Recorder::~Recorder() - { - } - - bool Recorder::wantsTime() - { - return mWantsTime; - } - - // virtual - bool Recorder::wantsTags() - { - return mWantsTags; - } - - // virtual - bool Recorder::wantsLevel() - { - return mWantsLevel; - } - - // virtual - bool Recorder::wantsLocation() - { - return mWantsLocation; - } - - // virtual - bool Recorder::wantsFunctionName() - { - return mWantsFunctionName; - } - - // virtual - bool Recorder::wantsMultiline() - { - return mWantsMultiline; - } - - void Recorder::showTime(bool show) - { - mWantsTime = show; - } - - void Recorder::showTags(bool show) - { - mWantsTags = show; - } - - void Recorder::showLevel(bool show) - { - mWantsLevel = show; - } - - void Recorder::showLocation(bool show) - { - mWantsLocation = show; - } - - void Recorder::showFunctionName(bool show) - { - mWantsFunctionName = show; - } - - void Recorder::showMultiline(bool show) - { - mWantsMultiline = show; - } - - void addRecorder(RecorderPtr recorder) - { - if (!recorder) - { - return; - } - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - s->mRecorders.push_back(recorder); - } - - void removeRecorder(RecorderPtr recorder) - { - if (!recorder) - { - return; - } - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder), - s->mRecorders.end()); - } - - // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to - // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which - // points to the Recorder base class), but a shared_ptr<RECORDER> which - // specifically points to the concrete RECORDER subclass instance, along - // with a Recorders::iterator indicating the position of that entry in - // mRecorders. The shared_ptr might be empty (operator!() returns true) if - // there was no such RECORDER subclass instance in mRecorders. - // - // NOTE!!! Requires external mutex lock!!! - template <typename RECORDER> - std::pair<std::shared_ptr<RECORDER>, Recorders::iterator> - findRecorderPos(SettingsConfigPtr &s) - { - // Since we promise to return an iterator, use a classic iterator - // loop. - auto end{s->mRecorders.end()}; - for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it) - { - // *it is a RecorderPtr, a shared_ptr<Recorder>. Use a - // dynamic_pointer_cast to try to downcast to test if it's also a - // shared_ptr<RECORDER>. - auto ptr = std::dynamic_pointer_cast<RECORDER>(*it); - if (ptr) - { - // found the entry we want - return { ptr, it }; - } - } - // dropped out of the loop without finding any such entry -- instead - // of default-constructing Recorders::iterator (which might or might - // not be valid), return a value that is valid but not dereferenceable. - return { {}, end }; - } - - // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to - // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which - // points to the Recorder base class), but a shared_ptr<RECORDER> which - // specifically points to the concrete RECORDER subclass instance. The - // shared_ptr might be empty (operator!() returns true) if there was no - // such RECORDER subclass instance in mRecorders. - template <typename RECORDER> - std::shared_ptr<RECORDER> findRecorder() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - return findRecorderPos<RECORDER>(s).first; - } - - // Remove an entry from SettingsConfig::mRecorders whose RecorderPtr - // points to a Recorder subclass of type RECORDER. Return true if there - // was one and we removed it, false if there wasn't one to start with. - template <typename RECORDER> - bool removeRecorder() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - auto found = findRecorderPos<RECORDER>(s); - if (found.first) - { - s->mRecorders.erase(found.second); - } - return bool(found.first); - } -} - -namespace LLError -{ - void logToFile(const std::string& file_name) - { - // remove any previous Recorder filling this role - removeRecorder<RecordToFile>(); - - if (!file_name.empty()) - { - std::shared_ptr<RecordToFile> recordToFile(new RecordToFile(file_name)); - if (recordToFile->okay()) - { - addRecorder(recordToFile); - } - } - } - - std::string logFileName() - { - auto found = findRecorder<RecordToFile>(); - return found? found->getFilename() : std::string(); - } - - void logToStderr() - { - if (! findRecorder<RecordToStderr>()) - { - RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime())); - addRecorder(recordToStdErr); - } - } - - void logToFixedBuffer(LLLineBuffer* fixedBuffer) - { - // remove any previous Recorder filling this role - removeRecorder<RecordToFixedBuffer>(); - - if (fixedBuffer) - { - RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer)); - addRecorder(recordToFixedBuffer); - } - } -} - -namespace -{ - std::string escapedMessageLines(const std::string& message) - { - std::ostringstream out; - size_t written_out = 0; - size_t all_content = message.length(); - size_t escape_char_index; // always relative to start of message - // Use find_first_of to find the next character in message that needs escaping - for ( escape_char_index = message.find_first_of("\\\n\r"); - escape_char_index != std::string::npos && written_out < all_content; - // record what we've written this iteration, scan for next char that needs escaping - written_out = escape_char_index + 1, escape_char_index = message.find_first_of("\\\n\r", written_out) - ) - { - // found a character that needs escaping, so write up to that with the escape prefix - // note that escape_char_index is relative to the start, not to the written_out offset - out << message.substr(written_out, escape_char_index - written_out) << '\\'; - - // write out the appropriate second character in the escape sequence - char found = message[escape_char_index]; - switch ( found ) - { - case '\\': - out << '\\'; - break; - case '\n': - out << 'n'; - break; - case '\r': - out << 'r'; - break; - } - } - - if ( written_out < all_content ) // if the loop above didn't write everything - { - // write whatever was left - out << message.substr(written_out, std::string::npos); - } - return out.str(); - } - - void writeToRecorders(const LLError::CallSite& site, const std::string& message) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - LLError::ELevel level = site.mLevel; - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - - std::string escaped_message; - - LLMutexLock lock(&s->mRecorderMutex); - for (LLError::RecorderPtr& r : s->mRecorders) - { - if (!r->enabled()) - { - continue; - } - - std::ostringstream message_stream; - - if (r->wantsTime() && s->mTimeFunction != NULL) - { - message_stream << s->mTimeFunction(); - } - message_stream << " "; - - if (r->wantsLevel()) - { - message_stream << site.mLevelString; - } - message_stream << " "; - - if (r->wantsTags()) - { - message_stream << site.mTagString; - } - message_stream << " "; - - if (r->wantsLocation() || level == LLError::LEVEL_ERROR) - { - message_stream << site.mLocationString; - } - message_stream << " "; - - if (r->wantsFunctionName()) - { - message_stream << site.mFunctionString; - } - message_stream << " : "; - - if (r->wantsMultiline()) - { - message_stream << message; - } - else - { - if (escaped_message.empty()) - { - escaped_message = escapedMessageLines(message); - } - message_stream << escaped_message; - } - - r->recordMessage(level, message_stream.str()); - } - } -} - -namespace { - // We need a couple different mutexes, but we want to use the same mechanism - // for both. Make getMutex() a template function with different instances - // for different MutexDiscriminator values. - enum MutexDiscriminator - { - LOG_MUTEX, - STACKS_MUTEX - }; - // Some logging calls happen very early in processing -- so early that our - // module-static variables aren't yet initialized. getMutex() wraps a - // function-static LLMutex so that early calls can still have a valid - // LLMutex instance. - template <MutexDiscriminator MTX> - LLMutex* getMutex() - { - // guaranteed to be initialized the first time control reaches here - static LLMutex sMutex; - return &sMutex; - } - - bool checkLevelMap(const LevelMap& map, const std::string& key, - LLError::ELevel& level) - { - bool stop_checking; - LevelMap::const_iterator i = map.find(key); - if (i == map.end()) - { - return stop_checking = false; - } - - level = i->second; - return stop_checking = true; - } - - bool checkLevelMap( const LevelMap& map, - const char *const * keys, - size_t count, - LLError::ELevel& level) - { - bool found_level = false; - - LLError::ELevel tag_level = LLError::LEVEL_NONE; - - for (size_t i = 0; i < count; i++) - { - LevelMap::const_iterator it = map.find(keys[i]); - if (it != map.end()) - { - found_level = true; - tag_level = llmin(tag_level, it->second); - } - } - - if (found_level) - { - level = tag_level; - } - return found_level; - } -} - -namespace LLError -{ - - bool Log::shouldLog(CallSite& site) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5); - if (!lock.isLocked()) - { - return false; - } - - Globals *g = Globals::getInstance(); - SettingsConfigPtr s = g->getSettingsConfig(); - - s->mShouldLogCallCounter++; - - const std::string& class_name = className(site.mClassInfo); - std::string function_name = functionName(site.mFunction); -#if LL_LINUX - // gross, but typeid comparison seems to always fail here with gcc4.1 - if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name())) -#else - if (site.mClassInfo != typeid(NoClassInfo)) -#endif // LL_LINUX - { - function_name = class_name + "::" + function_name; - } - - ELevel compareLevel = s->mDefaultLevel; - - // The most specific match found will be used as the log level, - // since the computation short circuits. - // So, in increasing order of importance: - // Default < Tags < File < Class < Function - checkLevelMap(s->mFunctionLevelMap, function_name, compareLevel) - || checkLevelMap(s->mClassLevelMap, class_name, compareLevel) - || checkLevelMap(s->mFileLevelMap, abbreviateFile(site.mFile), compareLevel) - || (site.mTagCount > 0 - ? checkLevelMap(s->mTagLevelMap, site.mTags, site.mTagCount, compareLevel) - : false); - - site.mCached = true; - g->addCallSite(site); - return site.mShouldLog = site.mLevel >= compareLevel; - } - - - void Log::flush(const std::ostringstream& out, const CallSite& site) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING - LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5); - if (!lock.isLocked()) - { - return; - } - - Globals* g = Globals::getInstance(); - SettingsConfigPtr s = g->getSettingsConfig(); - - std::string message = out.str(); - - if (site.mPrintOnce) - { - std::ostringstream message_stream; - - std::map<std::string, unsigned int>::iterator messageIter = s->mUniqueLogMessages.find(message); - if (messageIter != s->mUniqueLogMessages.end()) - { - messageIter->second++; - unsigned int num_messages = messageIter->second; - if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0) - { - message_stream << "ONCE (" << num_messages << "th time seen): "; - } - else - { - return; - } - } - else - { - message_stream << "ONCE: "; - s->mUniqueLogMessages[message] = 1; - } - message_stream << message; - message = message_stream.str(); - } - - writeToRecorders(site, message); - - if (site.mLevel == LEVEL_ERROR) - { - g->mFatalMessage = message; - if (s->mCrashFunction) - { - s->mCrashFunction(message); - } - } - } -} - -namespace LLError -{ - SettingsStoragePtr saveAndResetSettings() - { - return Globals::getInstance()->saveAndResetSettingsConfig(); - } - - void restoreSettings(SettingsStoragePtr pSettingsStorage) - { - return Globals::getInstance()->restore(pSettingsStorage); - } - - std::string removePrefix(std::string& s, const std::string& p) - { - std::string::size_type where = s.find(p); - if (where == std::string::npos) - { - return s; - } - - return std::string(s, where + p.size()); - } - - void replaceChar(std::string& s, char old, char replacement) - { - std::string::size_type i = 0; - std::string::size_type len = s.length(); - for ( ; i < len; i++ ) - { - if (s[i] == old) - { - s[i] = replacement; - } - } - } - - std::string abbreviateFile(const std::string& filePath) - { - std::string f = filePath; -#if LL_WINDOWS - replaceChar(f, '\\', '/'); -#endif - static std::string indra_prefix = "indra/"; - f = removePrefix(f, indra_prefix); - -#if LL_DARWIN - static std::string newview_prefix = "newview/../"; - f = removePrefix(f, newview_prefix); -#endif - - return f; - } - - int shouldLogCallCount() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - return s->mShouldLogCallCounter; - } - - std::string utcTime() - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - auto chars = strftime(time_str, BUF_SIZE, - "%Y-%m-%dT%H:%M:%SZ", - gmtime(&now)); - - return chars ? time_str : "time error"; - } -} - -namespace LLError -{ - LLCallStacks::StringVector LLCallStacks::sBuffer ; - - //static - void LLCallStacks::push(const char* function, const int line) - { - LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5); - if (!lock.isLocked()) - { - return; - } - - if(sBuffer.size() > 511) - { - clear() ; - } - - std::ostringstream out; - insert(out, function, line); - sBuffer.push_back(out.str()); - } - - //static - void LLCallStacks::insert(std::ostream& out, const char* function, const int line) - { - out << function << " line " << line << " " ; - } - - //static - void LLCallStacks::end(const std::ostringstream& out) - { - LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5); - if (!lock.isLocked()) - { - return; - } - - if(sBuffer.size() > 511) - { - clear() ; - } - - sBuffer.push_back(out.str()); - } - - //static - void LLCallStacks::print() - { - LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5); - if (!lock.isLocked()) - { - return; - } - - if(! sBuffer.empty()) - { - LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL; - for (StringVector::const_reverse_iterator ri(sBuffer.rbegin()), re(sBuffer.rend()); - ri != re; ++ri) - { - LL_INFOS() << (*ri) << LL_ENDL; - } - LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL; - } - - cleanup(); - } - - //static - void LLCallStacks::clear() - { - sBuffer.clear(); - } - - //static - void LLCallStacks::cleanup() - { - clear(); - } - - std::ostream& operator<<(std::ostream& out, const LLStacktrace&) - { - return out << boost::stacktrace::stacktrace(); - } - - // LLOutOfMemoryWarning - std::string LLUserWarningMsg::sLocalizedOutOfMemoryTitle; - std::string LLUserWarningMsg::sLocalizedOutOfMemoryWarning; - LLUserWarningMsg::Handler LLUserWarningMsg::sHandler; - - void LLUserWarningMsg::show(const std::string& message) - { - if (sHandler) - { - sHandler(std::string(), message); - } - } - - void LLUserWarningMsg::showOutOfMemory() - { - if (sHandler && !sLocalizedOutOfMemoryTitle.empty()) - { - sHandler(sLocalizedOutOfMemoryTitle, sLocalizedOutOfMemoryWarning); - } - } - - void LLUserWarningMsg::showMissingFiles() - { - // Files Are missing, likely can't localize. - const std::string error_string = - "Second Life viewer couldn't access some of the files it needs and will be closed." - "\n\nPlease reinstall viewer from https://secondlife.com/support/downloads/ and " - "contact https://support.secondlife.com if issue persists after reinstall."; - sHandler("Missing Files", error_string); - } - - void LLUserWarningMsg::setHandler(const LLUserWarningMsg::Handler &handler) - { - sHandler = handler; - } - - void LLUserWarningMsg::setOutOfMemoryStrings(const std::string& title, const std::string& message) - { - sLocalizedOutOfMemoryTitle = title; - sLocalizedOutOfMemoryWarning = message; - } -} - -void crashdriver(void (*callback)(int*)) -{ - // The LLERROR_CRASH macro used to have inline code of the form: - //int* make_me_crash = NULL; - //*make_me_crash = 0; - - // But compilers are getting smart enough to recognize that, so we must - // assign to an address supplied by a separate source file. We could do - // the assignment here in crashdriver() -- but then BugSplat would group - // all LL_ERRS() crashes as the fault of this one function, instead of - // identifying the specific LL_ERRS() source line. So instead, do the - // assignment in a lambda in the caller's source. We just provide the - // nullptr target. - callback(nullptr); -} +/**
+ * @file llerror.cpp
+ * @date December 2006
+ * @brief error message system
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llerror.h"
+#include "llerrorcontrol.h"
+#include "llsdutil.h"
+
+#include <cctype>
+#ifdef __GNUC__
+# include <cxxabi.h>
+#endif // __GNUC__
+#include <sstream>
+#if !LL_WINDOWS
+# include <syslog.h>
+# include <unistd.h>
+# include <sys/stat.h>
+#else
+# include <io.h>
+#endif // !LL_WINDOWS
+#include <vector>
+#include "string.h"
+
+#include "llapp.h"
+#include "llapr.h"
+#include "llfile.h"
+#include "lllivefile.h"
+#include "llsd.h"
+#include "llsdserialize.h"
+#include "llsingleton.h"
+#include "llstl.h"
+#include "lltimer.h"
+
+// On Mac, got:
+// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define
+// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if
+// _Unwind_Backtrace is available without `_GNU_SOURCE`."
+#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
+#include <boost/stacktrace.hpp>
+
+namespace {
+#if LL_WINDOWS
+ void debugger_print(const std::string& s)
+ {
+ // Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C
+ // which works just fine under the windows debugger, but can cause users who
+ // have enabled SEHOP exception chain validation to crash due to interactions
+ // between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707
+ //
+ if (IsDebuggerPresent())
+ {
+ // Need UTF16 for Unicode OutputDebugString
+ //
+ if (s.size())
+ {
+ OutputDebugString(utf8str_to_utf16str(s).c_str());
+ OutputDebugString(TEXT("\n"));
+ }
+ }
+ }
+#else
+ class RecordToSyslog : public LLError::Recorder
+ {
+ public:
+ RecordToSyslog(const std::string& identity)
+ : mIdentity(identity)
+ {
+ openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0);
+ // we need to set the string from a local copy of the string
+ // since apparanetly openlog expects the const char* to remain
+ // valid even after it returns (presumably until closelog)
+ }
+
+ ~RecordToSyslog()
+ {
+ closelog();
+ }
+
+ virtual bool enabled() override
+ {
+ return LLError::getEnabledLogTypesMask() & 0x01;
+ }
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
+ int syslogPriority = LOG_CRIT;
+ switch (level) {
+ case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break;
+ case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break;
+ case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break;
+ case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break;
+ default: syslogPriority = LOG_CRIT;
+ }
+
+ syslog(syslogPriority, "%s", message.c_str());
+ }
+ private:
+ std::string mIdentity;
+ };
+#endif
+
+ class RecordToFile : public LLError::Recorder
+ {
+ public:
+ RecordToFile(const std::string& filename):
+ mName(filename)
+ {
+ mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app);
+ if (!mFile)
+ {
+ LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
+ }
+ else
+ {
+ if (!LLError::getAlwaysFlush())
+ {
+ mFile.sync_with_stdio(false);
+ }
+ }
+ }
+
+ ~RecordToFile()
+ {
+ mFile.close();
+ }
+
+ virtual bool enabled() override
+ {
+#ifdef LL_RELEASE_FOR_DOWNLOAD
+ return 1;
+#else
+ return LLError::getEnabledLogTypesMask() & 0x02;
+#endif
+ }
+
+ bool okay() const { return mFile.good(); }
+
+ std::string getFilename() const { return mName; }
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
+ if (LLError::getAlwaysFlush())
+ {
+ mFile << message << std::endl;
+ }
+ else
+ {
+ mFile << message << "\n";
+ }
+ }
+
+ private:
+ const std::string mName;
+ llofstream mFile;
+ };
+
+
+ class RecordToStderr : public LLError::Recorder
+ {
+ public:
+ RecordToStderr(bool timestamp) : mUseANSI(checkANSI())
+ {
+ this->showMultiline(true);
+ }
+
+ virtual bool enabled() override
+ {
+ return LLError::getEnabledLogTypesMask() & 0x04;
+ }
+
+ LL_FORCE_INLINE std::string createBoldANSI()
+ {
+ std::string ansi_code;
+ ansi_code += '\033';
+ ansi_code += "[";
+ ansi_code += "1";
+ ansi_code += "m";
+
+ return ansi_code;
+ }
+
+ LL_FORCE_INLINE std::string createResetANSI()
+ {
+ std::string ansi_code;
+ ansi_code += '\033';
+ ansi_code += "[";
+ ansi_code += "0";
+ ansi_code += "m";
+
+ return ansi_code;
+ }
+
+ LL_FORCE_INLINE std::string createANSI(const std::string& color)
+ {
+ std::string ansi_code;
+ ansi_code += '\033';
+ ansi_code += "[";
+ ansi_code += "38;5;";
+ ansi_code += color;
+ ansi_code += "m";
+
+ return ansi_code;
+ }
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
+ // The default colors for error, warn and debug are now a bit more pastel
+ // and easier to read on the default (black) terminal background but you
+ // now have the option to set the color of each via an environment variables:
+ // LL_ANSI_ERROR_COLOR_CODE (default is red)
+ // LL_ANSI_WARN_COLOR_CODE (default is blue)
+ // LL_ANSI_DEBUG_COLOR_CODE (default is magenta)
+ // The list of color codes can be found in many places but I used this page:
+ // https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
+ // (Note: you may need to restart Visual Studio to pick environment changes)
+ char* val = nullptr;
+ std::string s_ansi_error_code = "160";
+ if ((val = getenv("LL_ANSI_ERROR_COLOR_CODE")) != nullptr) s_ansi_error_code = std::string(val);
+ std::string s_ansi_warn_code = "33";
+ if ((val = getenv("LL_ANSI_WARN_COLOR_CODE")) != nullptr) s_ansi_warn_code = std::string(val);
+ std::string s_ansi_debug_code = "177";
+ if ((val = getenv("LL_ANSI_DEBUG_COLOR_CODE")) != nullptr) s_ansi_debug_code = std::string(val);
+
+ static std::string s_ansi_error = createANSI(s_ansi_error_code); // default is red
+ static std::string s_ansi_warn = createANSI(s_ansi_warn_code); // default is blue
+ static std::string s_ansi_debug = createANSI(s_ansi_debug_code); // default is magenta
+
+ if (mUseANSI)
+ {
+ writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error :
+ (level == LLError::LEVEL_WARN) ? s_ansi_warn :
+ s_ansi_debug, message);
+ }
+ else
+ {
+ LL_PROFILE_ZONE_NAMED("fprintf");
+ fprintf(stderr, "%s\n", message.c_str());
+ }
+ }
+
+ private:
+ bool mUseANSI;
+
+ LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message)
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
+ static std::string s_ansi_bold = createBoldANSI(); // bold text
+ static std::string s_ansi_reset = createResetANSI(); // reset
+ // ANSI color code escape sequence, message, and reset in one fprintf call
+ // Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries.
+ fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() );
+ }
+
+ static bool checkANSI(void)
+ {
+ // Check whether it's okay to use ANSI; if stderr is
+ // a tty then we assume yes. Can be turned off with
+ // the LL_NO_ANSI_COLOR env var.
+ return (0 != isatty(2)) &&
+ (NULL == getenv("LL_NO_ANSI_COLOR"));
+ }
+ };
+
+ class RecordToFixedBuffer : public LLError::Recorder
+ {
+ public:
+ RecordToFixedBuffer(LLLineBuffer* buffer)
+ : mBuffer(buffer)
+ {
+ this->showMultiline(true);
+ this->showTags(false);
+ this->showLocation(false);
+ }
+
+ virtual bool enabled() override
+ {
+ return LLError::getEnabledLogTypesMask() & 0x08;
+ }
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
+ mBuffer->addLine(message);
+ }
+
+ private:
+ LLLineBuffer* mBuffer;
+ };
+
+#if LL_WINDOWS
+ class RecordToWinDebug: public LLError::Recorder
+ {
+ public:
+ RecordToWinDebug()
+ {
+ this->showMultiline(true);
+ this->showTags(false);
+ this->showLocation(false);
+ }
+
+ virtual bool enabled() override
+ {
+ return LLError::getEnabledLogTypesMask() & 0x10;
+ }
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
+ debugger_print(message);
+ }
+ };
+#endif
+}
+
+
+namespace
+{
+ std::string className(const std::type_info& type)
+ {
+ return LLError::Log::demangle(type.name());
+ }
+} // anonymous
+
+namespace LLError
+{
+ std::string Log::demangle(const char* mangled)
+ {
+#ifdef __GNUC__
+ // GCC: type_info::name() returns a mangled class name,st demangle
+ // passing nullptr, 0 forces allocation of a unique buffer we can free
+ // fixing MAINT-8724 on OSX 10.14
+ int status = -1;
+ char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status);
+ std::string result(name ? name : mangled);
+ free(name);
+ return result;
+
+#elif LL_WINDOWS
+ // Visual Studio: type_info::name() includes the text "class " at the start
+ std::string name = mangled;
+ for (const auto& prefix : std::vector<std::string>{ "class ", "struct " })
+ {
+ if (0 == name.compare(0, prefix.length(), prefix))
+ {
+ return name.substr(prefix.length());
+ }
+ }
+ // huh, that's odd, we should see one or the other prefix -- but don't
+ // try to log unless logging is already initialized
+ // in Python, " or ".join(vector) -- but in C++, a PITB
+ LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '"
+ << name << "'" << LL_ENDL;
+ return name;
+
+#else // neither GCC nor Visual Studio
+ return mangled;
+#endif
+ }
+} // LLError
+
+namespace
+{
+ std::string functionName(const std::string& preprocessor_name)
+ {
+#if LL_WINDOWS
+ // DevStudio: the __FUNCTION__ macro string includes
+ // the type and/or namespace prefixes
+
+ std::string::size_type p = preprocessor_name.rfind(':');
+ if (p == std::string::npos)
+ {
+ return preprocessor_name;
+ }
+ return preprocessor_name.substr(p + 1);
+
+#else
+ return preprocessor_name;
+#endif
+ }
+
+
+ class LogControlFile : public LLLiveFile
+ {
+ LOG_CLASS(LogControlFile);
+
+ public:
+ static LogControlFile& fromDirectory(const std::string& user_dir, const std::string& app_dir);
+
+ virtual bool loadFile();
+
+ private:
+ LogControlFile(const std::string &filename)
+ : LLLiveFile(filename)
+ { }
+ };
+
+ LogControlFile& LogControlFile::fromDirectory(const std::string& user_dir, const std::string& app_dir)
+ {
+ // NB: We have no abstraction in llcommon for the "proper"
+ // delimiter but it turns out that "/" works on all three platforms
+
+ 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";
+ }
+ return * new LogControlFile(file);
+ // NB: This instance is never freed
+ }
+
+ bool LogControlFile::loadFile()
+ {
+ LLSD configuration;
+
+ {
+ llifstream file(filename().c_str());
+ if (!file.is_open())
+ {
+ LL_WARNS() << filename() << " failed to open file; not changing configuration" << LL_ENDL;
+ return false;
+ }
+
+ if (LLSDSerialize::fromXML(configuration, file) == LLSDParser::PARSE_FAILURE)
+ {
+ LL_WARNS() << filename() << " parcing error; not changing configuration" << LL_ENDL;
+ return false;
+ }
+
+ if (! configuration || !configuration.isMap())
+ {
+ LL_WARNS() << filename() << " missing, ill-formed, or simply undefined"
+ " content; not changing configuration"
+ << LL_ENDL;
+ return false;
+ }
+ }
+
+ LLError::configure(configuration);
+ LL_INFOS("LogControlFile") << "logging reconfigured from " << filename() << LL_ENDL;
+ return true;
+ }
+
+
+ typedef std::map<std::string, LLError::ELevel> LevelMap;
+ typedef std::vector<LLError::RecorderPtr> Recorders;
+ typedef std::vector<LLError::CallSite*> CallSiteVector;
+
+ class SettingsConfig : public LLRefCount
+ {
+ friend class Globals;
+
+ public:
+ virtual ~SettingsConfig();
+
+ LLError::ELevel mDefaultLevel;
+
+ bool mLogAlwaysFlush;
+
+ U32 mEnabledLogTypesMask;
+
+ LevelMap mFunctionLevelMap;
+ LevelMap mClassLevelMap;
+ LevelMap mFileLevelMap;
+ LevelMap mTagLevelMap;
+ std::map<std::string, unsigned int> mUniqueLogMessages;
+
+ LLError::FatalFunction mCrashFunction;
+ LLError::TimeFunction mTimeFunction;
+
+ Recorders mRecorders;
+ LLMutex mRecorderMutex;
+
+ int mShouldLogCallCounter;
+
+ private:
+ SettingsConfig();
+ };
+
+ typedef LLPointer<SettingsConfig> SettingsConfigPtr;
+
+ SettingsConfig::SettingsConfig()
+ : LLRefCount(),
+ mDefaultLevel(LLError::LEVEL_DEBUG),
+ mLogAlwaysFlush(true),
+ mEnabledLogTypesMask(255),
+ mFunctionLevelMap(),
+ mClassLevelMap(),
+ mFileLevelMap(),
+ mTagLevelMap(),
+ mUniqueLogMessages(),
+ mCrashFunction(NULL),
+ mTimeFunction(NULL),
+ mRecorders(),
+ mRecorderMutex(),
+ mShouldLogCallCounter(0)
+ {
+ }
+
+ SettingsConfig::~SettingsConfig()
+ {
+ mRecorders.clear();
+ }
+
+ class Globals
+ {
+ public:
+ static Globals* getInstance();
+ protected:
+ Globals();
+ public:
+ std::string mFatalMessage;
+
+ void addCallSite(LLError::CallSite&);
+ void invalidateCallSites();
+
+ SettingsConfigPtr getSettingsConfig();
+
+ void resetSettingsConfig();
+ LLError::SettingsStoragePtr saveAndResetSettingsConfig();
+ void restore(LLError::SettingsStoragePtr pSettingsStorage);
+ private:
+ CallSiteVector callSites;
+ SettingsConfigPtr mSettingsConfig;
+ };
+
+ Globals::Globals()
+ :
+ callSites(),
+ mSettingsConfig(new SettingsConfig())
+ {
+ }
+
+
+ Globals* Globals::getInstance()
+ {
+ // According to C++11 Function-Local Initialization
+ // of static variables is supposed to be thread safe
+ // without risk of deadlocks.
+ static Globals inst;
+
+ return &inst;
+ }
+
+ void Globals::addCallSite(LLError::CallSite& site)
+ {
+ callSites.push_back(&site);
+ }
+
+ void Globals::invalidateCallSites()
+ {
+ for (LLError::CallSite* site : callSites)
+ {
+ site->invalidate();
+ }
+
+ callSites.clear();
+ }
+
+ SettingsConfigPtr Globals::getSettingsConfig()
+ {
+ return mSettingsConfig;
+ }
+
+ void Globals::resetSettingsConfig()
+ {
+ invalidateCallSites();
+ mSettingsConfig = new SettingsConfig();
+ }
+
+ LLError::SettingsStoragePtr Globals::saveAndResetSettingsConfig()
+ {
+ LLError::SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get());
+ resetSettingsConfig();
+ return oldSettingsConfig;
+ }
+
+ void Globals::restore(LLError::SettingsStoragePtr pSettingsStorage)
+ {
+ invalidateCallSites();
+ SettingsConfigPtr newSettingsConfig(dynamic_cast<SettingsConfig *>(pSettingsStorage.get()));
+ mSettingsConfig = newSettingsConfig;
+ }
+}
+
+namespace LLError
+{
+ CallSite::CallSite(ELevel level,
+ const char* file,
+ int line,
+ const std::type_info& class_info,
+ const char* function,
+ bool printOnce,
+ const char** tags,
+ size_t tag_count)
+ : mLevel(level),
+ mFile(file),
+ mLine(line),
+ mClassInfo(class_info),
+ mFunction(function),
+ mCached(false),
+ mShouldLog(false),
+ mPrintOnce(printOnce),
+ mTags(new const char* [tag_count]),
+ mTagCount(tag_count)
+ {
+ switch (mLevel)
+ {
+ case LEVEL_DEBUG: mLevelString = "DEBUG"; break;
+ case LEVEL_INFO: mLevelString = "INFO"; break;
+ case LEVEL_WARN: mLevelString = "WARNING"; break;
+ case LEVEL_ERROR: mLevelString = "ERROR"; break;
+ default: mLevelString = "XXX"; break;
+ };
+
+ mLocationString = llformat("%s(%d)", abbreviateFile(mFile).c_str(), mLine);
+#if LL_WINDOWS
+ // DevStudio: __FUNCTION__ already includes the full class name
+#else
+#if LL_LINUX
+ // gross, but typeid comparison seems to always fail here with gcc4.1
+ if (0 != strcmp(mClassInfo.name(), typeid(NoClassInfo).name()))
+#else
+ if (mClassInfo != typeid(NoClassInfo))
+#endif // LL_LINUX
+ {
+ mFunctionString = className(mClassInfo) + "::";
+ }
+#endif
+ mFunctionString += std::string(mFunction);
+
+ for (int i = 0; i < tag_count; i++)
+ {
+ if (strchr(tags[i], ' '))
+ {
+ LL_ERRS() << "Space is not allowed in a log tag at " << mLocationString << LL_ENDL;
+ }
+ mTags[i] = tags[i];
+ }
+
+ mTagString.append("#");
+ // always construct a tag sequence; will be just a single # if no tag
+ for (size_t i = 0; i < mTagCount; i++)
+ {
+ mTagString.append(mTags[i]);
+ mTagString.append("#");
+ }
+ }
+
+ CallSite::~CallSite()
+ {
+ delete []mTags;
+ }
+
+ void CallSite::invalidate()
+ {
+ mCached = false;
+ }
+}
+
+namespace
+{
+ bool shouldLogToStderr()
+ {
+#if LL_DARWIN
+ // On macOS, stderr from apps launched from the Finder goes to the
+ // console log. It's generally considered bad form to spam too much
+ // there. That scenario can be detected by noticing that stderr is a
+ // character device (S_IFCHR).
+
+ // If stderr is a tty or a pipe, assume the user launched from the
+ // command line or debugger and therefore wants to see stderr.
+ if (isatty(STDERR_FILENO))
+ return true;
+ // not a tty, but might still be a pipe -- check
+ struct stat st;
+ if (fstat(STDERR_FILENO, &st) < 0)
+ {
+ // capture errno right away, before engaging any other operations
+ auto errno_save = errno;
+ // this gets called during log-system setup -- can't log yet!
+ std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno "
+ << errno_save << std::endl;
+ // if we can't tell, err on the safe side and don't write stderr
+ return false;
+ }
+
+ // fstat() worked: return true only if stderr is a pipe
+ return ((st.st_mode & S_IFMT) == S_IFIFO);
+#else
+ return true;
+#endif
+ }
+
+ bool stderrLogWantsTime()
+ {
+#if LL_WINDOWS
+ return false;
+#else
+ return true;
+#endif
+ }
+
+
+ void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true)
+ {
+ Globals::getInstance()->resetSettingsConfig();
+
+ LLError::setDefaultLevel(LLError::LEVEL_INFO);
+ LLError::setAlwaysFlush(true);
+ LLError::setEnabledLogTypesMask(0xFFFFFFFF);
+ LLError::setTimeFunction(LLError::utcTime);
+
+ // log_to_stderr is only false in the unit and integration tests to keep builds quieter
+ if (log_to_stderr && shouldLogToStderr())
+ {
+ LLError::logToStderr();
+ }
+
+#if LL_WINDOWS
+ LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug());
+ LLError::addRecorder(recordToWinDebug);
+#endif
+
+ LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir);
+
+ // NOTE: We want to explicitly load the file before we add it to the event timer
+ // that checks for changes to the file. Else, we're not actually loading the file yet,
+ // and most of the initialization happens without any attention being paid to the
+ // log control file. Not to mention that when it finally gets checked later,
+ // all log statements that have been evaluated already become dirty and need to be
+ // evaluated for printing again. So, make sure to call checkAndReload()
+ // before addToEventTimer().
+ e.checkAndReload();
+ e.addToEventTimer();
+ }
+}
+
+namespace LLError
+{
+ void initForApplication(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr)
+ {
+ commonInit(user_dir, app_dir, log_to_stderr);
+ }
+
+ void setFatalFunction(const FatalFunction& f)
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ s->mCrashFunction = f;
+ }
+
+ FatalFunction getFatalFunction()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mCrashFunction;
+ }
+
+ std::string getFatalMessage()
+ {
+ return Globals::getInstance()->mFatalMessage;
+ }
+
+ void setTimeFunction(TimeFunction f)
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ s->mTimeFunction = f;
+ }
+
+ void setDefaultLevel(ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mDefaultLevel = level;
+ }
+
+ ELevel getDefaultLevel()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mDefaultLevel;
+ }
+
+ void setAlwaysFlush(bool flush)
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ s->mLogAlwaysFlush = flush;
+ }
+
+ bool getAlwaysFlush()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mLogAlwaysFlush;
+ }
+
+ void setEnabledLogTypesMask(U32 mask)
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ s->mEnabledLogTypesMask = mask;
+ }
+
+ U32 getEnabledLogTypesMask()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mEnabledLogTypesMask;
+ }
+
+ void setFunctionLevel(const std::string& function_name, ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mFunctionLevelMap[function_name] = level;
+ }
+
+ void setClassLevel(const std::string& class_name, ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mClassLevelMap[class_name] = level;
+ }
+
+ void setFileLevel(const std::string& file_name, ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mFileLevelMap[file_name] = level;
+ }
+
+ void setTagLevel(const std::string& tag_name, ELevel level)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+ s->mTagLevelMap[tag_name] = level;
+ }
+
+ LLError::ELevel decodeLevel(std::string name)
+ {
+ static LevelMap level_names;
+ if (level_names.empty())
+ {
+ level_names["ALL"] = LLError::LEVEL_ALL;
+ level_names["DEBUG"] = LLError::LEVEL_DEBUG;
+ level_names["INFO"] = LLError::LEVEL_INFO;
+ level_names["WARN"] = LLError::LEVEL_WARN;
+ level_names["ERROR"] = LLError::LEVEL_ERROR;
+ level_names["NONE"] = LLError::LEVEL_NONE;
+ }
+
+ std::transform(name.begin(), name.end(), name.begin(), toupper);
+
+ LevelMap::const_iterator i = level_names.find(name);
+ if (i == level_names.end())
+ {
+ LL_WARNS() << "unrecognized logging level: '" << name << "'" << LL_ENDL;
+ return LLError::LEVEL_INFO;
+ }
+
+ return i->second;
+ }
+}
+
+namespace {
+ void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level)
+ {
+ LLSD::array_const_iterator i, end;
+ for (i = list.beginArray(), end = list.endArray(); i != end; ++i)
+ {
+ map[*i] = level;
+ }
+ }
+}
+
+namespace LLError
+{
+ void configure(const LLSD& config)
+ {
+ Globals *g = Globals::getInstance();
+ g->invalidateCallSites();
+ SettingsConfigPtr s = g->getSettingsConfig();
+
+ s->mFunctionLevelMap.clear();
+ s->mClassLevelMap.clear();
+ s->mFileLevelMap.clear();
+ s->mTagLevelMap.clear();
+ s->mUniqueLogMessages.clear();
+
+ setDefaultLevel(decodeLevel(config["default-level"]));
+ if (config.has("log-always-flush"))
+ {
+ setAlwaysFlush(config["log-always-flush"]);
+ }
+ if (config.has("enabled-log-types-mask"))
+ {
+ setEnabledLogTypesMask(config["enabled-log-types-mask"].asInteger());
+ }
+
+ if (config.has("settings") && config["settings"].isArray())
+ {
+ LLSD sets = config["settings"];
+ LLSD::array_const_iterator a, end;
+ for (a = sets.beginArray(), end = sets.endArray(); a != end; ++a)
+ {
+ const LLSD& entry = *a;
+ if (entry.isMap() && entry.size() != 0)
+ {
+ ELevel level = decodeLevel(entry["level"]);
+
+ setLevels(s->mFunctionLevelMap, entry["functions"], level);
+ setLevels(s->mClassLevelMap, entry["classes"], level);
+ setLevels(s->mFileLevelMap, entry["files"], level);
+ setLevels(s->mTagLevelMap, entry["tags"], level);
+ }
+ }
+ }
+ }
+}
+
+
+namespace LLError
+{
+ Recorder::Recorder()
+ : mWantsTime(true)
+ , mWantsTags(true)
+ , mWantsLevel(true)
+ , mWantsLocation(true)
+ , mWantsFunctionName(true)
+ , mWantsMultiline(false)
+ {
+ }
+
+ Recorder::~Recorder()
+ {
+ }
+
+ bool Recorder::wantsTime()
+ {
+ return mWantsTime;
+ }
+
+ // virtual
+ bool Recorder::wantsTags()
+ {
+ return mWantsTags;
+ }
+
+ // virtual
+ bool Recorder::wantsLevel()
+ {
+ return mWantsLevel;
+ }
+
+ // virtual
+ bool Recorder::wantsLocation()
+ {
+ return mWantsLocation;
+ }
+
+ // virtual
+ bool Recorder::wantsFunctionName()
+ {
+ return mWantsFunctionName;
+ }
+
+ // virtual
+ bool Recorder::wantsMultiline()
+ {
+ return mWantsMultiline;
+ }
+
+ void Recorder::showTime(bool show)
+ {
+ mWantsTime = show;
+ }
+
+ void Recorder::showTags(bool show)
+ {
+ mWantsTags = show;
+ }
+
+ void Recorder::showLevel(bool show)
+ {
+ mWantsLevel = show;
+ }
+
+ void Recorder::showLocation(bool show)
+ {
+ mWantsLocation = show;
+ }
+
+ void Recorder::showFunctionName(bool show)
+ {
+ mWantsFunctionName = show;
+ }
+
+ void Recorder::showMultiline(bool show)
+ {
+ mWantsMultiline = show;
+ }
+
+ void addRecorder(RecorderPtr recorder)
+ {
+ if (!recorder)
+ {
+ return;
+ }
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ LLMutexLock lock(&s->mRecorderMutex);
+ s->mRecorders.push_back(recorder);
+ }
+
+ void removeRecorder(RecorderPtr recorder)
+ {
+ if (!recorder)
+ {
+ return;
+ }
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ LLMutexLock lock(&s->mRecorderMutex);
+ s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
+ s->mRecorders.end());
+ }
+
+ // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
+ // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
+ // points to the Recorder base class), but a shared_ptr<RECORDER> which
+ // specifically points to the concrete RECORDER subclass instance, along
+ // with a Recorders::iterator indicating the position of that entry in
+ // mRecorders. The shared_ptr might be empty (operator!() returns true) if
+ // there was no such RECORDER subclass instance in mRecorders.
+ //
+ // NOTE!!! Requires external mutex lock!!!
+ template <typename RECORDER>
+ std::pair<std::shared_ptr<RECORDER>, Recorders::iterator>
+ findRecorderPos(SettingsConfigPtr &s)
+ {
+ // Since we promise to return an iterator, use a classic iterator
+ // loop.
+ auto end{s->mRecorders.end()};
+ for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it)
+ {
+ // *it is a RecorderPtr, a shared_ptr<Recorder>. Use a
+ // dynamic_pointer_cast to try to downcast to test if it's also a
+ // shared_ptr<RECORDER>.
+ auto ptr = std::dynamic_pointer_cast<RECORDER>(*it);
+ if (ptr)
+ {
+ // found the entry we want
+ return { ptr, it };
+ }
+ }
+ // dropped out of the loop without finding any such entry -- instead
+ // of default-constructing Recorders::iterator (which might or might
+ // not be valid), return a value that is valid but not dereferenceable.
+ return { {}, end };
+ }
+
+ // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
+ // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
+ // points to the Recorder base class), but a shared_ptr<RECORDER> which
+ // specifically points to the concrete RECORDER subclass instance. The
+ // shared_ptr might be empty (operator!() returns true) if there was no
+ // such RECORDER subclass instance in mRecorders.
+ template <typename RECORDER>
+ std::shared_ptr<RECORDER> findRecorder()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ LLMutexLock lock(&s->mRecorderMutex);
+ return findRecorderPos<RECORDER>(s).first;
+ }
+
+ // Remove an entry from SettingsConfig::mRecorders whose RecorderPtr
+ // points to a Recorder subclass of type RECORDER. Return true if there
+ // was one and we removed it, false if there wasn't one to start with.
+ template <typename RECORDER>
+ bool removeRecorder()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ LLMutexLock lock(&s->mRecorderMutex);
+ auto found = findRecorderPos<RECORDER>(s);
+ if (found.first)
+ {
+ s->mRecorders.erase(found.second);
+ }
+ return bool(found.first);
+ }
+}
+
+namespace LLError
+{
+ void logToFile(const std::string& file_name)
+ {
+ // remove any previous Recorder filling this role
+ removeRecorder<RecordToFile>();
+
+ if (!file_name.empty())
+ {
+ std::shared_ptr<RecordToFile> recordToFile(new RecordToFile(file_name));
+ if (recordToFile->okay())
+ {
+ addRecorder(recordToFile);
+ }
+ }
+ }
+
+ std::string logFileName()
+ {
+ auto found = findRecorder<RecordToFile>();
+ return found? found->getFilename() : std::string();
+ }
+
+ void logToStderr()
+ {
+ if (! findRecorder<RecordToStderr>())
+ {
+ RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
+ addRecorder(recordToStdErr);
+ }
+ }
+
+ void logToFixedBuffer(LLLineBuffer* fixedBuffer)
+ {
+ // remove any previous Recorder filling this role
+ removeRecorder<RecordToFixedBuffer>();
+
+ if (fixedBuffer)
+ {
+ RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
+ addRecorder(recordToFixedBuffer);
+ }
+ }
+}
+
+namespace
+{
+ std::string escapedMessageLines(const std::string& message)
+ {
+ std::ostringstream out;
+ size_t written_out = 0;
+ size_t all_content = message.length();
+ size_t escape_char_index; // always relative to start of message
+ // Use find_first_of to find the next character in message that needs escaping
+ for ( escape_char_index = message.find_first_of("\\\n\r");
+ escape_char_index != std::string::npos && written_out < all_content;
+ // record what we've written this iteration, scan for next char that needs escaping
+ written_out = escape_char_index + 1, escape_char_index = message.find_first_of("\\\n\r", written_out)
+ )
+ {
+ // found a character that needs escaping, so write up to that with the escape prefix
+ // note that escape_char_index is relative to the start, not to the written_out offset
+ out << message.substr(written_out, escape_char_index - written_out) << '\\';
+
+ // write out the appropriate second character in the escape sequence
+ char found = message[escape_char_index];
+ switch ( found )
+ {
+ case '\\':
+ out << '\\';
+ break;
+ case '\n':
+ out << 'n';
+ break;
+ case '\r':
+ out << 'r';
+ break;
+ }
+ }
+
+ if ( written_out < all_content ) // if the loop above didn't write everything
+ {
+ // write whatever was left
+ out << message.substr(written_out, std::string::npos);
+ }
+ return out.str();
+ }
+
+ void writeToRecorders(const LLError::CallSite& site, const std::string& message)
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
+ LLError::ELevel level = site.mLevel;
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+
+ std::string escaped_message;
+
+ LLMutexLock lock(&s->mRecorderMutex);
+ for (LLError::RecorderPtr& r : s->mRecorders)
+ {
+ if (!r->enabled())
+ {
+ continue;
+ }
+
+ std::ostringstream message_stream;
+
+ if (r->wantsTime() && s->mTimeFunction != NULL)
+ {
+ message_stream << s->mTimeFunction();
+ }
+ message_stream << " ";
+
+ if (r->wantsLevel())
+ {
+ message_stream << site.mLevelString;
+ }
+ message_stream << " ";
+
+ if (r->wantsTags())
+ {
+ message_stream << site.mTagString;
+ }
+ message_stream << " ";
+
+ if (r->wantsLocation() || level == LLError::LEVEL_ERROR)
+ {
+ message_stream << site.mLocationString;
+ }
+ message_stream << " ";
+
+ if (r->wantsFunctionName())
+ {
+ message_stream << site.mFunctionString;
+ }
+ message_stream << " : ";
+
+ if (r->wantsMultiline())
+ {
+ message_stream << message;
+ }
+ else
+ {
+ if (escaped_message.empty())
+ {
+ escaped_message = escapedMessageLines(message);
+ }
+ message_stream << escaped_message;
+ }
+
+ r->recordMessage(level, message_stream.str());
+ }
+ }
+}
+
+namespace {
+ // We need a couple different mutexes, but we want to use the same mechanism
+ // for both. Make getMutex() a template function with different instances
+ // for different MutexDiscriminator values.
+ enum MutexDiscriminator
+ {
+ LOG_MUTEX,
+ STACKS_MUTEX
+ };
+ // Some logging calls happen very early in processing -- so early that our
+ // module-static variables aren't yet initialized. getMutex() wraps a
+ // function-static LLMutex so that early calls can still have a valid
+ // LLMutex instance.
+ template <MutexDiscriminator MTX>
+ LLMutex* getMutex()
+ {
+ // guaranteed to be initialized the first time control reaches here
+ static LLMutex sMutex;
+ return &sMutex;
+ }
+
+ bool checkLevelMap(const LevelMap& map, const std::string& key,
+ LLError::ELevel& level)
+ {
+ bool stop_checking;
+ LevelMap::const_iterator i = map.find(key);
+ if (i == map.end())
+ {
+ return stop_checking = false;
+ }
+
+ level = i->second;
+ return stop_checking = true;
+ }
+
+ bool checkLevelMap( const LevelMap& map,
+ const char *const * keys,
+ size_t count,
+ LLError::ELevel& level)
+ {
+ bool found_level = false;
+
+ LLError::ELevel tag_level = LLError::LEVEL_NONE;
+
+ for (size_t i = 0; i < count; i++)
+ {
+ LevelMap::const_iterator it = map.find(keys[i]);
+ if (it != map.end())
+ {
+ found_level = true;
+ tag_level = llmin(tag_level, it->second);
+ }
+ }
+
+ if (found_level)
+ {
+ level = tag_level;
+ }
+ return found_level;
+ }
+}
+
+namespace LLError
+{
+
+ bool Log::shouldLog(CallSite& site)
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return false;
+ }
+
+ Globals *g = Globals::getInstance();
+ SettingsConfigPtr s = g->getSettingsConfig();
+
+ s->mShouldLogCallCounter++;
+
+ const std::string& class_name = className(site.mClassInfo);
+ std::string function_name = functionName(site.mFunction);
+#if LL_LINUX
+ // gross, but typeid comparison seems to always fail here with gcc4.1
+ if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name()))
+#else
+ if (site.mClassInfo != typeid(NoClassInfo))
+#endif // LL_LINUX
+ {
+ function_name = class_name + "::" + function_name;
+ }
+
+ ELevel compareLevel = s->mDefaultLevel;
+
+ // The most specific match found will be used as the log level,
+ // since the computation short circuits.
+ // So, in increasing order of importance:
+ // Default < Tags < File < Class < Function
+ checkLevelMap(s->mFunctionLevelMap, function_name, compareLevel)
+ || checkLevelMap(s->mClassLevelMap, class_name, compareLevel)
+ || checkLevelMap(s->mFileLevelMap, abbreviateFile(site.mFile), compareLevel)
+ || (site.mTagCount > 0
+ ? checkLevelMap(s->mTagLevelMap, site.mTags, site.mTagCount, compareLevel)
+ : false);
+
+ site.mCached = true;
+ g->addCallSite(site);
+ return site.mShouldLog = site.mLevel >= compareLevel;
+ }
+
+
+ void Log::flush(const std::ostringstream& out, const CallSite& site)
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ Globals* g = Globals::getInstance();
+ SettingsConfigPtr s = g->getSettingsConfig();
+
+ std::string message = out.str();
+
+ if (site.mPrintOnce)
+ {
+ std::ostringstream message_stream;
+
+ std::map<std::string, unsigned int>::iterator messageIter = s->mUniqueLogMessages.find(message);
+ if (messageIter != s->mUniqueLogMessages.end())
+ {
+ messageIter->second++;
+ unsigned int num_messages = messageIter->second;
+ if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0)
+ {
+ message_stream << "ONCE (" << num_messages << "th time seen): ";
+ }
+ else
+ {
+ return;
+ }
+ }
+ else
+ {
+ message_stream << "ONCE: ";
+ s->mUniqueLogMessages[message] = 1;
+ }
+ message_stream << message;
+ message = message_stream.str();
+ }
+
+ writeToRecorders(site, message);
+
+ if (site.mLevel == LEVEL_ERROR)
+ {
+ g->mFatalMessage = message;
+ if (s->mCrashFunction)
+ {
+ s->mCrashFunction(message);
+ }
+ }
+ }
+}
+
+namespace LLError
+{
+ SettingsStoragePtr saveAndResetSettings()
+ {
+ return Globals::getInstance()->saveAndResetSettingsConfig();
+ }
+
+ void restoreSettings(SettingsStoragePtr pSettingsStorage)
+ {
+ return Globals::getInstance()->restore(pSettingsStorage);
+ }
+
+ std::string removePrefix(std::string& s, const std::string& p)
+ {
+ std::string::size_type where = s.find(p);
+ if (where == std::string::npos)
+ {
+ return s;
+ }
+
+ return std::string(s, where + p.size());
+ }
+
+ void replaceChar(std::string& s, char old, char replacement)
+ {
+ std::string::size_type i = 0;
+ std::string::size_type len = s.length();
+ for ( ; i < len; i++ )
+ {
+ if (s[i] == old)
+ {
+ s[i] = replacement;
+ }
+ }
+ }
+
+ std::string abbreviateFile(const std::string& filePath)
+ {
+ std::string f = filePath;
+#if LL_WINDOWS
+ replaceChar(f, '\\', '/');
+#endif
+ static std::string indra_prefix = "indra/";
+ f = removePrefix(f, indra_prefix);
+
+#if LL_DARWIN
+ static std::string newview_prefix = "newview/../";
+ f = removePrefix(f, newview_prefix);
+#endif
+
+ return f;
+ }
+
+ int shouldLogCallCount()
+ {
+ SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
+ return s->mShouldLogCallCounter;
+ }
+
+ std::string utcTime()
+ {
+ time_t now = time(NULL);
+ const size_t BUF_SIZE = 64;
+ char time_str[BUF_SIZE]; /* Flawfinder: ignore */
+
+ auto chars = strftime(time_str, BUF_SIZE,
+ "%Y-%m-%dT%H:%M:%SZ",
+ gmtime(&now));
+
+ return chars ? time_str : "time error";
+ }
+}
+
+namespace LLError
+{
+ LLCallStacks::StringVector LLCallStacks::sBuffer ;
+
+ //static
+ void LLCallStacks::push(const char* function, const int line)
+ {
+ LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ if(sBuffer.size() > 511)
+ {
+ clear() ;
+ }
+
+ std::ostringstream out;
+ insert(out, function, line);
+ sBuffer.push_back(out.str());
+ }
+
+ //static
+ void LLCallStacks::insert(std::ostream& out, const char* function, const int line)
+ {
+ out << function << " line " << line << " " ;
+ }
+
+ //static
+ void LLCallStacks::end(const std::ostringstream& out)
+ {
+ LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ if(sBuffer.size() > 511)
+ {
+ clear() ;
+ }
+
+ sBuffer.push_back(out.str());
+ }
+
+ //static
+ void LLCallStacks::print()
+ {
+ LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ if(! sBuffer.empty())
+ {
+ LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
+ for (StringVector::const_reverse_iterator ri(sBuffer.rbegin()), re(sBuffer.rend());
+ ri != re; ++ri)
+ {
+ LL_INFOS() << (*ri) << LL_ENDL;
+ }
+ LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
+ }
+
+ cleanup();
+ }
+
+ //static
+ void LLCallStacks::clear()
+ {
+ sBuffer.clear();
+ }
+
+ //static
+ void LLCallStacks::cleanup()
+ {
+ clear();
+ }
+
+ std::ostream& operator<<(std::ostream& out, const LLStacktrace&)
+ {
+ return out << boost::stacktrace::stacktrace();
+ }
+
+ // LLOutOfMemoryWarning
+ std::string LLUserWarningMsg::sLocalizedOutOfMemoryTitle;
+ std::string LLUserWarningMsg::sLocalizedOutOfMemoryWarning;
+ LLUserWarningMsg::Handler LLUserWarningMsg::sHandler;
+
+ void LLUserWarningMsg::show(const std::string& message)
+ {
+ if (sHandler)
+ {
+ sHandler(std::string(), message);
+ }
+ }
+
+ void LLUserWarningMsg::showOutOfMemory()
+ {
+ if (sHandler && !sLocalizedOutOfMemoryTitle.empty())
+ {
+ sHandler(sLocalizedOutOfMemoryTitle, sLocalizedOutOfMemoryWarning);
+ }
+ }
+
+ void LLUserWarningMsg::showMissingFiles()
+ {
+ // Files Are missing, likely can't localize.
+ const std::string error_string =
+ "Second Life viewer couldn't access some of the files it needs and will be closed."
+ "\n\nPlease reinstall viewer from https://secondlife.com/support/downloads/ and "
+ "contact https://support.secondlife.com if issue persists after reinstall.";
+ sHandler("Missing Files", error_string);
+ }
+
+ void LLUserWarningMsg::setHandler(const LLUserWarningMsg::Handler &handler)
+ {
+ sHandler = handler;
+ }
+
+ void LLUserWarningMsg::setOutOfMemoryStrings(const std::string& title, const std::string& message)
+ {
+ sLocalizedOutOfMemoryTitle = title;
+ sLocalizedOutOfMemoryWarning = message;
+ }
+}
+
+void crashdriver(void (*callback)(int*))
+{
+ // The LLERROR_CRASH macro used to have inline code of the form:
+ //int* make_me_crash = NULL;
+ //*make_me_crash = 0;
+
+ // But compilers are getting smart enough to recognize that, so we must
+ // assign to an address supplied by a separate source file. We could do
+ // the assignment here in crashdriver() -- but then BugSplat would group
+ // all LL_ERRS() crashes as the fault of this one function, instead of
+ // identifying the specific LL_ERRS() source line. So instead, do the
+ // assignment in a lambda in the caller's source. We just provide the
+ // nullptr target.
+ callback(nullptr);
+}
diff --git a/indra/llcommon/lleventemitter.h b/indra/llcommon/lleventemitter.h index 14160e1e76..0079104715 100644 --- a/indra/llcommon/lleventemitter.h +++ b/indra/llcommon/lleventemitter.h @@ -1,102 +1,102 @@ -/** - * @file lleventemitter.h - * @brief General event emitter class - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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$ - */ - -// header guard -#ifndef LL_EVENTEMITTER_H -#define LL_EVENTEMITTER_H - -// standard headers -#include <algorithm> -#include <typeinfo> -#include <iostream> -#include <string> -#include <list> - -#include "stdtypes.h" - -/////////////////////////////////////////////////////////////////////////////// -// templatized emitter class -template < class T > -class eventEmitter -{ - public: - typedef typename T::EventType EventType; - typedef std::list< T* > ObserverContainer; - typedef void ( T::*observerMethod )( const EventType& ); - - protected: - ObserverContainer observers; - - public: - eventEmitter () { }; - - ~eventEmitter () { }; - - /////////////////////////////////////////////////////////////////////////////// - // - BOOL addObserver ( T* observerIn ) - { - if ( ! observerIn ) - return FALSE; - - // check if observer already exists - if ( std::find ( observers.begin (), observers.end (), observerIn ) != observers.end () ) - return FALSE; - - // save it - observers.push_back ( observerIn ); - - return true; - }; - - /////////////////////////////////////////////////////////////////////////////// - // - BOOL remObserver ( T* observerIn ) - { - if ( ! observerIn ) - return FALSE; - - observers.remove ( observerIn ); - - return TRUE; - }; - - /////////////////////////////////////////////////////////////////////////////// - // - void update ( observerMethod method, const EventType& msgIn ) - { - typename std::list< T* >::iterator iter = observers.begin (); - - while ( iter != observers.end () ) - { - ( ( *iter )->*method ) ( msgIn ); - - ++iter; - }; - }; -}; - -#endif // lleventemitter_h +/**
+ * @file lleventemitter.h
+ * @brief General event emitter class
+ *
+ * $LicenseInfo:firstyear=2005&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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$
+ */
+
+// header guard
+#ifndef LL_EVENTEMITTER_H
+#define LL_EVENTEMITTER_H
+
+// standard headers
+#include <algorithm>
+#include <typeinfo>
+#include <iostream>
+#include <string>
+#include <list>
+
+#include "stdtypes.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// templatized emitter class
+template < class T >
+class eventEmitter
+{
+ public:
+ typedef typename T::EventType EventType;
+ typedef std::list< T* > ObserverContainer;
+ typedef void ( T::*observerMethod )( const EventType& );
+
+ protected:
+ ObserverContainer observers;
+
+ public:
+ eventEmitter () { };
+
+ ~eventEmitter () { };
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ bool addObserver ( T* observerIn )
+ {
+ if ( ! observerIn )
+ return false;
+
+ // check if observer already exists
+ if ( std::find ( observers.begin (), observers.end (), observerIn ) != observers.end () )
+ return false;
+
+ // save it
+ observers.push_back ( observerIn );
+
+ return true;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ bool remObserver ( T* observerIn )
+ {
+ if ( ! observerIn )
+ return false;
+
+ observers.remove ( observerIn );
+
+ return true;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ void update ( observerMethod method, const EventType& msgIn )
+ {
+ typename std::list< T* >::iterator iter = observers.begin ();
+
+ while ( iter != observers.end () )
+ {
+ ( ( *iter )->*method ) ( msgIn );
+
+ ++iter;
+ };
+ };
+};
+
+#endif // lleventemitter_h
diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h index ed6f10d5e1..20c5fe7a82 100644 --- a/indra/llcommon/lleventtimer.h +++ b/indra/llcommon/lleventtimer.h @@ -1,122 +1,122 @@ -/** - * @file lleventtimer.h - * @brief Cross-platform objects for doing timing - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_EVENTTIMER_H -#define LL_EVENTTIMER_H - -#include "stdtypes.h" -#include "lldate.h" -#include "llinstancetracker.h" -#include "lltimer.h" - -// class for scheduling a function to be called at a given frequency (approximate, inprecise) -class LL_COMMON_API LLEventTimer : public LLInstanceTracker<LLEventTimer> -{ -public: - - LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds - LLEventTimer(const LLDate& time); - virtual ~LLEventTimer(); - - //function to be called at the supplied frequency - // Normally return FALSE; TRUE will delete the timer after the function returns. - virtual BOOL tick() = 0; - - static void updateClass(); - - /// Schedule recurring calls to generic callable every period seconds. - /// Returns a pointer; if you delete it, cancels the recurring calls. - template <typename CALLABLE> - static LLEventTimer* run_every(F32 period, const CALLABLE& callable); - - /// Schedule a future call to generic callable. Returns a pointer. - /// CAUTION: The object referenced by that pointer WILL BE DELETED once - /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT - /// pointer->getInstance(pointer)!) can be used to test whether the - /// pointer is still valid. If it is, deleting it will cancel the - /// callback. - template <typename CALLABLE> - static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable); - - /// Like run_at(), but after a time delta rather than at a timestamp. - /// Same CAUTION. - template <typename CALLABLE> - static LLEventTimer* run_after(F32 interval, const CALLABLE& callable); - -protected: - LLTimer mEventTimer; - F32 mPeriod; - -private: - template <typename CALLABLE> - class Generic; -}; - -template <typename CALLABLE> -class LLEventTimer::Generic: public LLEventTimer -{ -public: - // making TIME generic allows engaging either LLEventTimer constructor - template <typename TIME> - Generic(const TIME& time, bool once, const CALLABLE& callable): - LLEventTimer(time), - mOnce(once), - mCallable(callable) - {} - BOOL tick() override - { - mCallable(); - // true tells updateClass() to delete this instance - return mOnce; - } - -private: - bool mOnce; - CALLABLE mCallable; -}; - -template <typename CALLABLE> -LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable) -{ - // return false to schedule recurring calls - return new Generic<CALLABLE>(period, false, callable); -} - -template <typename CALLABLE> -LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable) -{ - // return true for one-shot callback - return new Generic<CALLABLE>(time, true, callable); -} - -template <typename CALLABLE> -LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable) -{ - // one-shot callback after specified interval - return new Generic<CALLABLE>(interval, true, callable); -} - -#endif //LL_EVENTTIMER_H +/**
+ * @file lleventtimer.h
+ * @brief Cross-platform objects for doing timing
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_EVENTTIMER_H
+#define LL_EVENTTIMER_H
+
+#include "stdtypes.h"
+#include "lldate.h"
+#include "llinstancetracker.h"
+#include "lltimer.h"
+
+// class for scheduling a function to be called at a given frequency (approximate, inprecise)
+class LL_COMMON_API LLEventTimer : public LLInstanceTracker<LLEventTimer>
+{
+public:
+
+ LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds
+ LLEventTimer(const LLDate& time);
+ virtual ~LLEventTimer();
+
+ //function to be called at the supplied frequency
+ // Normally return FALSE; TRUE will delete the timer after the function returns.
+ virtual bool tick() = 0;
+
+ static void updateClass();
+
+ /// Schedule recurring calls to generic callable every period seconds.
+ /// Returns a pointer; if you delete it, cancels the recurring calls.
+ template <typename CALLABLE>
+ static LLEventTimer* run_every(F32 period, const CALLABLE& callable);
+
+ /// Schedule a future call to generic callable. Returns a pointer.
+ /// CAUTION: The object referenced by that pointer WILL BE DELETED once
+ /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT
+ /// pointer->getInstance(pointer)!) can be used to test whether the
+ /// pointer is still valid. If it is, deleting it will cancel the
+ /// callback.
+ template <typename CALLABLE>
+ static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable);
+
+ /// Like run_at(), but after a time delta rather than at a timestamp.
+ /// Same CAUTION.
+ template <typename CALLABLE>
+ static LLEventTimer* run_after(F32 interval, const CALLABLE& callable);
+
+protected:
+ LLTimer mEventTimer;
+ F32 mPeriod;
+
+private:
+ template <typename CALLABLE>
+ class Generic;
+};
+
+template <typename CALLABLE>
+class LLEventTimer::Generic: public LLEventTimer
+{
+public:
+ // making TIME generic allows engaging either LLEventTimer constructor
+ template <typename TIME>
+ Generic(const TIME& time, bool once, const CALLABLE& callable):
+ LLEventTimer(time),
+ mOnce(once),
+ mCallable(callable)
+ {}
+ bool tick() override
+ {
+ mCallable();
+ // true tells updateClass() to delete this instance
+ return mOnce;
+ }
+
+private:
+ bool mOnce;
+ CALLABLE mCallable;
+};
+
+template <typename CALLABLE>
+LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable)
+{
+ // return false to schedule recurring calls
+ return new Generic<CALLABLE>(period, false, callable);
+}
+
+template <typename CALLABLE>
+LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable)
+{
+ // return true for one-shot callback
+ return new Generic<CALLABLE>(time, true, callable);
+}
+
+template <typename CALLABLE>
+LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable)
+{
+ // one-shot callback after specified interval
+ return new Generic<CALLABLE>(interval, true, callable);
+}
+
+#endif //LL_EVENTTIMER_H
diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp index 2805662d6f..8694129c77 100644 --- a/indra/llcommon/llframetimer.cpp +++ b/indra/llcommon/llframetimer.cpp @@ -1,150 +1,150 @@ -/** - * @file llframetimer.cpp - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "u64.h" - -#include "llframetimer.h" - -// Static members -//LLTimer LLFrameTimer::sInternalTimer; -U64 LLFrameTimer::sStartTotalTime = totalTime(); -F64 LLFrameTimer::sFrameTime = 0.0; -U64 LLFrameTimer::sTotalTime = 0; -F64 LLFrameTimer::sTotalSeconds = 0.0; -S32 LLFrameTimer::sFrameCount = 0; -U64 LLFrameTimer::sFrameDeltaTime = 0; -const F64 USEC_TO_SEC_F64 = 0.000001; - -// static -void LLFrameTimer::updateFrameTime() -{ - U64 total_time = totalTime(); - sFrameDeltaTime = total_time - sTotalTime; - sTotalTime = total_time; - sTotalSeconds = U64_to_F64(sTotalTime) * USEC_TO_SEC_F64; - sFrameTime = U64_to_F64(sTotalTime - sStartTotalTime) * USEC_TO_SEC_F64; -} - -void LLFrameTimer::start() -{ - reset(); - mStarted = TRUE; -} - -void LLFrameTimer::stop() -{ - mStarted = FALSE; -} - -void LLFrameTimer::reset() -{ - mStartTime = sFrameTime; - mExpiry = sFrameTime; -} - -void LLFrameTimer::resetWithExpiry(F32 expiration) -{ - reset(); - setTimerExpirySec(expiration); -} - -// Don't combine pause/unpause with start/stop -// Useage: -// LLFrameTime foo; // starts automatically -// foo.unpause(); // noop but safe -// foo.pause(); // pauses timer -// foo.unpause() // unpauses -// F32 elapsed = foo.getElapsedTimeF32() // does not include time between pause() and unpause() -// Note: elapsed would also be valid with no unpause() call (= time run until pause() called) -void LLFrameTimer::pause() -{ - if (mStarted) - mStartTime = sFrameTime - mStartTime; // save dtime - mStarted = FALSE; -} - -void LLFrameTimer::unpause() -{ - if (!mStarted) - mStartTime = sFrameTime - mStartTime; // restore dtime - mStarted = TRUE; -} - -void LLFrameTimer::setTimerExpirySec(F32 expiration) -{ - mExpiry = expiration + mStartTime; -} - -void LLFrameTimer::setExpiryAt(F64 seconds_since_epoch) -{ - mStartTime = sFrameTime; - mExpiry = seconds_since_epoch - (USEC_TO_SEC_F64 * sStartTotalTime); -} - -F64 LLFrameTimer::expiresAt() const -{ - F64 expires_at = U64_to_F64(sStartTotalTime) * USEC_TO_SEC_F64; - expires_at += mExpiry; - return expires_at; -} - -BOOL LLFrameTimer::checkExpirationAndReset(F32 expiration) -{ - //LL_INFOS() << "LLFrameTimer::checkExpirationAndReset()" << LL_ENDL; - //LL_INFOS() << " mStartTime:" << mStartTime << LL_ENDL; - //LL_INFOS() << " sFrameTime:" << sFrameTime << LL_ENDL; - //LL_INFOS() << " mExpiry: " << mExpiry << LL_ENDL; - - if(hasExpired()) - { - reset(); - setTimerExpirySec(expiration); - return TRUE; - } - return FALSE; -} - -// static -F32 LLFrameTimer::getFrameDeltaTimeF32() -{ - return (F32)(U64_to_F64(sFrameDeltaTime) * USEC_TO_SEC_F64); -} - - -// static -// Return seconds since the current frame started -F32 LLFrameTimer::getCurrentFrameTime() -{ - U64 frame_time = totalTime() - sTotalTime; - return (F32)(U64_to_F64(frame_time) * USEC_TO_SEC_F64); -} - -// Glue code to avoid full class .h file #includes -F32 getCurrentFrameTime() -{ - return (F32)(LLFrameTimer::getCurrentFrameTime()); -} +/**
+ * @file llframetimer.cpp
+ *
+ * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "u64.h"
+
+#include "llframetimer.h"
+
+// Static members
+//LLTimer LLFrameTimer::sInternalTimer;
+U64 LLFrameTimer::sStartTotalTime = totalTime();
+F64 LLFrameTimer::sFrameTime = 0.0;
+U64 LLFrameTimer::sTotalTime = 0;
+F64 LLFrameTimer::sTotalSeconds = 0.0;
+S32 LLFrameTimer::sFrameCount = 0;
+U64 LLFrameTimer::sFrameDeltaTime = 0;
+const F64 USEC_TO_SEC_F64 = 0.000001;
+
+// static
+void LLFrameTimer::updateFrameTime()
+{
+ U64 total_time = totalTime();
+ sFrameDeltaTime = total_time - sTotalTime;
+ sTotalTime = total_time;
+ sTotalSeconds = U64_to_F64(sTotalTime) * USEC_TO_SEC_F64;
+ sFrameTime = U64_to_F64(sTotalTime - sStartTotalTime) * USEC_TO_SEC_F64;
+}
+
+void LLFrameTimer::start()
+{
+ reset();
+ mStarted = true;
+}
+
+void LLFrameTimer::stop()
+{
+ mStarted = false;
+}
+
+void LLFrameTimer::reset()
+{
+ mStartTime = sFrameTime;
+ mExpiry = sFrameTime;
+}
+
+void LLFrameTimer::resetWithExpiry(F32 expiration)
+{
+ reset();
+ setTimerExpirySec(expiration);
+}
+
+// Don't combine pause/unpause with start/stop
+// Useage:
+// LLFrameTime foo; // starts automatically
+// foo.unpause(); // noop but safe
+// foo.pause(); // pauses timer
+// foo.unpause() // unpauses
+// F32 elapsed = foo.getElapsedTimeF32() // does not include time between pause() and unpause()
+// Note: elapsed would also be valid with no unpause() call (= time run until pause() called)
+void LLFrameTimer::pause()
+{
+ if (mStarted)
+ mStartTime = sFrameTime - mStartTime; // save dtime
+ mStarted = false;
+}
+
+void LLFrameTimer::unpause()
+{
+ if (!mStarted)
+ mStartTime = sFrameTime - mStartTime; // restore dtime
+ mStarted = true;
+}
+
+void LLFrameTimer::setTimerExpirySec(F32 expiration)
+{
+ mExpiry = expiration + mStartTime;
+}
+
+void LLFrameTimer::setExpiryAt(F64 seconds_since_epoch)
+{
+ mStartTime = sFrameTime;
+ mExpiry = seconds_since_epoch - (USEC_TO_SEC_F64 * sStartTotalTime);
+}
+
+F64 LLFrameTimer::expiresAt() const
+{
+ F64 expires_at = U64_to_F64(sStartTotalTime) * USEC_TO_SEC_F64;
+ expires_at += mExpiry;
+ return expires_at;
+}
+
+bool LLFrameTimer::checkExpirationAndReset(F32 expiration)
+{
+ //LL_INFOS() << "LLFrameTimer::checkExpirationAndReset()" << LL_ENDL;
+ //LL_INFOS() << " mStartTime:" << mStartTime << LL_ENDL;
+ //LL_INFOS() << " sFrameTime:" << sFrameTime << LL_ENDL;
+ //LL_INFOS() << " mExpiry: " << mExpiry << LL_ENDL;
+
+ if(hasExpired())
+ {
+ reset();
+ setTimerExpirySec(expiration);
+ return true;
+ }
+ return false;
+}
+
+// static
+F32 LLFrameTimer::getFrameDeltaTimeF32()
+{
+ return (F32)(U64_to_F64(sFrameDeltaTime) * USEC_TO_SEC_F64);
+}
+
+
+// static
+// Return seconds since the current frame started
+F32 LLFrameTimer::getCurrentFrameTime()
+{
+ U64 frame_time = totalTime() - sTotalTime;
+ return (F32)(U64_to_F64(frame_time) * USEC_TO_SEC_F64);
+}
+
+// Glue code to avoid full class .h file #includes
+F32 getCurrentFrameTime()
+{
+ return (F32)(LLFrameTimer::getCurrentFrameTime());
+}
diff --git a/indra/llcommon/llframetimer.h b/indra/llcommon/llframetimer.h index 876d933fd1..b519a637e3 100644 --- a/indra/llcommon/llframetimer.h +++ b/indra/llcommon/llframetimer.h @@ -1,151 +1,151 @@ -/** - * @file llframetimer.h - * @brief A lightweight timer that measures seconds and is only - * updated once per frame. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLFRAMETIMER_H -#define LL_LLFRAMETIMER_H - -/** - * *NOTE: Because of limitations on linux which we do not really have - * time to explore, the total time is derived from the frame time - * and is recsynchronized on every frame. - */ - -#include "lltimer.h" - -class LL_COMMON_API LLFrameTimer -{ -public: - LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(TRUE) {} - - // Return the number of seconds since the start of this - // application instance. - static F64SecondsImplicit getElapsedSeconds() - { - // Loses msec precision after ~4.5 hours... - return sFrameTime; - } - - // Return a low precision usec since epoch - static U64 getTotalTime() - { - return sTotalTime ? U64MicrosecondsImplicit(sTotalTime) : totalTime(); - } - - // Return a low precision seconds since epoch - static F64 getTotalSeconds() - { - return sTotalSeconds; - } - - // Call this method once per frame to update the current frame time. This is actually called - // at some other times as well - static void updateFrameTime(); - - // Call this method once, and only once, per frame to update the current frame count. - static void updateFrameCount() { sFrameCount++; } - - static U32 getFrameCount() { return sFrameCount; } - - static F32 getFrameDeltaTimeF32(); - - // Return seconds since the current frame started - static F32 getCurrentFrameTime(); - - // MANIPULATORS - void start(); - void stop(); - void reset(); - void resetWithExpiry(F32 expiration); - void pause(); - void unpause(); - void setTimerExpirySec(F32 expiration); - void setExpiryAt(F64 seconds_since_epoch); - BOOL checkExpirationAndReset(F32 expiration); - F32 getElapsedTimeAndResetF32() { F32 t = F32(sFrameTime - mStartTime); reset(); return t; } - - void setAge(const F64 age) { mStartTime = sFrameTime - age; } - - // ACCESSORS - BOOL hasExpired() const { return (sFrameTime >= mExpiry); } - F32 getTimeToExpireF32() const { return (F32)(mExpiry - sFrameTime); } - F32 getElapsedTimeF32() const { return mStarted ? (F32)(sFrameTime - mStartTime) : (F32)mStartTime; } - BOOL getStarted() const { return mStarted; } - - // return the seconds since epoch when this timer will expire. - F64 expiresAt() const; - -protected: - // A single, high resolution timer that drives all LLFrameTimers - // *NOTE: no longer used. - //static LLTimer sInternalTimer; - - // - // Aplication constants - // - - // Start time of opp in usec since epoch - static U64 sStartTotalTime; - - // - // Data updated per frame - // - - // Seconds since application start - static F64 sFrameTime; - - // Time that has elapsed since last call to updateFrameTime() - static U64 sFrameDeltaTime; - - // Total microseconds since epoch. - static U64 sTotalTime; - - // Seconds since epoch. - static F64 sTotalSeconds; - - // Total number of frames elapsed in application - static S32 sFrameCount; - - // - // Member data - // - - // Number of seconds after application start when this timer was - // started. Set equal to sFrameTime when reset. - F64 mStartTime; - - // Timer expires this many seconds after application start time. - F64 mExpiry; - - // Useful bit of state usually associated with timers, but does - // not affect actual functionality - BOOL mStarted; -}; - -// Glue code for Havok (or anything else that doesn't want the full .h files) -extern F32 getCurrentFrameTime(); - -#endif // LL_LLFRAMETIMER_H +/**
+ * @file llframetimer.h
+ * @brief A lightweight timer that measures seconds and is only
+ * updated once per frame.
+ *
+ * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLFRAMETIMER_H
+#define LL_LLFRAMETIMER_H
+
+/**
+ * *NOTE: Because of limitations on linux which we do not really have
+ * time to explore, the total time is derived from the frame time
+ * and is recsynchronized on every frame.
+ */
+
+#include "lltimer.h"
+
+class LL_COMMON_API LLFrameTimer
+{
+public:
+ LLFrameTimer() : mStartTime( sFrameTime ), mExpiry(0), mStarted(true) {}
+
+ // Return the number of seconds since the start of this
+ // application instance.
+ static F64SecondsImplicit getElapsedSeconds()
+ {
+ // Loses msec precision after ~4.5 hours...
+ return sFrameTime;
+ }
+
+ // Return a low precision usec since epoch
+ static U64 getTotalTime()
+ {
+ return sTotalTime ? U64MicrosecondsImplicit(sTotalTime) : totalTime();
+ }
+
+ // Return a low precision seconds since epoch
+ static F64 getTotalSeconds()
+ {
+ return sTotalSeconds;
+ }
+
+ // Call this method once per frame to update the current frame time. This is actually called
+ // at some other times as well
+ static void updateFrameTime();
+
+ // Call this method once, and only once, per frame to update the current frame count.
+ static void updateFrameCount() { sFrameCount++; }
+
+ static U32 getFrameCount() { return sFrameCount; }
+
+ static F32 getFrameDeltaTimeF32();
+
+ // Return seconds since the current frame started
+ static F32 getCurrentFrameTime();
+
+ // MANIPULATORS
+ void start();
+ void stop();
+ void reset();
+ void resetWithExpiry(F32 expiration);
+ void pause();
+ void unpause();
+ void setTimerExpirySec(F32 expiration);
+ void setExpiryAt(F64 seconds_since_epoch);
+ bool checkExpirationAndReset(F32 expiration);
+ F32 getElapsedTimeAndResetF32() { F32 t = F32(sFrameTime - mStartTime); reset(); return t; }
+
+ void setAge(const F64 age) { mStartTime = sFrameTime - age; }
+
+ // ACCESSORS
+ bool hasExpired() const { return (sFrameTime >= mExpiry); }
+ F32 getTimeToExpireF32() const { return (F32)(mExpiry - sFrameTime); }
+ F32 getElapsedTimeF32() const { return mStarted ? (F32)(sFrameTime - mStartTime) : (F32)mStartTime; }
+ bool getStarted() const { return mStarted; }
+
+ // return the seconds since epoch when this timer will expire.
+ F64 expiresAt() const;
+
+protected:
+ // A single, high resolution timer that drives all LLFrameTimers
+ // *NOTE: no longer used.
+ //static LLTimer sInternalTimer;
+
+ //
+ // Aplication constants
+ //
+
+ // Start time of opp in usec since epoch
+ static U64 sStartTotalTime;
+
+ //
+ // Data updated per frame
+ //
+
+ // Seconds since application start
+ static F64 sFrameTime;
+
+ // Time that has elapsed since last call to updateFrameTime()
+ static U64 sFrameDeltaTime;
+
+ // Total microseconds since epoch.
+ static U64 sTotalTime;
+
+ // Seconds since epoch.
+ static F64 sTotalSeconds;
+
+ // Total number of frames elapsed in application
+ static S32 sFrameCount;
+
+ //
+ // Member data
+ //
+
+ // Number of seconds after application start when this timer was
+ // started. Set equal to sFrameTime when reset.
+ F64 mStartTime;
+
+ // Timer expires this many seconds after application start time.
+ F64 mExpiry;
+
+ // Useful bit of state usually associated with timers, but does
+ // not affect actual functionality
+ bool mStarted;
+};
+
+// Glue code for Havok (or anything else that doesn't want the full .h files)
+extern F32 getCurrentFrameTime();
+
+#endif // LL_LLFRAMETIMER_H
diff --git a/indra/llcommon/llkeythrottle.h b/indra/llcommon/llkeythrottle.h index a5b5eaa946..0909acb747 100644 --- a/indra/llcommon/llkeythrottle.h +++ b/indra/llcommon/llkeythrottle.h @@ -1,331 +1,331 @@ -/** - * @file llkeythrottle.h - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLKEY_THROTTLE_H -#define LL_LLKEY_THROTTLE_H - -// LLKeyThrottle keeps track of the number of action occurences with a key value -// for a type over a given time period. If the rate set in the constructor is -// exceeed, the key is considered blocked. The transition from unblocked to -// blocked is noted so the responsible agent can be informed. This transition -// takes twice the look back window to clear. - -#include "linden_common.h" - -#include "llframetimer.h" -#include <map> - - -// forward declaration so LLKeyThrottleImpl can befriend it -template <class T> class LLKeyThrottle; - - -// Implementation utility class - use LLKeyThrottle, not this -template <class T> -class LLKeyThrottleImpl -{ - friend class LLKeyThrottle<T>; -protected: - struct Entry { - U32 count; - bool blocked; - - Entry() : count(0), blocked(false) { } - }; - - typedef std::map<T, Entry> EntryMap; - - EntryMap* prevMap; - EntryMap* currMap; - - U32 countLimit; - // maximum number of keys allowed per interval - - U64 intervalLength; // each map covers this time period (usec or frame number) - U64 startTime; // start of the time period (usec or frame number) - // currMap started counting at this time - // prevMap covers the previous interval - - LLKeyThrottleImpl() : - prevMap(NULL), - currMap(NULL), - countLimit(0), - intervalLength(1), - startTime(0) - {} - - static U64 getTime() - { - return LLFrameTimer::getTotalTime(); - } - static U64 getFrame() // Return the current frame number - { - return (U64) LLFrameTimer::getFrameCount(); - } -}; - - -template< class T > -class LLKeyThrottle -{ -public: - // @param realtime = FALSE for frame-based throttle, TRUE for usec - // real-time throttle - LLKeyThrottle(U32 limit, F32 interval, BOOL realtime = TRUE) - : m(* new LLKeyThrottleImpl<T>) - { - setParameters( limit, interval, realtime ); - } - - ~LLKeyThrottle() - { - delete m.prevMap; - delete m.currMap; - delete &m; - } - - enum State { - THROTTLE_OK, // rate not exceeded, let pass - THROTTLE_NEWLY_BLOCKED, // rate exceed for the first time - THROTTLE_BLOCKED, // rate exceed, block key - }; - - F64 getActionCount(const T& id) - { - U64 now = 0; - if ( mIsRealtime ) - { - now = LLKeyThrottleImpl<T>::getTime(); - } - else - { - now = LLKeyThrottleImpl<T>::getFrame(); - } - - if (now >= (m.startTime + m.intervalLength)) - { - if (now < (m.startTime + 2 * m.intervalLength)) - { - // prune old data - delete m.prevMap; - m.prevMap = m.currMap; - m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; - - m.startTime += m.intervalLength; - } - else - { - // lots of time has passed, all data is stale - delete m.prevMap; - delete m.currMap; - m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap; - m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; - - m.startTime = now; - } - } - - U32 prevCount = 0; - - typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id); - if (prev != m.prevMap->end()) - { - prevCount = prev->second.count; - } - - typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id]; - - // curr.count is the number of keys in - // this current 'time slice' from the beginning of it until now - // prevCount is the number of keys in the previous - // time slice scaled to be one full time slice back from the current - // (now) time. - - // compute current, windowed rate - F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); - F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); - return averageCount; - } - - // call each time the key wants use - State noteAction(const T& id, S32 weight = 1) - { - U64 now = 0; - if ( mIsRealtime ) - { - now = LLKeyThrottleImpl<T>::getTime(); - } - else - { - now = LLKeyThrottleImpl<T>::getFrame(); - } - - if (now >= (m.startTime + m.intervalLength)) - { - if (now < (m.startTime + 2 * m.intervalLength)) - { - // prune old data - delete m.prevMap; - m.prevMap = m.currMap; - m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; - - m.startTime += m.intervalLength; - } - else - { - // lots of time has passed, all data is stale - delete m.prevMap; - delete m.currMap; - m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap; - m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; - - m.startTime = now; - } - } - - U32 prevCount = 0; - bool prevBlocked = false; - - typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id); - if (prev != m.prevMap->end()) - { - prevCount = prev->second.count; - prevBlocked = prev->second.blocked; - } - - typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id]; - - bool wereBlocked = curr.blocked || prevBlocked; - - curr.count += weight; - - // curr.count is the number of keys in - // this current 'time slice' from the beginning of it until now - // prevCount is the number of keys in the previous - // time slice scaled to be one full time slice back from the current - // (now) time. - - // compute current, windowed rate - F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); - F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); - - curr.blocked |= averageCount > m.countLimit; - - bool nowBlocked = curr.blocked || prevBlocked; - - if (!nowBlocked) - { - return THROTTLE_OK; - } - else if (!wereBlocked) - { - return THROTTLE_NEWLY_BLOCKED; - } - else - { - return THROTTLE_BLOCKED; - } - } - - // call to force throttle conditions for id - void throttleAction(const T& id) - { - noteAction(id); - typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id]; - curr.count = llmax(m.countLimit, curr.count); - curr.blocked = true; - } - - // returns true if key is blocked - bool isThrottled(const T& id) const - { - if (m.currMap->empty() - && m.prevMap->empty()) - { - // most of the time we'll fall in here - return false; - } - - // NOTE, we ignore the case where id is in the map but the map is stale. - // You might think that we'd stop throttling things in such a case, - // however it may be that a god has disabled scripts in the region or - // estate --> we probably want to report the state of the id when the - // scripting engine was paused. - typename LLKeyThrottleImpl<T>::EntryMap::const_iterator entry = m.currMap->find(id); - if (entry != m.currMap->end()) - { - return entry->second.blocked; - } - entry = m.prevMap->find(id); - if (entry != m.prevMap->end()) - { - return entry->second.blocked; - } - return false; - } - - // Get the throttling parameters - void getParameters( U32 & out_limit, F32 & out_interval, BOOL & out_realtime ) - { - out_limit = m.countLimit; - out_interval = m.intervalLength; - out_realtime = mIsRealtime; - } - - // Set the throttling behavior - void setParameters( U32 limit, F32 interval, BOOL realtime = TRUE ) - { - // limit is the maximum number of keys - // allowed per interval (in seconds or frames) - mIsRealtime = realtime; - m.countLimit = limit; - if ( mIsRealtime ) - { - m.intervalLength = (U64)(interval * USEC_PER_SEC); - m.startTime = LLKeyThrottleImpl<T>::getTime(); - } - else - { - m.intervalLength = (U64)interval; - m.startTime = LLKeyThrottleImpl<T>::getFrame(); - } - - if ( m.intervalLength == 0 ) - { // Don't allow zero intervals - m.intervalLength = 1; - } - - delete m.prevMap; - m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap; - delete m.currMap; - m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; - } - -protected: - LLKeyThrottleImpl<T>& m; - BOOL mIsRealtime; // TRUE to be time based (default), FALSE for frame based -}; - -#endif +/**
+ * @file llkeythrottle.h
+ *
+ * $LicenseInfo:firstyear=2005&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLKEY_THROTTLE_H
+#define LL_LLKEY_THROTTLE_H
+
+// LLKeyThrottle keeps track of the number of action occurences with a key value
+// for a type over a given time period. If the rate set in the constructor is
+// exceeed, the key is considered blocked. The transition from unblocked to
+// blocked is noted so the responsible agent can be informed. This transition
+// takes twice the look back window to clear.
+
+#include "linden_common.h"
+
+#include "llframetimer.h"
+#include <map>
+
+
+// forward declaration so LLKeyThrottleImpl can befriend it
+template <class T> class LLKeyThrottle;
+
+
+// Implementation utility class - use LLKeyThrottle, not this
+template <class T>
+class LLKeyThrottleImpl
+{
+ friend class LLKeyThrottle<T>;
+protected:
+ struct Entry {
+ U32 count;
+ bool blocked;
+
+ Entry() : count(0), blocked(false) { }
+ };
+
+ typedef std::map<T, Entry> EntryMap;
+
+ EntryMap* prevMap;
+ EntryMap* currMap;
+
+ U32 countLimit;
+ // maximum number of keys allowed per interval
+
+ U64 intervalLength; // each map covers this time period (usec or frame number)
+ U64 startTime; // start of the time period (usec or frame number)
+ // currMap started counting at this time
+ // prevMap covers the previous interval
+
+ LLKeyThrottleImpl() :
+ prevMap(NULL),
+ currMap(NULL),
+ countLimit(0),
+ intervalLength(1),
+ startTime(0)
+ {}
+
+ static U64 getTime()
+ {
+ return LLFrameTimer::getTotalTime();
+ }
+ static U64 getFrame() // Return the current frame number
+ {
+ return (U64) LLFrameTimer::getFrameCount();
+ }
+};
+
+
+template< class T >
+class LLKeyThrottle
+{
+public:
+ // @param realtime = false for frame-based throttle, true for usec
+ // real-time throttle
+ LLKeyThrottle(U32 limit, F32 interval, bool realtime = true)
+ : m(* new LLKeyThrottleImpl<T>)
+ {
+ setParameters( limit, interval, realtime );
+ }
+
+ ~LLKeyThrottle()
+ {
+ delete m.prevMap;
+ delete m.currMap;
+ delete &m;
+ }
+
+ enum State {
+ THROTTLE_OK, // rate not exceeded, let pass
+ THROTTLE_NEWLY_BLOCKED, // rate exceed for the first time
+ THROTTLE_BLOCKED, // rate exceed, block key
+ };
+
+ F64 getActionCount(const T& id)
+ {
+ U64 now = 0;
+ if ( mIsRealtime )
+ {
+ now = LLKeyThrottleImpl<T>::getTime();
+ }
+ else
+ {
+ now = LLKeyThrottleImpl<T>::getFrame();
+ }
+
+ if (now >= (m.startTime + m.intervalLength))
+ {
+ if (now < (m.startTime + 2 * m.intervalLength))
+ {
+ // prune old data
+ delete m.prevMap;
+ m.prevMap = m.currMap;
+ m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+
+ m.startTime += m.intervalLength;
+ }
+ else
+ {
+ // lots of time has passed, all data is stale
+ delete m.prevMap;
+ delete m.currMap;
+ m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+ m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+
+ m.startTime = now;
+ }
+ }
+
+ U32 prevCount = 0;
+
+ typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id);
+ if (prev != m.prevMap->end())
+ {
+ prevCount = prev->second.count;
+ }
+
+ typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id];
+
+ // curr.count is the number of keys in
+ // this current 'time slice' from the beginning of it until now
+ // prevCount is the number of keys in the previous
+ // time slice scaled to be one full time slice back from the current
+ // (now) time.
+
+ // compute current, windowed rate
+ F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength);
+ F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent);
+ return averageCount;
+ }
+
+ // call each time the key wants use
+ State noteAction(const T& id, S32 weight = 1)
+ {
+ U64 now = 0;
+ if ( mIsRealtime )
+ {
+ now = LLKeyThrottleImpl<T>::getTime();
+ }
+ else
+ {
+ now = LLKeyThrottleImpl<T>::getFrame();
+ }
+
+ if (now >= (m.startTime + m.intervalLength))
+ {
+ if (now < (m.startTime + 2 * m.intervalLength))
+ {
+ // prune old data
+ delete m.prevMap;
+ m.prevMap = m.currMap;
+ m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+
+ m.startTime += m.intervalLength;
+ }
+ else
+ {
+ // lots of time has passed, all data is stale
+ delete m.prevMap;
+ delete m.currMap;
+ m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+ m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+
+ m.startTime = now;
+ }
+ }
+
+ U32 prevCount = 0;
+ bool prevBlocked = false;
+
+ typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id);
+ if (prev != m.prevMap->end())
+ {
+ prevCount = prev->second.count;
+ prevBlocked = prev->second.blocked;
+ }
+
+ typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id];
+
+ bool wereBlocked = curr.blocked || prevBlocked;
+
+ curr.count += weight;
+
+ // curr.count is the number of keys in
+ // this current 'time slice' from the beginning of it until now
+ // prevCount is the number of keys in the previous
+ // time slice scaled to be one full time slice back from the current
+ // (now) time.
+
+ // compute current, windowed rate
+ F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength);
+ F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent);
+
+ curr.blocked |= averageCount > m.countLimit;
+
+ bool nowBlocked = curr.blocked || prevBlocked;
+
+ if (!nowBlocked)
+ {
+ return THROTTLE_OK;
+ }
+ else if (!wereBlocked)
+ {
+ return THROTTLE_NEWLY_BLOCKED;
+ }
+ else
+ {
+ return THROTTLE_BLOCKED;
+ }
+ }
+
+ // call to force throttle conditions for id
+ void throttleAction(const T& id)
+ {
+ noteAction(id);
+ typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id];
+ curr.count = llmax(m.countLimit, curr.count);
+ curr.blocked = true;
+ }
+
+ // returns true if key is blocked
+ bool isThrottled(const T& id) const
+ {
+ if (m.currMap->empty()
+ && m.prevMap->empty())
+ {
+ // most of the time we'll fall in here
+ return false;
+ }
+
+ // NOTE, we ignore the case where id is in the map but the map is stale.
+ // You might think that we'd stop throttling things in such a case,
+ // however it may be that a god has disabled scripts in the region or
+ // estate --> we probably want to report the state of the id when the
+ // scripting engine was paused.
+ typename LLKeyThrottleImpl<T>::EntryMap::const_iterator entry = m.currMap->find(id);
+ if (entry != m.currMap->end())
+ {
+ return entry->second.blocked;
+ }
+ entry = m.prevMap->find(id);
+ if (entry != m.prevMap->end())
+ {
+ return entry->second.blocked;
+ }
+ return false;
+ }
+
+ // Get the throttling parameters
+ void getParameters( U32 & out_limit, F32 & out_interval, bool & out_realtime )
+ {
+ out_limit = m.countLimit;
+ out_interval = m.intervalLength;
+ out_realtime = mIsRealtime;
+ }
+
+ // Set the throttling behavior
+ void setParameters( U32 limit, F32 interval, bool realtime = true )
+ {
+ // limit is the maximum number of keys
+ // allowed per interval (in seconds or frames)
+ mIsRealtime = realtime;
+ m.countLimit = limit;
+ if ( mIsRealtime )
+ {
+ m.intervalLength = (U64)(interval * USEC_PER_SEC);
+ m.startTime = LLKeyThrottleImpl<T>::getTime();
+ }
+ else
+ {
+ m.intervalLength = (U64)interval;
+ m.startTime = LLKeyThrottleImpl<T>::getFrame();
+ }
+
+ if ( m.intervalLength == 0 )
+ { // Don't allow zero intervals
+ m.intervalLength = 1;
+ }
+
+ delete m.prevMap;
+ m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+ delete m.currMap;
+ m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
+ }
+
+protected:
+ LLKeyThrottleImpl<T>& m;
+ bool mIsRealtime; // true to be time based (default), false for frame based
+};
+
+#endif
diff --git a/indra/llcommon/lllivefile.cpp b/indra/llcommon/lllivefile.cpp index 15651a6813..3fe5f4bdb6 100644 --- a/indra/llcommon/lllivefile.cpp +++ b/indra/llcommon/lllivefile.cpp @@ -1,198 +1,198 @@ -/** - * @file lllivefile.cpp - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "lllivefile.h" -#include "llframetimer.h" -#include "lleventtimer.h" - -const F32 DEFAULT_CONFIG_FILE_REFRESH = 5.0f; - - -class LLLiveFile::Impl -{ -public: - Impl(const std::string& filename, const F32 refresh_period); - ~Impl(); - - bool check(); - void changed(); - - bool mForceCheck; - F32 mRefreshPeriod; - LLFrameTimer mRefreshTimer; - - std::string mFilename; - time_t mLastModTime; - time_t mLastStatTime; - bool mLastExists; - - LLEventTimer* mEventTimer; -private: - LOG_CLASS(LLLiveFile); -}; - -LLLiveFile::Impl::Impl(const std::string& filename, const F32 refresh_period) - : - mForceCheck(true), - mRefreshPeriod(refresh_period), - mFilename(filename), - mLastModTime(0), - mLastStatTime(0), - mLastExists(false), - mEventTimer(NULL) -{ -} - -LLLiveFile::Impl::~Impl() -{ - delete mEventTimer; -} - -LLLiveFile::LLLiveFile(const std::string& filename, const F32 refresh_period) - : impl(* new Impl(filename, refresh_period)) -{ -} - -LLLiveFile::~LLLiveFile() -{ - delete &impl; -} - - -bool LLLiveFile::Impl::check() -{ - bool detected_change = false; - // Skip the check if not enough time has elapsed and we're not - // forcing a check of the file - if (mForceCheck || mRefreshTimer.getElapsedTimeF32() >= mRefreshPeriod) - { - mForceCheck = false; // force only forces one check - mRefreshTimer.reset(); // don't check again until mRefreshPeriod has passed - - // Stat the file to see if it exists and when it was last modified. - llstat stat_data; - if (LLFile::stat(mFilename, &stat_data)) - { - // Couldn't stat the file, that means it doesn't exist or is - // broken somehow. - if (mLastExists) - { - mLastExists = false; - detected_change = true; // no longer existing is a change! - LL_DEBUGS() << "detected deleted file '" << mFilename << "'" << LL_ENDL; - } - } - else - { - // The file exists - if ( ! mLastExists ) - { - // last check, it did not exist - that counts as a change - LL_DEBUGS() << "detected created file '" << mFilename << "'" << LL_ENDL; - detected_change = true; - } - else if ( stat_data.st_mtime > mLastModTime ) - { - // file modification time is newer than last check - LL_DEBUGS() << "detected updated file '" << mFilename << "'" << LL_ENDL; - detected_change = true; - } - mLastExists = true; - mLastStatTime = stat_data.st_mtime; - } - } - if (detected_change) - { - LL_INFOS() << "detected file change '" << mFilename << "'" << LL_ENDL; - } - return detected_change; -} - -void LLLiveFile::Impl::changed() -{ - // we wanted to read this file, and we were successful. - mLastModTime = mLastStatTime; -} - -bool LLLiveFile::checkAndReload() -{ - bool changed = impl.check(); - if (changed) - { - if(loadFile()) - { - impl.changed(); - this->changed(); - } - else - { - changed = false; - } - } - return changed; -} - -std::string LLLiveFile::filename() const -{ - return impl.mFilename; -} - -namespace -{ - class LiveFileEventTimer : public LLEventTimer - { - public: - LiveFileEventTimer(LLLiveFile& f, F32 refresh) - : LLEventTimer(refresh), mLiveFile(f) - { } - - BOOL tick() - { - mLiveFile.checkAndReload(); - return FALSE; - } - - private: - LLLiveFile& mLiveFile; - }; - -} - -void LLLiveFile::addToEventTimer() -{ - impl.mEventTimer = new LiveFileEventTimer(*this, impl.mRefreshPeriod); -} - -void LLLiveFile::setRefreshPeriod(F32 seconds) -{ - if (seconds < 0.f) - { - seconds = -seconds; - } - impl.mRefreshPeriod = seconds; -} - +/**
+ * @file lllivefile.cpp
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "lllivefile.h"
+#include "llframetimer.h"
+#include "lleventtimer.h"
+
+const F32 DEFAULT_CONFIG_FILE_REFRESH = 5.0f;
+
+
+class LLLiveFile::Impl
+{
+public:
+ Impl(const std::string& filename, const F32 refresh_period);
+ ~Impl();
+
+ bool check();
+ void changed();
+
+ bool mForceCheck;
+ F32 mRefreshPeriod;
+ LLFrameTimer mRefreshTimer;
+
+ std::string mFilename;
+ time_t mLastModTime;
+ time_t mLastStatTime;
+ bool mLastExists;
+
+ LLEventTimer* mEventTimer;
+private:
+ LOG_CLASS(LLLiveFile);
+};
+
+LLLiveFile::Impl::Impl(const std::string& filename, const F32 refresh_period)
+ :
+ mForceCheck(true),
+ mRefreshPeriod(refresh_period),
+ mFilename(filename),
+ mLastModTime(0),
+ mLastStatTime(0),
+ mLastExists(false),
+ mEventTimer(NULL)
+{
+}
+
+LLLiveFile::Impl::~Impl()
+{
+ delete mEventTimer;
+}
+
+LLLiveFile::LLLiveFile(const std::string& filename, const F32 refresh_period)
+ : impl(* new Impl(filename, refresh_period))
+{
+}
+
+LLLiveFile::~LLLiveFile()
+{
+ delete &impl;
+}
+
+
+bool LLLiveFile::Impl::check()
+{
+ bool detected_change = false;
+ // Skip the check if not enough time has elapsed and we're not
+ // forcing a check of the file
+ if (mForceCheck || mRefreshTimer.getElapsedTimeF32() >= mRefreshPeriod)
+ {
+ mForceCheck = false; // force only forces one check
+ mRefreshTimer.reset(); // don't check again until mRefreshPeriod has passed
+
+ // Stat the file to see if it exists and when it was last modified.
+ llstat stat_data;
+ if (LLFile::stat(mFilename, &stat_data))
+ {
+ // Couldn't stat the file, that means it doesn't exist or is
+ // broken somehow.
+ if (mLastExists)
+ {
+ mLastExists = false;
+ detected_change = true; // no longer existing is a change!
+ LL_DEBUGS() << "detected deleted file '" << mFilename << "'" << LL_ENDL;
+ }
+ }
+ else
+ {
+ // The file exists
+ if ( ! mLastExists )
+ {
+ // last check, it did not exist - that counts as a change
+ LL_DEBUGS() << "detected created file '" << mFilename << "'" << LL_ENDL;
+ detected_change = true;
+ }
+ else if ( stat_data.st_mtime > mLastModTime )
+ {
+ // file modification time is newer than last check
+ LL_DEBUGS() << "detected updated file '" << mFilename << "'" << LL_ENDL;
+ detected_change = true;
+ }
+ mLastExists = true;
+ mLastStatTime = stat_data.st_mtime;
+ }
+ }
+ if (detected_change)
+ {
+ LL_INFOS() << "detected file change '" << mFilename << "'" << LL_ENDL;
+ }
+ return detected_change;
+}
+
+void LLLiveFile::Impl::changed()
+{
+ // we wanted to read this file, and we were successful.
+ mLastModTime = mLastStatTime;
+}
+
+bool LLLiveFile::checkAndReload()
+{
+ bool changed = impl.check();
+ if (changed)
+ {
+ if(loadFile())
+ {
+ impl.changed();
+ this->changed();
+ }
+ else
+ {
+ changed = false;
+ }
+ }
+ return changed;
+}
+
+std::string LLLiveFile::filename() const
+{
+ return impl.mFilename;
+}
+
+namespace
+{
+ class LiveFileEventTimer : public LLEventTimer
+ {
+ public:
+ LiveFileEventTimer(LLLiveFile& f, F32 refresh)
+ : LLEventTimer(refresh), mLiveFile(f)
+ { }
+
+ bool tick()
+ {
+ mLiveFile.checkAndReload();
+ return false;
+ }
+
+ private:
+ LLLiveFile& mLiveFile;
+ };
+
+}
+
+void LLLiveFile::addToEventTimer()
+{
+ impl.mEventTimer = new LiveFileEventTimer(*this, impl.mRefreshPeriod);
+}
+
+void LLLiveFile::setRefreshPeriod(F32 seconds)
+{
+ if (seconds < 0.f)
+ {
+ seconds = -seconds;
+ }
+ impl.mRefreshPeriod = seconds;
+}
+
diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h index 28ad62830b..5fae0212c4 100644 --- a/indra/llcommon/llmainthreadtask.h +++ b/indra/llcommon/llmainthreadtask.h @@ -4,7 +4,7 @@ * @date 2019-12-04 * @brief LLMainThreadTask dispatches work to the main thread. When invoked on * the main thread, it performs the work inline. - * + * * $LicenseInfo:firstyear=2019&license=viewerlgpl$ * Copyright (c) 2019, Linden Research, Inc. * $/LicenseInfo$ @@ -79,13 +79,13 @@ private: LLEventTimer(0), mTask(std::forward<CALLABLE>(callable)) {} - BOOL tick() override + bool tick() override { // run the task on the main thread, will populate the future // obtained by get_future() mTask(); // tell LLEventTimer we're done (one shot) - return TRUE; + return true; } // Given arbitrary CALLABLE, which might be a lambda, how are we // supposed to obtain its signature for std::packaged_task? It seems diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index cf5ead718d..0e5b91c9b8 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -1,354 +1,354 @@ -/** - * @file llmemory.cpp - * @brief Very special memory allocation/deallocation stuff here - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - - -#include "llthread.h" - -#if defined(LL_WINDOWS) -# include <psapi.h> -#elif defined(LL_DARWIN) -# include <sys/types.h> -# include <mach/task.h> -# include <mach/mach_init.h> -#include <mach/mach_host.h> -#elif LL_LINUX -# include <unistd.h> -# include <sys/resource.h> -#endif - -#include "llmemory.h" - -#include "llsys.h" -#include "llframetimer.h" -#include "lltrace.h" -#include "llerror.h" -//---------------------------------------------------------------------------- - -//static -U32Kilobytes LLMemory::sAvailPhysicalMemInKB(U32_MAX); -U32Kilobytes LLMemory::sMaxPhysicalMemInKB(0); -static LLTrace::SampleStatHandle<F64Megabytes> sAllocatedMem("allocated_mem", "active memory in use by application"); -static LLTrace::SampleStatHandle<F64Megabytes> sVirtualMem("virtual_mem", "virtual memory assigned to application"); -U32Kilobytes LLMemory::sAllocatedMemInKB(0); -U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0); -U32Kilobytes LLMemory::sMaxHeapSizeInKB(U32_MAX); - -void ll_assert_aligned_func(uintptr_t ptr,U32 alignment) -{ -#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN) - //do not check - return; -#else - #ifdef SHOW_ASSERT - // Redundant, place to set breakpoints. - if (ptr%alignment!=0) - { - LL_WARNS() << "alignment check failed" << LL_ENDL; - } - llassert(ptr%alignment==0); - #endif -#endif -} - -//static -void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size) -{ - sMaxHeapSizeInKB = U32Kilobytes::convert(max_heap_size); -} - -//static -void LLMemory::updateMemoryInfo() -{ - LL_PROFILE_ZONE_SCOPED -#if LL_WINDOWS - PROCESS_MEMORY_COUNTERS counters; - - if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) - { - LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL; - return ; - } - - sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize)); - sample(sAllocatedMem, sAllocatedMemInKB); - sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage)); - sample(sVirtualMem, sAllocatedPageSizeInKB); - - U32Kilobytes avail_phys, avail_virtual; - LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ; - sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB); - - if(sMaxPhysicalMemInKB > sAllocatedMemInKB) - { - sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ; - } - else - { - sAvailPhysicalMemInKB = U32Kilobytes(0); - } - -#elif defined(LL_DARWIN) - task_vm_info info; - mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT; - // MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint. - if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&info), &infoCount) == KERN_SUCCESS) - { - // Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of - // memory that the memory manager has committed for a running process", which is rss. - sAllocatedPageSizeInKB = U32Bytes(info.resident_size); - - // Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size - // Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less. - // I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals. - // - // In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value - // reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint. - // - // (On Windows, we use WorkingSetSize.) - sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable); - } - else - { - LL_WARNS() << "task_info failed" << LL_ENDL; - } - - // Total installed and available physical memory are properties of the host, not just our process. - vm_statistics64_data_t vmstat; - mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; - mach_port_t host = mach_host_self(); - vm_size_t page_size; - host_page_size(host, &page_size); - kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count); - if (result == KERN_SUCCESS) { - // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.' - // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure. - sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size); - sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize(); - } - else - { - LL_WARNS() << "task_info failed" << LL_ENDL; - } - -#else - //not valid for other systems for now. - sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS()); - sMaxPhysicalMemInKB = U64Bytes(U32_MAX); - sAvailPhysicalMemInKB = U64Bytes(U32_MAX); -#endif - - return ; -} - -// -//this function is to test if there is enough space with the size in the virtual address space. -//it does not do any real allocation -//if success, it returns the address where the memory chunk can fit in; -//otherwise it returns NULL. -// -//static -void* LLMemory::tryToAlloc(void* address, U32 size) -{ -#if LL_WINDOWS - address = VirtualAlloc(address, size, MEM_RESERVE | MEM_TOP_DOWN, PAGE_NOACCESS) ; - if(address) - { - if(!VirtualFree(address, 0, MEM_RELEASE)) - { - LL_ERRS() << "error happens when free some memory reservation." << LL_ENDL ; - } - } - return address ; -#else - return (void*)0x01 ; //skip checking -#endif -} - -//static -void LLMemory::logMemoryInfo(BOOL update) -{ - LL_PROFILE_ZONE_SCOPED - if(update) - { - updateMemoryInfo() ; - } - - LL_INFOS() << "Current allocated physical memory(KB): " << sAllocatedMemInKB << LL_ENDL ; - LL_INFOS() << "Current allocated page size (KB): " << sAllocatedPageSizeInKB << LL_ENDL ; - LL_INFOS() << "Current available physical memory(KB): " << sAvailPhysicalMemInKB << LL_ENDL ; - LL_INFOS() << "Current max usable memory(KB): " << sMaxPhysicalMemInKB << LL_ENDL ; -} - -//static -U32Kilobytes LLMemory::getAvailableMemKB() -{ - return sAvailPhysicalMemInKB ; -} - -//static -U32Kilobytes LLMemory::getMaxMemKB() -{ - return sMaxPhysicalMemInKB ; -} - -//static -U32Kilobytes LLMemory::getAllocatedMemKB() -{ - return sAllocatedMemInKB ; -} - -//---------------------------------------------------------------------------- - -#if defined(LL_WINDOWS) - -//static -U64 LLMemory::getCurrentRSS() -{ - PROCESS_MEMORY_COUNTERS counters; - - if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) - { - LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL; - return 0; - } - - return counters.WorkingSetSize; -} - -#elif defined(LL_DARWIN) - -// if (sysctl(ctl, 2, &page_size, &size, NULL, 0) == -1) -// { -// LL_WARNS() << "Couldn't get page size" << LL_ENDL; -// return 0; -// } else { -// return page_size; -// } -// } - -U64 LLMemory::getCurrentRSS() -{ - U64 residentSize = 0; - mach_task_basic_info_data_t basicInfo; - mach_msg_type_number_t basicInfoCount = MACH_TASK_BASIC_INFO_COUNT; - if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&basicInfo, &basicInfoCount) == KERN_SUCCESS) - { - residentSize = basicInfo.resident_size; - // 64-bit macos apps allocate 32 GB or more at startup, and this is reflected in virtual_size. - // basicInfo.virtual_size is not what we want. - } - else - { - LL_WARNS() << "task_info failed" << LL_ENDL; - } - - return residentSize; -} - -#elif defined(LL_LINUX) - -U64 LLMemory::getCurrentRSS() -{ - struct rusage usage; - - if (getrusage(RUSAGE_SELF, &usage) != 0) { - // Error handling code could be here - return 0; - } - - // ru_maxrss (since Linux 2.6.32) - // This is the maximum resident set size used (in kilobytes). - return usage.ru_maxrss * 1024; -} - -#else - -U64 LLMemory::getCurrentRSS() -{ - return 0; -} - -#endif - -//-------------------------------------------------------------------- - -#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN) - -#include <map> - -struct mem_info { - std::map<void*, void*> memory_info; - LLMutex mutex; - - static mem_info& get() { - static mem_info instance; - return instance; - } - -private: - mem_info(){} -}; - -void* ll_aligned_malloc_fallback( size_t size, int align ) -{ - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - - unsigned int for_alloc = (size/sysinfo.dwPageSize + !!(size%sysinfo.dwPageSize)) * sysinfo.dwPageSize; - - void *p = VirtualAlloc(NULL, for_alloc+sysinfo.dwPageSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); - if(NULL == p) { - // call debugger - __asm int 3; - } - DWORD old; - BOOL Res = VirtualProtect((void*)((char*)p + for_alloc), sysinfo.dwPageSize, PAGE_NOACCESS, &old); - if(FALSE == Res) { - // call debugger - __asm int 3; - } - - void* ret = (void*)((char*)p + for_alloc-size); - - { - LLMutexLock lock(&mem_info::get().mutex); - mem_info::get().memory_info.insert(std::pair<void*, void*>(ret, p)); - } - - - return ret; -} - -void ll_aligned_free_fallback( void* ptr ) -{ - LLMutexLock lock(&mem_info::get().mutex); - VirtualFree(mem_info::get().memory_info.find(ptr)->second, 0, MEM_RELEASE); - mem_info::get().memory_info.erase(ptr); -} - -#endif +/**
+ * @file llmemory.cpp
+ * @brief Very special memory allocation/deallocation stuff here
+ *
+ * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+
+#include "llthread.h"
+
+#if defined(LL_WINDOWS)
+# include <psapi.h>
+#elif defined(LL_DARWIN)
+# include <sys/types.h>
+# include <mach/task.h>
+# include <mach/mach_init.h>
+#include <mach/mach_host.h>
+#elif LL_LINUX
+# include <unistd.h>
+# include <sys/resource.h>
+#endif
+
+#include "llmemory.h"
+
+#include "llsys.h"
+#include "llframetimer.h"
+#include "lltrace.h"
+#include "llerror.h"
+//----------------------------------------------------------------------------
+
+//static
+U32Kilobytes LLMemory::sAvailPhysicalMemInKB(U32_MAX);
+U32Kilobytes LLMemory::sMaxPhysicalMemInKB(0);
+static LLTrace::SampleStatHandle<F64Megabytes> sAllocatedMem("allocated_mem", "active memory in use by application");
+static LLTrace::SampleStatHandle<F64Megabytes> sVirtualMem("virtual_mem", "virtual memory assigned to application");
+U32Kilobytes LLMemory::sAllocatedMemInKB(0);
+U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0);
+U32Kilobytes LLMemory::sMaxHeapSizeInKB(U32_MAX);
+
+void ll_assert_aligned_func(uintptr_t ptr,U32 alignment)
+{
+#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN)
+ //do not check
+ return;
+#else
+ #ifdef SHOW_ASSERT
+ // Redundant, place to set breakpoints.
+ if (ptr%alignment!=0)
+ {
+ LL_WARNS() << "alignment check failed" << LL_ENDL;
+ }
+ llassert(ptr%alignment==0);
+ #endif
+#endif
+}
+
+//static
+void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size)
+{
+ sMaxHeapSizeInKB = U32Kilobytes::convert(max_heap_size);
+}
+
+//static
+void LLMemory::updateMemoryInfo()
+{
+ LL_PROFILE_ZONE_SCOPED
+#if LL_WINDOWS
+ PROCESS_MEMORY_COUNTERS counters;
+
+ if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters)))
+ {
+ LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL;
+ return ;
+ }
+
+ sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize));
+ sample(sAllocatedMem, sAllocatedMemInKB);
+ sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage));
+ sample(sVirtualMem, sAllocatedPageSizeInKB);
+
+ U32Kilobytes avail_phys, avail_virtual;
+ LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ;
+ sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB);
+
+ if(sMaxPhysicalMemInKB > sAllocatedMemInKB)
+ {
+ sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ;
+ }
+ else
+ {
+ sAvailPhysicalMemInKB = U32Kilobytes(0);
+ }
+
+#elif defined(LL_DARWIN)
+ task_vm_info info;
+ mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT;
+ // MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint.
+ if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&info), &infoCount) == KERN_SUCCESS)
+ {
+ // Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of
+ // memory that the memory manager has committed for a running process", which is rss.
+ sAllocatedPageSizeInKB = U32Bytes(info.resident_size);
+
+ // Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size
+ // Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less.
+ // I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals.
+ //
+ // In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value
+ // reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint.
+ //
+ // (On Windows, we use WorkingSetSize.)
+ sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable);
+ }
+ else
+ {
+ LL_WARNS() << "task_info failed" << LL_ENDL;
+ }
+
+ // Total installed and available physical memory are properties of the host, not just our process.
+ vm_statistics64_data_t vmstat;
+ mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
+ mach_port_t host = mach_host_self();
+ vm_size_t page_size;
+ host_page_size(host, &page_size);
+ kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count);
+ if (result == KERN_SUCCESS) {
+ // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.'
+ // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure.
+ sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size);
+ sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize();
+ }
+ else
+ {
+ LL_WARNS() << "task_info failed" << LL_ENDL;
+ }
+
+#else
+ //not valid for other systems for now.
+ sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS());
+ sMaxPhysicalMemInKB = U64Bytes(U32_MAX);
+ sAvailPhysicalMemInKB = U64Bytes(U32_MAX);
+#endif
+
+ return ;
+}
+
+//
+//this function is to test if there is enough space with the size in the virtual address space.
+//it does not do any real allocation
+//if success, it returns the address where the memory chunk can fit in;
+//otherwise it returns NULL.
+//
+//static
+void* LLMemory::tryToAlloc(void* address, U32 size)
+{
+#if LL_WINDOWS
+ address = VirtualAlloc(address, size, MEM_RESERVE | MEM_TOP_DOWN, PAGE_NOACCESS) ;
+ if(address)
+ {
+ if(!VirtualFree(address, 0, MEM_RELEASE))
+ {
+ LL_ERRS() << "error happens when free some memory reservation." << LL_ENDL ;
+ }
+ }
+ return address ;
+#else
+ return (void*)0x01 ; //skip checking
+#endif
+}
+
+//static
+void LLMemory::logMemoryInfo(bool update)
+{
+ LL_PROFILE_ZONE_SCOPED
+ if(update)
+ {
+ updateMemoryInfo() ;
+ }
+
+ LL_INFOS() << "Current allocated physical memory(KB): " << sAllocatedMemInKB << LL_ENDL ;
+ LL_INFOS() << "Current allocated page size (KB): " << sAllocatedPageSizeInKB << LL_ENDL ;
+ LL_INFOS() << "Current available physical memory(KB): " << sAvailPhysicalMemInKB << LL_ENDL ;
+ LL_INFOS() << "Current max usable memory(KB): " << sMaxPhysicalMemInKB << LL_ENDL ;
+}
+
+//static
+U32Kilobytes LLMemory::getAvailableMemKB()
+{
+ return sAvailPhysicalMemInKB ;
+}
+
+//static
+U32Kilobytes LLMemory::getMaxMemKB()
+{
+ return sMaxPhysicalMemInKB ;
+}
+
+//static
+U32Kilobytes LLMemory::getAllocatedMemKB()
+{
+ return sAllocatedMemInKB ;
+}
+
+//----------------------------------------------------------------------------
+
+#if defined(LL_WINDOWS)
+
+//static
+U64 LLMemory::getCurrentRSS()
+{
+ PROCESS_MEMORY_COUNTERS counters;
+
+ if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters)))
+ {
+ LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL;
+ return 0;
+ }
+
+ return counters.WorkingSetSize;
+}
+
+#elif defined(LL_DARWIN)
+
+// if (sysctl(ctl, 2, &page_size, &size, NULL, 0) == -1)
+// {
+// LL_WARNS() << "Couldn't get page size" << LL_ENDL;
+// return 0;
+// } else {
+// return page_size;
+// }
+// }
+
+U64 LLMemory::getCurrentRSS()
+{
+ U64 residentSize = 0;
+ mach_task_basic_info_data_t basicInfo;
+ mach_msg_type_number_t basicInfoCount = MACH_TASK_BASIC_INFO_COUNT;
+ if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&basicInfo, &basicInfoCount) == KERN_SUCCESS)
+ {
+ residentSize = basicInfo.resident_size;
+ // 64-bit macos apps allocate 32 GB or more at startup, and this is reflected in virtual_size.
+ // basicInfo.virtual_size is not what we want.
+ }
+ else
+ {
+ LL_WARNS() << "task_info failed" << LL_ENDL;
+ }
+
+ return residentSize;
+}
+
+#elif defined(LL_LINUX)
+
+U64 LLMemory::getCurrentRSS()
+{
+ struct rusage usage;
+
+ if (getrusage(RUSAGE_SELF, &usage) != 0) {
+ // Error handling code could be here
+ return 0;
+ }
+
+ // ru_maxrss (since Linux 2.6.32)
+ // This is the maximum resident set size used (in kilobytes).
+ return usage.ru_maxrss * 1024;
+}
+
+#else
+
+U64 LLMemory::getCurrentRSS()
+{
+ return 0;
+}
+
+#endif
+
+//--------------------------------------------------------------------
+
+#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN)
+
+#include <map>
+
+struct mem_info {
+ std::map<void*, void*> memory_info;
+ LLMutex mutex;
+
+ static mem_info& get() {
+ static mem_info instance;
+ return instance;
+ }
+
+private:
+ mem_info(){}
+};
+
+void* ll_aligned_malloc_fallback( size_t size, int align )
+{
+ SYSTEM_INFO sysinfo;
+ GetSystemInfo(&sysinfo);
+
+ unsigned int for_alloc = (size/sysinfo.dwPageSize + !!(size%sysinfo.dwPageSize)) * sysinfo.dwPageSize;
+
+ void *p = VirtualAlloc(NULL, for_alloc+sysinfo.dwPageSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
+ if(NULL == p) {
+ // call debugger
+ __asm int 3;
+ }
+ DWORD old;
+ bool Res = VirtualProtect((void*)((char*)p + for_alloc), sysinfo.dwPageSize, PAGE_NOACCESS, &old);
+ if(false == Res) {
+ // call debugger
+ __asm int 3;
+ }
+
+ void* ret = (void*)((char*)p + for_alloc-size);
+
+ {
+ LLMutexLock lock(&mem_info::get().mutex);
+ mem_info::get().memory_info.insert(std::pair<void*, void*>(ret, p));
+ }
+
+
+ return ret;
+}
+
+void ll_aligned_free_fallback( void* ptr )
+{
+ LLMutexLock lock(&mem_info::get().mutex);
+ VirtualFree(mem_info::get().memory_info.find(ptr)->second, 0, MEM_RELEASE);
+ mem_info::get().memory_info.erase(ptr);
+}
+
+#endif
diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index 313c380587..ea360881c6 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -1,418 +1,418 @@ -/** - * @file llmemory.h - * @brief Memory allocation/deallocation header-stuff goes here. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 LLMEMORY_H -#define LLMEMORY_H - -#include "linden_common.h" -#include "llunits.h" -#include "stdtypes.h" -#if !LL_WINDOWS -#include <stdint.h> -#endif - -class LLMutex ; - -#if LL_WINDOWS && LL_DEBUG -#define LL_CHECK_MEMORY llassert(_CrtCheckMemory()); -#else -#define LL_CHECK_MEMORY -#endif - - -#if LL_WINDOWS -#define LL_ALIGN_OF __alignof -#else -#define LL_ALIGN_OF __align_of__ -#endif - -#if LL_WINDOWS -#define LL_DEFAULT_HEAP_ALIGN 8 -#elif LL_DARWIN -#define LL_DEFAULT_HEAP_ALIGN 16 -#elif LL_LINUX -#define LL_DEFAULT_HEAP_ALIGN 8 -#endif - - -LL_COMMON_API void ll_assert_aligned_func(uintptr_t ptr,U32 alignment); - -#ifdef SHOW_ASSERT -// This is incredibly expensive - in profiling Windows RWD builds, 30% -// of CPU time was in aligment checks. -//#define ASSERT_ALIGNMENT -#endif - -#ifdef ASSERT_ALIGNMENT -#define ll_assert_aligned(ptr,alignment) ll_assert_aligned_func(uintptr_t(ptr),((U32)alignment)) -#else -#define ll_assert_aligned(ptr,alignment) -#endif - -#include <xmmintrin.h> - -template <typename T> T* LL_NEXT_ALIGNED_ADDRESS(T* address) -{ - return reinterpret_cast<T*>( - (uintptr_t(address) + 0xF) & ~0xF); -} - -template <typename T> T* LL_NEXT_ALIGNED_ADDRESS_64(T* address) -{ - return reinterpret_cast<T*>( - (uintptr_t(address) + 0x3F) & ~0x3F); -} - -#if LL_LINUX || LL_DARWIN - -#define LL_ALIGN_PREFIX(x) -#define LL_ALIGN_POSTFIX(x) __attribute__((aligned(x))) - -#elif LL_WINDOWS - -#define LL_ALIGN_PREFIX(x) __declspec(align(x)) -#define LL_ALIGN_POSTFIX(x) - -#else -#error "LL_ALIGN_PREFIX and LL_ALIGN_POSTFIX undefined" -#endif - -#define LL_ALIGN_16(var) LL_ALIGN_PREFIX(16) var LL_ALIGN_POSTFIX(16) - -#define LL_ALIGN_NEW \ -public: \ - void* operator new(size_t size) \ - { \ - return ll_aligned_malloc_16(size); \ - } \ - \ - void operator delete(void* ptr) \ - { \ - ll_aligned_free_16(ptr); \ - } \ - \ - void* operator new[](size_t size) \ - { \ - return ll_aligned_malloc_16(size); \ - } \ - \ - void operator delete[](void* ptr) \ - { \ - ll_aligned_free_16(ptr); \ - } - - -//------------------------------------------------------------------------------------------------ -//------------------------------------------------------------------------------------------------ - // for enable buffer overrun detection predefine LL_DEBUG_BUFFER_OVERRUN in current library - // change preprocessor code to: #if 1 && defined(LL_WINDOWS) - -#if 0 && defined(LL_WINDOWS) - void* ll_aligned_malloc_fallback( size_t size, int align ); - void ll_aligned_free_fallback( void* ptr ); -//------------------------------------------------------------------------------------------------ -#else - inline void* ll_aligned_malloc_fallback( size_t size, int align ) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - #if defined(LL_WINDOWS) - void* ret = _aligned_malloc(size, align); - #else - char* aligned = NULL; - void* mem = malloc( size + (align - 1) + sizeof(void*) ); - if (mem) - { - aligned = ((char*)mem) + sizeof(void*); - aligned += align - ((uintptr_t)aligned & (align - 1)); - - ((void**)aligned)[-1] = mem; - } - void* ret = aligned; - #endif - LL_PROFILE_ALLOC(ret, size); - return ret; - } - - inline void ll_aligned_free_fallback( void* ptr ) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - LL_PROFILE_FREE(ptr); - #if defined(LL_WINDOWS) - _aligned_free(ptr); - #else - if (ptr) - { - free( ((void**)ptr)[-1] ); - } - #endif - } -#endif -//------------------------------------------------------------------------------------------------ -//------------------------------------------------------------------------------------------------ - -inline void* ll_aligned_malloc_16(size_t size) // returned hunk MUST be freed with ll_aligned_free_16(). -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; -#if defined(LL_WINDOWS) - void* ret = _aligned_malloc(size, 16); -#elif defined(LL_DARWIN) - void* ret = malloc(size); // default osx malloc is 16 byte aligned. -#else - void *ret; - if (0 != posix_memalign(&ret, 16, size)) - return nullptr; -#endif - LL_PROFILE_ALLOC(ret, size); - return ret; -} - -inline void ll_aligned_free_16(void *p) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - LL_PROFILE_FREE(p); -#if defined(LL_WINDOWS) - _aligned_free(p); -#elif defined(LL_DARWIN) - return free(p); -#else - free(p); // posix_memalign() is compatible with heap deallocator -#endif -} - -inline void* ll_aligned_realloc_16(void* ptr, size_t size, size_t old_size) // returned hunk MUST be freed with ll_aligned_free_16(). -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - LL_PROFILE_FREE(ptr); -#if defined(LL_WINDOWS) - void* ret = _aligned_realloc(ptr, size, 16); -#elif defined(LL_DARWIN) - void* ret = realloc(ptr,size); // default osx malloc is 16 byte aligned. -#else - //FIXME: memcpy is SLOW - void* ret = ll_aligned_malloc_16(size); - if (ptr) - { - if (ret) - { - // Only copy the size of the smallest memory block to avoid memory corruption. - memcpy(ret, ptr, llmin(old_size, size)); - } - ll_aligned_free_16(ptr); - } -#endif - LL_PROFILE_ALLOC(ptr, size); - return ret; -} - -inline void* ll_aligned_malloc_32(size_t size) // returned hunk MUST be freed with ll_aligned_free_32(). -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; -#if defined(LL_WINDOWS) - void* ret = _aligned_malloc(size, 32); -#elif defined(LL_DARWIN) - void* ret = ll_aligned_malloc_fallback( size, 32 ); -#else - void *ret; - if (0 != posix_memalign(&ret, 32, size)) - return nullptr; -#endif - LL_PROFILE_ALLOC(ret, size); - return ret; -} - -inline void ll_aligned_free_32(void *p) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - LL_PROFILE_FREE(p); -#if defined(LL_WINDOWS) - _aligned_free(p); -#elif defined(LL_DARWIN) - ll_aligned_free_fallback( p ); -#else - free(p); // posix_memalign() is compatible with heap deallocator -#endif -} - -// general purpose dispatch functions that are forced inline so they can compile down to a single call -template<size_t ALIGNMENT> -LL_FORCE_INLINE void* ll_aligned_malloc(size_t size) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - void* ret; - if (LL_DEFAULT_HEAP_ALIGN % ALIGNMENT == 0) - { - ret = malloc(size); - LL_PROFILE_ALLOC(ret, size); - } - else if (ALIGNMENT == 16) - { - ret = ll_aligned_malloc_16(size); - } - else if (ALIGNMENT == 32) - { - ret = ll_aligned_malloc_32(size); - } - else - { - ret = ll_aligned_malloc_fallback(size, ALIGNMENT); - } - return ret; -} - -template<size_t ALIGNMENT> -LL_FORCE_INLINE void ll_aligned_free(void* ptr) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - if (ALIGNMENT == LL_DEFAULT_HEAP_ALIGN) - { - LL_PROFILE_FREE(ptr); - free(ptr); - } - else if (ALIGNMENT == 16) - { - ll_aligned_free_16(ptr); - } - else if (ALIGNMENT == 32) - { - return ll_aligned_free_32(ptr); - } - else - { - return ll_aligned_free_fallback(ptr); - } -} - -// Copy words 16-byte blocks from src to dst. Source and destination MUST NOT OVERLAP. -// Source and dest must be 16-byte aligned and size must be multiple of 16. -// -inline void ll_memcpy_nonaliased_aligned_16(char* __restrict dst, const char* __restrict src, size_t bytes) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; - assert(src != NULL); - assert(dst != NULL); - assert(bytes > 0); - assert((bytes % sizeof(F32))== 0); - ll_assert_aligned(src,16); - ll_assert_aligned(dst,16); - - assert((src < dst) ? ((src + bytes) <= dst) : ((dst + bytes) <= src)); - assert(bytes%16==0); - - char* end = dst + bytes; - - if (bytes > 64) - { - - // Find start of 64b aligned area within block - // - void* begin_64 = LL_NEXT_ALIGNED_ADDRESS_64(dst); - - //at least 64 bytes before the end of the destination, switch to 16 byte copies - void* end_64 = end-64; - - // Prefetch the head of the 64b area now - // - _mm_prefetch((char*)begin_64, _MM_HINT_NTA); - _mm_prefetch((char*)begin_64 + 64, _MM_HINT_NTA); - _mm_prefetch((char*)begin_64 + 128, _MM_HINT_NTA); - _mm_prefetch((char*)begin_64 + 192, _MM_HINT_NTA); - - // Copy 16b chunks until we're 64b aligned - // - while (dst < begin_64) - { - - _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); - dst += 16; - src += 16; - } - - // Copy 64b chunks up to your tail - // - // might be good to shmoo the 512b prefetch offset - // (characterize performance for various values) - // - while (dst < end_64) - { - _mm_prefetch((char*)src + 512, _MM_HINT_NTA); - _mm_prefetch((char*)dst + 512, _MM_HINT_NTA); - _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); - _mm_store_ps((F32*)(dst + 16), _mm_load_ps((F32*)(src + 16))); - _mm_store_ps((F32*)(dst + 32), _mm_load_ps((F32*)(src + 32))); - _mm_store_ps((F32*)(dst + 48), _mm_load_ps((F32*)(src + 48))); - dst += 64; - src += 64; - } - } - - // Copy remainder 16b tail chunks (or ALL 16b chunks for sub-64b copies) - // - while (dst < end) - { - _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); - dst += 16; - src += 16; - } -} - -#ifndef __DEBUG_PRIVATE_MEM__ -#define __DEBUG_PRIVATE_MEM__ 0 -#endif - -class LL_COMMON_API LLMemory -{ -public: - // Return the resident set size of the current process, in bytes. - // Return value is zero if not known. - static U64 getCurrentRSS(); - static void* tryToAlloc(void* address, U32 size); - static void initMaxHeapSizeGB(F32Gigabytes max_heap_size); - static void updateMemoryInfo() ; - static void logMemoryInfo(BOOL update = FALSE); - - static U32Kilobytes getAvailableMemKB() ; - static U32Kilobytes getMaxMemKB() ; - static U32Kilobytes getAllocatedMemKB() ; -private: - static U32Kilobytes sAvailPhysicalMemInKB ; - static U32Kilobytes sMaxPhysicalMemInKB ; - static U32Kilobytes sAllocatedMemInKB; - static U32Kilobytes sAllocatedPageSizeInKB ; - - static U32Kilobytes sMaxHeapSizeInKB; -}; - -// LLRefCount moved to llrefcount.h - -// LLPointer moved to llpointer.h - -// LLSafeHandle moved to llsafehandle.h - -// LLSingleton moved to llsingleton.h - - - - -#endif +/**
+ * @file llmemory.h
+ * @brief Memory allocation/deallocation header-stuff goes here.
+ *
+ * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 LLMEMORY_H
+#define LLMEMORY_H
+
+#include "linden_common.h"
+#include "llunits.h"
+#include "stdtypes.h"
+#if !LL_WINDOWS
+#include <stdint.h>
+#endif
+
+class LLMutex ;
+
+#if LL_WINDOWS && LL_DEBUG
+#define LL_CHECK_MEMORY llassert(_CrtCheckMemory());
+#else
+#define LL_CHECK_MEMORY
+#endif
+
+
+#if LL_WINDOWS
+#define LL_ALIGN_OF __alignof
+#else
+#define LL_ALIGN_OF __align_of__
+#endif
+
+#if LL_WINDOWS
+#define LL_DEFAULT_HEAP_ALIGN 8
+#elif LL_DARWIN
+#define LL_DEFAULT_HEAP_ALIGN 16
+#elif LL_LINUX
+#define LL_DEFAULT_HEAP_ALIGN 8
+#endif
+
+
+LL_COMMON_API void ll_assert_aligned_func(uintptr_t ptr,U32 alignment);
+
+#ifdef SHOW_ASSERT
+// This is incredibly expensive - in profiling Windows RWD builds, 30%
+// of CPU time was in aligment checks.
+//#define ASSERT_ALIGNMENT
+#endif
+
+#ifdef ASSERT_ALIGNMENT
+#define ll_assert_aligned(ptr,alignment) ll_assert_aligned_func(uintptr_t(ptr),((U32)alignment))
+#else
+#define ll_assert_aligned(ptr,alignment)
+#endif
+
+#include <xmmintrin.h>
+
+template <typename T> T* LL_NEXT_ALIGNED_ADDRESS(T* address)
+{
+ return reinterpret_cast<T*>(
+ (uintptr_t(address) + 0xF) & ~0xF);
+}
+
+template <typename T> T* LL_NEXT_ALIGNED_ADDRESS_64(T* address)
+{
+ return reinterpret_cast<T*>(
+ (uintptr_t(address) + 0x3F) & ~0x3F);
+}
+
+#if LL_LINUX || LL_DARWIN
+
+#define LL_ALIGN_PREFIX(x)
+#define LL_ALIGN_POSTFIX(x) __attribute__((aligned(x)))
+
+#elif LL_WINDOWS
+
+#define LL_ALIGN_PREFIX(x) __declspec(align(x))
+#define LL_ALIGN_POSTFIX(x)
+
+#else
+#error "LL_ALIGN_PREFIX and LL_ALIGN_POSTFIX undefined"
+#endif
+
+#define LL_ALIGN_16(var) LL_ALIGN_PREFIX(16) var LL_ALIGN_POSTFIX(16)
+
+#define LL_ALIGN_NEW \
+public: \
+ void* operator new(size_t size) \
+ { \
+ return ll_aligned_malloc_16(size); \
+ } \
+ \
+ void operator delete(void* ptr) \
+ { \
+ ll_aligned_free_16(ptr); \
+ } \
+ \
+ void* operator new[](size_t size) \
+ { \
+ return ll_aligned_malloc_16(size); \
+ } \
+ \
+ void operator delete[](void* ptr) \
+ { \
+ ll_aligned_free_16(ptr); \
+ }
+
+
+//------------------------------------------------------------------------------------------------
+//------------------------------------------------------------------------------------------------
+ // for enable buffer overrun detection predefine LL_DEBUG_BUFFER_OVERRUN in current library
+ // change preprocessor code to: #if 1 && defined(LL_WINDOWS)
+
+#if 0 && defined(LL_WINDOWS)
+ void* ll_aligned_malloc_fallback( size_t size, int align );
+ void ll_aligned_free_fallback( void* ptr );
+//------------------------------------------------------------------------------------------------
+#else
+ inline void* ll_aligned_malloc_fallback( size_t size, int align )
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ #if defined(LL_WINDOWS)
+ void* ret = _aligned_malloc(size, align);
+ #else
+ char* aligned = NULL;
+ void* mem = malloc( size + (align - 1) + sizeof(void*) );
+ if (mem)
+ {
+ aligned = ((char*)mem) + sizeof(void*);
+ aligned += align - ((uintptr_t)aligned & (align - 1));
+
+ ((void**)aligned)[-1] = mem;
+ }
+ void* ret = aligned;
+ #endif
+ LL_PROFILE_ALLOC(ret, size);
+ return ret;
+ }
+
+ inline void ll_aligned_free_fallback( void* ptr )
+ {
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ LL_PROFILE_FREE(ptr);
+ #if defined(LL_WINDOWS)
+ _aligned_free(ptr);
+ #else
+ if (ptr)
+ {
+ free( ((void**)ptr)[-1] );
+ }
+ #endif
+ }
+#endif
+//------------------------------------------------------------------------------------------------
+//------------------------------------------------------------------------------------------------
+
+inline void* ll_aligned_malloc_16(size_t size) // returned hunk MUST be freed with ll_aligned_free_16().
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+#if defined(LL_WINDOWS)
+ void* ret = _aligned_malloc(size, 16);
+#elif defined(LL_DARWIN)
+ void* ret = malloc(size); // default osx malloc is 16 byte aligned.
+#else
+ void *ret;
+ if (0 != posix_memalign(&ret, 16, size))
+ return nullptr;
+#endif
+ LL_PROFILE_ALLOC(ret, size);
+ return ret;
+}
+
+inline void ll_aligned_free_16(void *p)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ LL_PROFILE_FREE(p);
+#if defined(LL_WINDOWS)
+ _aligned_free(p);
+#elif defined(LL_DARWIN)
+ return free(p);
+#else
+ free(p); // posix_memalign() is compatible with heap deallocator
+#endif
+}
+
+inline void* ll_aligned_realloc_16(void* ptr, size_t size, size_t old_size) // returned hunk MUST be freed with ll_aligned_free_16().
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ LL_PROFILE_FREE(ptr);
+#if defined(LL_WINDOWS)
+ void* ret = _aligned_realloc(ptr, size, 16);
+#elif defined(LL_DARWIN)
+ void* ret = realloc(ptr,size); // default osx malloc is 16 byte aligned.
+#else
+ //FIXME: memcpy is SLOW
+ void* ret = ll_aligned_malloc_16(size);
+ if (ptr)
+ {
+ if (ret)
+ {
+ // Only copy the size of the smallest memory block to avoid memory corruption.
+ memcpy(ret, ptr, llmin(old_size, size));
+ }
+ ll_aligned_free_16(ptr);
+ }
+#endif
+ LL_PROFILE_ALLOC(ptr, size);
+ return ret;
+}
+
+inline void* ll_aligned_malloc_32(size_t size) // returned hunk MUST be freed with ll_aligned_free_32().
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+#if defined(LL_WINDOWS)
+ void* ret = _aligned_malloc(size, 32);
+#elif defined(LL_DARWIN)
+ void* ret = ll_aligned_malloc_fallback( size, 32 );
+#else
+ void *ret;
+ if (0 != posix_memalign(&ret, 32, size))
+ return nullptr;
+#endif
+ LL_PROFILE_ALLOC(ret, size);
+ return ret;
+}
+
+inline void ll_aligned_free_32(void *p)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ LL_PROFILE_FREE(p);
+#if defined(LL_WINDOWS)
+ _aligned_free(p);
+#elif defined(LL_DARWIN)
+ ll_aligned_free_fallback( p );
+#else
+ free(p); // posix_memalign() is compatible with heap deallocator
+#endif
+}
+
+// general purpose dispatch functions that are forced inline so they can compile down to a single call
+template<size_t ALIGNMENT>
+LL_FORCE_INLINE void* ll_aligned_malloc(size_t size)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ void* ret;
+ if (LL_DEFAULT_HEAP_ALIGN % ALIGNMENT == 0)
+ {
+ ret = malloc(size);
+ LL_PROFILE_ALLOC(ret, size);
+ }
+ else if (ALIGNMENT == 16)
+ {
+ ret = ll_aligned_malloc_16(size);
+ }
+ else if (ALIGNMENT == 32)
+ {
+ ret = ll_aligned_malloc_32(size);
+ }
+ else
+ {
+ ret = ll_aligned_malloc_fallback(size, ALIGNMENT);
+ }
+ return ret;
+}
+
+template<size_t ALIGNMENT>
+LL_FORCE_INLINE void ll_aligned_free(void* ptr)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ if (ALIGNMENT == LL_DEFAULT_HEAP_ALIGN)
+ {
+ LL_PROFILE_FREE(ptr);
+ free(ptr);
+ }
+ else if (ALIGNMENT == 16)
+ {
+ ll_aligned_free_16(ptr);
+ }
+ else if (ALIGNMENT == 32)
+ {
+ return ll_aligned_free_32(ptr);
+ }
+ else
+ {
+ return ll_aligned_free_fallback(ptr);
+ }
+}
+
+// Copy words 16-byte blocks from src to dst. Source and destination MUST NOT OVERLAP.
+// Source and dest must be 16-byte aligned and size must be multiple of 16.
+//
+inline void ll_memcpy_nonaliased_aligned_16(char* __restrict dst, const char* __restrict src, size_t bytes)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
+ assert(src != NULL);
+ assert(dst != NULL);
+ assert(bytes > 0);
+ assert((bytes % sizeof(F32))== 0);
+ ll_assert_aligned(src,16);
+ ll_assert_aligned(dst,16);
+
+ assert((src < dst) ? ((src + bytes) <= dst) : ((dst + bytes) <= src));
+ assert(bytes%16==0);
+
+ char* end = dst + bytes;
+
+ if (bytes > 64)
+ {
+
+ // Find start of 64b aligned area within block
+ //
+ void* begin_64 = LL_NEXT_ALIGNED_ADDRESS_64(dst);
+
+ //at least 64 bytes before the end of the destination, switch to 16 byte copies
+ void* end_64 = end-64;
+
+ // Prefetch the head of the 64b area now
+ //
+ _mm_prefetch((char*)begin_64, _MM_HINT_NTA);
+ _mm_prefetch((char*)begin_64 + 64, _MM_HINT_NTA);
+ _mm_prefetch((char*)begin_64 + 128, _MM_HINT_NTA);
+ _mm_prefetch((char*)begin_64 + 192, _MM_HINT_NTA);
+
+ // Copy 16b chunks until we're 64b aligned
+ //
+ while (dst < begin_64)
+ {
+
+ _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src));
+ dst += 16;
+ src += 16;
+ }
+
+ // Copy 64b chunks up to your tail
+ //
+ // might be good to shmoo the 512b prefetch offset
+ // (characterize performance for various values)
+ //
+ while (dst < end_64)
+ {
+ _mm_prefetch((char*)src + 512, _MM_HINT_NTA);
+ _mm_prefetch((char*)dst + 512, _MM_HINT_NTA);
+ _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src));
+ _mm_store_ps((F32*)(dst + 16), _mm_load_ps((F32*)(src + 16)));
+ _mm_store_ps((F32*)(dst + 32), _mm_load_ps((F32*)(src + 32)));
+ _mm_store_ps((F32*)(dst + 48), _mm_load_ps((F32*)(src + 48)));
+ dst += 64;
+ src += 64;
+ }
+ }
+
+ // Copy remainder 16b tail chunks (or ALL 16b chunks for sub-64b copies)
+ //
+ while (dst < end)
+ {
+ _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src));
+ dst += 16;
+ src += 16;
+ }
+}
+
+#ifndef __DEBUG_PRIVATE_MEM__
+#define __DEBUG_PRIVATE_MEM__ 0
+#endif
+
+class LL_COMMON_API LLMemory
+{
+public:
+ // Return the resident set size of the current process, in bytes.
+ // Return value is zero if not known.
+ static U64 getCurrentRSS();
+ static void* tryToAlloc(void* address, U32 size);
+ static void initMaxHeapSizeGB(F32Gigabytes max_heap_size);
+ static void updateMemoryInfo() ;
+ static void logMemoryInfo(bool update = false);
+
+ static U32Kilobytes getAvailableMemKB() ;
+ static U32Kilobytes getMaxMemKB() ;
+ static U32Kilobytes getAllocatedMemKB() ;
+private:
+ static U32Kilobytes sAvailPhysicalMemInKB ;
+ static U32Kilobytes sMaxPhysicalMemInKB ;
+ static U32Kilobytes sAllocatedMemInKB;
+ static U32Kilobytes sAllocatedPageSizeInKB ;
+
+ static U32Kilobytes sMaxHeapSizeInKB;
+};
+
+// LLRefCount moved to llrefcount.h
+
+// LLPointer moved to llpointer.h
+
+// LLSafeHandle moved to llsafehandle.h
+
+// LLSingleton moved to llsingleton.h
+
+
+
+
+#endif
diff --git a/indra/llcommon/llmetricperformancetester.cpp b/indra/llcommon/llmetricperformancetester.cpp index e05c2558f6..addee9fdbf 100644 --- a/indra/llcommon/llmetricperformancetester.cpp +++ b/indra/llcommon/llmetricperformancetester.cpp @@ -1,333 +1,333 @@ -/** - * @file llmetricperformancetester.cpp - * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "indra_constants.h" -#include "llerror.h" -#include "llsdserialize.h" -#include "lltreeiterators.h" -#include "llmetricperformancetester.h" -#include "llfasttimer.h" - -//---------------------------------------------------------------------------------------------- -// LLMetricPerformanceTesterBasic : static methods and testers management -//---------------------------------------------------------------------------------------------- - -LLMetricPerformanceTesterBasic::name_tester_map_t LLMetricPerformanceTesterBasic::sTesterMap ; - -/*static*/ -void LLMetricPerformanceTesterBasic::cleanupClass() -{ - for (name_tester_map_t::value_type& pair : sTesterMap) - { - delete pair.second; - } - sTesterMap.clear() ; -} - -/*static*/ -BOOL LLMetricPerformanceTesterBasic::addTester(LLMetricPerformanceTesterBasic* tester) -{ - llassert_always(tester != NULL); - std::string name = tester->getTesterName() ; - if (getTester(name)) - { - LL_ERRS() << "Tester name is already used by some other tester : " << name << LL_ENDL ; - return FALSE; - } - - sTesterMap.insert(std::make_pair(name, tester)); - return TRUE; -} - -/*static*/ -void LLMetricPerformanceTesterBasic::deleteTester(std::string name) -{ - name_tester_map_t::iterator tester = sTesterMap.find(name); - if (tester != sTesterMap.end()) - { - delete tester->second; - sTesterMap.erase(tester); - } -} - -/*static*/ -LLMetricPerformanceTesterBasic* LLMetricPerformanceTesterBasic::getTester(std::string name) -{ - // Check for the requested metric name - name_tester_map_t::iterator found_it = sTesterMap.find(name) ; - if (found_it != sTesterMap.end()) - { - return found_it->second ; - } - return NULL ; -} - -/*static*/ -// Return TRUE if this metric is requested or if the general default "catch all" metric is requested -BOOL LLMetricPerformanceTesterBasic::isMetricLogRequested(std::string name) -{ - return (LLTrace::BlockTimer::sMetricLog && ((LLTrace::BlockTimer::sLogName == name) || (LLTrace::BlockTimer::sLogName == DEFAULT_METRIC_NAME))); -} - -/*static*/ -LLSD LLMetricPerformanceTesterBasic::analyzeMetricPerformanceLog(std::istream& is) -{ - LLSD ret; - LLSD cur; - - while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is)) - { - for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter) - { - std::string label = iter->first; - - LLMetricPerformanceTesterBasic* tester = LLMetricPerformanceTesterBasic::getTester(iter->second["Name"].asString()) ; - if(tester) - { - ret[label]["Name"] = iter->second["Name"] ; - - auto num_of_metrics = tester->getNumberOfMetrics() ; - for(size_t index = 0 ; index < num_of_metrics ; index++) - { - ret[label][ tester->getMetricName(index) ] = iter->second[ tester->getMetricName(index) ] ; - } - } - } - } - - return ret; -} - -/*static*/ -void LLMetricPerformanceTesterBasic::doAnalysisMetrics(std::string baseline, std::string target, std::string output) -{ - if(!LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters()) - { - return ; - } - - // Open baseline and current target, exit if one is inexistent - llifstream base_is(baseline.c_str()); - llifstream target_is(target.c_str()); - if (!base_is.is_open() || !target_is.is_open()) - { - LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL; - base_is.close(); - target_is.close(); - return; - } - - //analyze baseline - LLSD base = analyzeMetricPerformanceLog(base_is); - base_is.close(); - - //analyze current - LLSD current = analyzeMetricPerformanceLog(target_is); - target_is.close(); - - //output comparision - llofstream os(output.c_str()); - - os << "Label, Metric, Base(B), Target(T), Diff(T-B), Percentage(100*T/B)\n"; - for (LLMetricPerformanceTesterBasic::name_tester_map_t::value_type& pair : LLMetricPerformanceTesterBasic::sTesterMap) - { - LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)pair.second); - tester->analyzePerformance(&os, &base, ¤t) ; - } - - os.flush(); - os.close(); -} - - -//---------------------------------------------------------------------------------------------- -// LLMetricPerformanceTesterBasic : Tester instance methods -//---------------------------------------------------------------------------------------------- - -LLMetricPerformanceTesterBasic::LLMetricPerformanceTesterBasic(std::string name) : - mName(name), - mCount(0) -{ - if (mName == std::string()) - { - LL_ERRS() << "LLMetricPerformanceTesterBasic construction invalid : Empty name passed to constructor" << LL_ENDL ; - } - - mValidInstance = LLMetricPerformanceTesterBasic::addTester(this) ; -} - -LLMetricPerformanceTesterBasic::~LLMetricPerformanceTesterBasic() -{ -} - -void LLMetricPerformanceTesterBasic::preOutputTestResults(LLSD* sd) -{ - incrementCurrentCount() ; -} - -void LLMetricPerformanceTesterBasic::postOutputTestResults(LLSD* sd) -{ - LLTrace::BlockTimer::pushLog(*sd); -} - -void LLMetricPerformanceTesterBasic::outputTestResults() -{ - LLSD sd; - - preOutputTestResults(&sd) ; - outputTestRecord(&sd) ; - postOutputTestResults(&sd) ; -} - -void LLMetricPerformanceTesterBasic::addMetric(std::string str) -{ - mMetricStrings.push_back(str) ; -} - -/*virtual*/ -void LLMetricPerformanceTesterBasic::analyzePerformance(llofstream* os, LLSD* base, LLSD* current) -{ - resetCurrentCount() ; - - std::string current_label = getCurrentLabelName(); - BOOL in_base = (*base).has(current_label) ; - BOOL in_current = (*current).has(current_label) ; - - while(in_base || in_current) - { - LLSD::String label = current_label ; - - if(in_base && in_current) - { - *os << llformat("%s\n", label.c_str()) ; - - for(U32 index = 0 ; index < mMetricStrings.size() ; index++) - { - switch((*current)[label][ mMetricStrings[index] ].type()) - { - case LLSD::TypeInteger: - compareTestResults(os, mMetricStrings[index], - (S32)((*base)[label][ mMetricStrings[index] ].asInteger()), (S32)((*current)[label][ mMetricStrings[index] ].asInteger())) ; - break ; - case LLSD::TypeReal: - compareTestResults(os, mMetricStrings[index], - (F32)((*base)[label][ mMetricStrings[index] ].asReal()), (F32)((*current)[label][ mMetricStrings[index] ].asReal())) ; - break; - default: - LL_ERRS() << "unsupported metric " << mMetricStrings[index] << " LLSD type: " << (S32)(*current)[label][ mMetricStrings[index] ].type() << LL_ENDL ; - } - } - } - - incrementCurrentCount(); - current_label = getCurrentLabelName(); - in_base = (*base).has(current_label) ; - in_current = (*current).has(current_label) ; - } -} - -/*virtual*/ -void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current) -{ - *os << llformat(" ,%s, %d, %d, %d, %.4f\n", metric_string.c_str(), v_base, v_current, - v_current - v_base, (v_base != 0) ? 100.f * v_current / v_base : 0) ; -} - -/*virtual*/ -void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current) -{ - *os << llformat(" ,%s, %.4f, %.4f, %.4f, %.4f\n", metric_string.c_str(), v_base, v_current, - v_current - v_base, (fabs(v_base) > 0.0001f) ? 100.f * v_current / v_base : 0.f ) ; -} - -//---------------------------------------------------------------------------------------------- -// LLMetricPerformanceTesterWithSession -//---------------------------------------------------------------------------------------------- - -LLMetricPerformanceTesterWithSession::LLMetricPerformanceTesterWithSession(std::string name) : - LLMetricPerformanceTesterBasic(name), - mBaseSessionp(NULL), - mCurrentSessionp(NULL) -{ -} - -LLMetricPerformanceTesterWithSession::~LLMetricPerformanceTesterWithSession() -{ - if (mBaseSessionp) - { - delete mBaseSessionp ; - mBaseSessionp = NULL ; - } - if (mCurrentSessionp) - { - delete mCurrentSessionp ; - mCurrentSessionp = NULL ; - } -} - -/*virtual*/ -void LLMetricPerformanceTesterWithSession::analyzePerformance(llofstream* os, LLSD* base, LLSD* current) -{ - // Load the base session - resetCurrentCount() ; - mBaseSessionp = loadTestSession(base) ; - - // Load the current session - resetCurrentCount() ; - mCurrentSessionp = loadTestSession(current) ; - - if (!mBaseSessionp || !mCurrentSessionp) - { - LL_ERRS() << "Error loading test sessions." << LL_ENDL ; - } - - // Compare - compareTestSessions(os) ; - - // Release memory - if (mBaseSessionp) - { - delete mBaseSessionp ; - mBaseSessionp = NULL ; - } - if (mCurrentSessionp) - { - delete mCurrentSessionp ; - mCurrentSessionp = NULL ; - } -} - - -//---------------------------------------------------------------------------------------------- -// LLTestSession -//---------------------------------------------------------------------------------------------- - -LLMetricPerformanceTesterWithSession::LLTestSession::~LLTestSession() -{ -} - +/**
+ * @file llmetricperformancetester.cpp
+ * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes implementation
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "indra_constants.h"
+#include "llerror.h"
+#include "llsdserialize.h"
+#include "lltreeiterators.h"
+#include "llmetricperformancetester.h"
+#include "llfasttimer.h"
+
+//----------------------------------------------------------------------------------------------
+// LLMetricPerformanceTesterBasic : static methods and testers management
+//----------------------------------------------------------------------------------------------
+
+LLMetricPerformanceTesterBasic::name_tester_map_t LLMetricPerformanceTesterBasic::sTesterMap ;
+
+/*static*/
+void LLMetricPerformanceTesterBasic::cleanupClass()
+{
+ for (name_tester_map_t::value_type& pair : sTesterMap)
+ {
+ delete pair.second;
+ }
+ sTesterMap.clear() ;
+}
+
+/*static*/
+bool LLMetricPerformanceTesterBasic::addTester(LLMetricPerformanceTesterBasic* tester)
+{
+ llassert_always(tester != NULL);
+ std::string name = tester->getTesterName() ;
+ if (getTester(name))
+ {
+ LL_ERRS() << "Tester name is already used by some other tester : " << name << LL_ENDL ;
+ return false;
+ }
+
+ sTesterMap.insert(std::make_pair(name, tester));
+ return true;
+}
+
+/*static*/
+void LLMetricPerformanceTesterBasic::deleteTester(std::string name)
+{
+ name_tester_map_t::iterator tester = sTesterMap.find(name);
+ if (tester != sTesterMap.end())
+ {
+ delete tester->second;
+ sTesterMap.erase(tester);
+ }
+}
+
+/*static*/
+LLMetricPerformanceTesterBasic* LLMetricPerformanceTesterBasic::getTester(std::string name)
+{
+ // Check for the requested metric name
+ name_tester_map_t::iterator found_it = sTesterMap.find(name) ;
+ if (found_it != sTesterMap.end())
+ {
+ return found_it->second ;
+ }
+ return NULL ;
+}
+
+/*static*/
+// Return true if this metric is requested or if the general default "catch all" metric is requested
+bool LLMetricPerformanceTesterBasic::isMetricLogRequested(std::string name)
+{
+ return (LLTrace::BlockTimer::sMetricLog && ((LLTrace::BlockTimer::sLogName == name) || (LLTrace::BlockTimer::sLogName == DEFAULT_METRIC_NAME)));
+}
+
+/*static*/
+LLSD LLMetricPerformanceTesterBasic::analyzeMetricPerformanceLog(std::istream& is)
+{
+ LLSD ret;
+ LLSD cur;
+
+ while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is))
+ {
+ for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter)
+ {
+ std::string label = iter->first;
+
+ LLMetricPerformanceTesterBasic* tester = LLMetricPerformanceTesterBasic::getTester(iter->second["Name"].asString()) ;
+ if(tester)
+ {
+ ret[label]["Name"] = iter->second["Name"] ;
+
+ auto num_of_metrics = tester->getNumberOfMetrics() ;
+ for(size_t index = 0 ; index < num_of_metrics ; index++)
+ {
+ ret[label][ tester->getMetricName(index) ] = iter->second[ tester->getMetricName(index) ] ;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+/*static*/
+void LLMetricPerformanceTesterBasic::doAnalysisMetrics(std::string baseline, std::string target, std::string output)
+{
+ if(!LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters())
+ {
+ return ;
+ }
+
+ // Open baseline and current target, exit if one is inexistent
+ llifstream base_is(baseline.c_str());
+ llifstream target_is(target.c_str());
+ if (!base_is.is_open() || !target_is.is_open())
+ {
+ LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL;
+ base_is.close();
+ target_is.close();
+ return;
+ }
+
+ //analyze baseline
+ LLSD base = analyzeMetricPerformanceLog(base_is);
+ base_is.close();
+
+ //analyze current
+ LLSD current = analyzeMetricPerformanceLog(target_is);
+ target_is.close();
+
+ //output comparision
+ llofstream os(output.c_str());
+
+ os << "Label, Metric, Base(B), Target(T), Diff(T-B), Percentage(100*T/B)\n";
+ for (LLMetricPerformanceTesterBasic::name_tester_map_t::value_type& pair : LLMetricPerformanceTesterBasic::sTesterMap)
+ {
+ LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)pair.second);
+ tester->analyzePerformance(&os, &base, ¤t) ;
+ }
+
+ os.flush();
+ os.close();
+}
+
+
+//----------------------------------------------------------------------------------------------
+// LLMetricPerformanceTesterBasic : Tester instance methods
+//----------------------------------------------------------------------------------------------
+
+LLMetricPerformanceTesterBasic::LLMetricPerformanceTesterBasic(std::string name) :
+ mName(name),
+ mCount(0)
+{
+ if (mName == std::string())
+ {
+ LL_ERRS() << "LLMetricPerformanceTesterBasic construction invalid : Empty name passed to constructor" << LL_ENDL ;
+ }
+
+ mValidInstance = LLMetricPerformanceTesterBasic::addTester(this) ;
+}
+
+LLMetricPerformanceTesterBasic::~LLMetricPerformanceTesterBasic()
+{
+}
+
+void LLMetricPerformanceTesterBasic::preOutputTestResults(LLSD* sd)
+{
+ incrementCurrentCount() ;
+}
+
+void LLMetricPerformanceTesterBasic::postOutputTestResults(LLSD* sd)
+{
+ LLTrace::BlockTimer::pushLog(*sd);
+}
+
+void LLMetricPerformanceTesterBasic::outputTestResults()
+{
+ LLSD sd;
+
+ preOutputTestResults(&sd) ;
+ outputTestRecord(&sd) ;
+ postOutputTestResults(&sd) ;
+}
+
+void LLMetricPerformanceTesterBasic::addMetric(std::string str)
+{
+ mMetricStrings.push_back(str) ;
+}
+
+/*virtual*/
+void LLMetricPerformanceTesterBasic::analyzePerformance(llofstream* os, LLSD* base, LLSD* current)
+{
+ resetCurrentCount() ;
+
+ std::string current_label = getCurrentLabelName();
+ bool in_base = (*base).has(current_label) ;
+ bool in_current = (*current).has(current_label) ;
+
+ while(in_base || in_current)
+ {
+ LLSD::String label = current_label ;
+
+ if(in_base && in_current)
+ {
+ *os << llformat("%s\n", label.c_str()) ;
+
+ for(U32 index = 0 ; index < mMetricStrings.size() ; index++)
+ {
+ switch((*current)[label][ mMetricStrings[index] ].type())
+ {
+ case LLSD::TypeInteger:
+ compareTestResults(os, mMetricStrings[index],
+ (S32)((*base)[label][ mMetricStrings[index] ].asInteger()), (S32)((*current)[label][ mMetricStrings[index] ].asInteger())) ;
+ break ;
+ case LLSD::TypeReal:
+ compareTestResults(os, mMetricStrings[index],
+ (F32)((*base)[label][ mMetricStrings[index] ].asReal()), (F32)((*current)[label][ mMetricStrings[index] ].asReal())) ;
+ break;
+ default:
+ LL_ERRS() << "unsupported metric " << mMetricStrings[index] << " LLSD type: " << (S32)(*current)[label][ mMetricStrings[index] ].type() << LL_ENDL ;
+ }
+ }
+ }
+
+ incrementCurrentCount();
+ current_label = getCurrentLabelName();
+ in_base = (*base).has(current_label) ;
+ in_current = (*current).has(current_label) ;
+ }
+}
+
+/*virtual*/
+void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current)
+{
+ *os << llformat(" ,%s, %d, %d, %d, %.4f\n", metric_string.c_str(), v_base, v_current,
+ v_current - v_base, (v_base != 0) ? 100.f * v_current / v_base : 0) ;
+}
+
+/*virtual*/
+void LLMetricPerformanceTesterBasic::compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current)
+{
+ *os << llformat(" ,%s, %.4f, %.4f, %.4f, %.4f\n", metric_string.c_str(), v_base, v_current,
+ v_current - v_base, (fabs(v_base) > 0.0001f) ? 100.f * v_current / v_base : 0.f ) ;
+}
+
+//----------------------------------------------------------------------------------------------
+// LLMetricPerformanceTesterWithSession
+//----------------------------------------------------------------------------------------------
+
+LLMetricPerformanceTesterWithSession::LLMetricPerformanceTesterWithSession(std::string name) :
+ LLMetricPerformanceTesterBasic(name),
+ mBaseSessionp(NULL),
+ mCurrentSessionp(NULL)
+{
+}
+
+LLMetricPerformanceTesterWithSession::~LLMetricPerformanceTesterWithSession()
+{
+ if (mBaseSessionp)
+ {
+ delete mBaseSessionp ;
+ mBaseSessionp = NULL ;
+ }
+ if (mCurrentSessionp)
+ {
+ delete mCurrentSessionp ;
+ mCurrentSessionp = NULL ;
+ }
+}
+
+/*virtual*/
+void LLMetricPerformanceTesterWithSession::analyzePerformance(llofstream* os, LLSD* base, LLSD* current)
+{
+ // Load the base session
+ resetCurrentCount() ;
+ mBaseSessionp = loadTestSession(base) ;
+
+ // Load the current session
+ resetCurrentCount() ;
+ mCurrentSessionp = loadTestSession(current) ;
+
+ if (!mBaseSessionp || !mCurrentSessionp)
+ {
+ LL_ERRS() << "Error loading test sessions." << LL_ENDL ;
+ }
+
+ // Compare
+ compareTestSessions(os) ;
+
+ // Release memory
+ if (mBaseSessionp)
+ {
+ delete mBaseSessionp ;
+ mBaseSessionp = NULL ;
+ }
+ if (mCurrentSessionp)
+ {
+ delete mCurrentSessionp ;
+ mCurrentSessionp = NULL ;
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------
+// LLTestSession
+//----------------------------------------------------------------------------------------------
+
+LLMetricPerformanceTesterWithSession::LLTestSession::~LLTestSession()
+{
+}
+
diff --git a/indra/llcommon/llmetricperformancetester.h b/indra/llcommon/llmetricperformancetester.h index 15c564f2ca..8989e657ce 100644 --- a/indra/llcommon/llmetricperformancetester.h +++ b/indra/llcommon/llmetricperformancetester.h @@ -1,215 +1,215 @@ -/** - * @file llmetricperformancetester.h - * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes definition - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_METRICPERFORMANCETESTER_H -#define LL_METRICPERFORMANCETESTER_H - -char const* const DEFAULT_METRIC_NAME = "metric"; - -/** - * @class LLMetricPerformanceTesterBasic - * @brief Performance Metric Base Class - */ -class LL_COMMON_API LLMetricPerformanceTesterBasic -{ -public: - /** - * @brief Creates a basic tester instance. - * @param[in] name - Unique string identifying this tester instance. - */ - LLMetricPerformanceTesterBasic(std::string name); - virtual ~LLMetricPerformanceTesterBasic(); - - /** - * @return Returns true if the instance has been added to the tester map. - * Need to be tested after creation of a tester instance so to know if the tester is correctly handled. - * A tester might not be added to the map if another tester with the same name already exists. - */ - BOOL isValid() const { return mValidInstance; } - - /** - * @brief Write a set of test results to the log LLSD. - */ - void outputTestResults() ; - - /** - * @brief Compare the test results. - * By default, compares the test results against the baseline one by one, item by item, - * in the increasing order of the LLSD record counter, starting from the first one. - */ - virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ; - - static void doAnalysisMetrics(std::string baseline, std::string target, std::string output) ; - - /** - * @return Returns the number of the test metrics in this tester instance. - */ - auto getNumberOfMetrics() const { return mMetricStrings.size() ;} - /** - * @return Returns the metric name at index - * @param[in] index - Index on the list of metrics managed by this tester instance. - */ - std::string getMetricName(size_t index) const { return mMetricStrings[index] ;} - -protected: - /** - * @return Returns the name of this tester instance. - */ - std::string getTesterName() const { return mName ;} - - /** - * @brief Insert a new metric to be managed by this tester instance. - * @param[in] str - Unique string identifying the new metric. - */ - void addMetric(std::string str) ; - - /** - * @brief Compare test results, provided in 2 flavors: compare integers and compare floats. - * @param[out] os - Formatted output string holding the compared values. - * @param[in] metric_string - Name of the metric. - * @param[in] v_base - Base value of the metric. - * @param[in] v_current - Current value of the metric. - */ - virtual void compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current) ; - virtual void compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current) ; - - /** - * @brief Reset internal record count. Count starts with 1. - */ - void resetCurrentCount() { mCount = 1; } - /** - * @brief Increment internal record count. - */ - void incrementCurrentCount() { mCount++; } - /** - * @return Returns the label to be used for the current count. It's "TesterName"-"Count". - */ - std::string getCurrentLabelName() const { return llformat("%s-%d", mName.c_str(), mCount) ;} - - /** - * @brief Write a test record to the LLSD. Implementers need to overload this method. - * @param[out] sd - The LLSD record to store metric data into. - */ - virtual void outputTestRecord(LLSD* sd) = 0 ; - -private: - void preOutputTestResults(LLSD* sd) ; - void postOutputTestResults(LLSD* sd) ; - static LLSD analyzeMetricPerformanceLog(std::istream& is) ; - - std::string mName ; // Name of this tester instance - S32 mCount ; // Current record count - BOOL mValidInstance; // TRUE if the instance is managed by the map - std::vector< std::string > mMetricStrings ; // Metrics strings - -// Static members managing the collection of testers -public: - // Map of all the tester instances in use - typedef std::map< std::string, LLMetricPerformanceTesterBasic* > name_tester_map_t; - static name_tester_map_t sTesterMap ; - - /** - * @return Returns a pointer to the tester - * @param[in] name - Name of the tester instance queried. - */ - static LLMetricPerformanceTesterBasic* getTester(std::string name) ; - - /** - * @return Delete the named tester from the list - * @param[in] name - Name of the tester instance to delete. - */ - static void deleteTester(std::string name); - - /** - * @return Returns TRUE if that metric *or* the default catch all metric has been requested to be logged - * @param[in] name - Name of the tester queried. - */ - static BOOL isMetricLogRequested(std::string name); - - /** - * @return Returns TRUE if there's a tester defined, FALSE otherwise. - */ - static BOOL hasMetricPerformanceTesters() { return !sTesterMap.empty() ;} - /** - * @brief Delete all testers and reset the tester map - */ - static void cleanupClass() ; - -private: - // Add a tester to the map. Returns false if adding fails. - static BOOL addTester(LLMetricPerformanceTesterBasic* tester) ; -}; - -/** - * @class LLMetricPerformanceTesterWithSession - * @brief Performance Metric Class with custom session - */ -class LL_COMMON_API LLMetricPerformanceTesterWithSession : public LLMetricPerformanceTesterBasic -{ -public: - /** - * @param[in] name - Unique string identifying this tester instance. - */ - LLMetricPerformanceTesterWithSession(std::string name); - virtual ~LLMetricPerformanceTesterWithSession(); - - /** - * @brief Compare the test results. - * This will be loading the base and current sessions and compare them using the virtual - * abstract methods loadTestSession() and compareTestSessions() - */ - virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ; - -protected: - /** - * @class LLMetricPerformanceTesterWithSession::LLTestSession - * @brief Defines an interface for the two abstract virtual functions loadTestSession() and compareTestSessions() - */ - class LL_COMMON_API LLTestSession - { - public: - virtual ~LLTestSession() ; - }; - - /** - * @brief Convert an LLSD log into a test session. - * @param[in] log - The LLSD record - * @return Returns the record as a test session - */ - virtual LLMetricPerformanceTesterWithSession::LLTestSession* loadTestSession(LLSD* log) = 0; - - /** - * @brief Compare the base session and the target session. Assumes base and current sessions have been loaded. - * @param[out] os - The comparison result as a standard stream - */ - virtual void compareTestSessions(llofstream* os) = 0; - - LLTestSession* mBaseSessionp; - LLTestSession* mCurrentSessionp; -}; - -#endif - +/**
+ * @file llmetricperformancetester.h
+ * @brief LLMetricPerformanceTesterBasic and LLMetricPerformanceTesterWithSession classes definition
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_METRICPERFORMANCETESTER_H
+#define LL_METRICPERFORMANCETESTER_H
+
+char const* const DEFAULT_METRIC_NAME = "metric";
+
+/**
+ * @class LLMetricPerformanceTesterBasic
+ * @brief Performance Metric Base Class
+ */
+class LL_COMMON_API LLMetricPerformanceTesterBasic
+{
+public:
+ /**
+ * @brief Creates a basic tester instance.
+ * @param[in] name - Unique string identifying this tester instance.
+ */
+ LLMetricPerformanceTesterBasic(std::string name);
+ virtual ~LLMetricPerformanceTesterBasic();
+
+ /**
+ * @return Returns true if the instance has been added to the tester map.
+ * Need to be tested after creation of a tester instance so to know if the tester is correctly handled.
+ * A tester might not be added to the map if another tester with the same name already exists.
+ */
+ bool isValid() const { return mValidInstance; }
+
+ /**
+ * @brief Write a set of test results to the log LLSD.
+ */
+ void outputTestResults() ;
+
+ /**
+ * @brief Compare the test results.
+ * By default, compares the test results against the baseline one by one, item by item,
+ * in the increasing order of the LLSD record counter, starting from the first one.
+ */
+ virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ;
+
+ static void doAnalysisMetrics(std::string baseline, std::string target, std::string output) ;
+
+ /**
+ * @return Returns the number of the test metrics in this tester instance.
+ */
+ auto getNumberOfMetrics() const { return mMetricStrings.size() ;}
+ /**
+ * @return Returns the metric name at index
+ * @param[in] index - Index on the list of metrics managed by this tester instance.
+ */
+ std::string getMetricName(size_t index) const { return mMetricStrings[index] ;}
+
+protected:
+ /**
+ * @return Returns the name of this tester instance.
+ */
+ std::string getTesterName() const { return mName ;}
+
+ /**
+ * @brief Insert a new metric to be managed by this tester instance.
+ * @param[in] str - Unique string identifying the new metric.
+ */
+ void addMetric(std::string str) ;
+
+ /**
+ * @brief Compare test results, provided in 2 flavors: compare integers and compare floats.
+ * @param[out] os - Formatted output string holding the compared values.
+ * @param[in] metric_string - Name of the metric.
+ * @param[in] v_base - Base value of the metric.
+ * @param[in] v_current - Current value of the metric.
+ */
+ virtual void compareTestResults(llofstream* os, std::string metric_string, S32 v_base, S32 v_current) ;
+ virtual void compareTestResults(llofstream* os, std::string metric_string, F32 v_base, F32 v_current) ;
+
+ /**
+ * @brief Reset internal record count. Count starts with 1.
+ */
+ void resetCurrentCount() { mCount = 1; }
+ /**
+ * @brief Increment internal record count.
+ */
+ void incrementCurrentCount() { mCount++; }
+ /**
+ * @return Returns the label to be used for the current count. It's "TesterName"-"Count".
+ */
+ std::string getCurrentLabelName() const { return llformat("%s-%d", mName.c_str(), mCount) ;}
+
+ /**
+ * @brief Write a test record to the LLSD. Implementers need to overload this method.
+ * @param[out] sd - The LLSD record to store metric data into.
+ */
+ virtual void outputTestRecord(LLSD* sd) = 0 ;
+
+private:
+ void preOutputTestResults(LLSD* sd) ;
+ void postOutputTestResults(LLSD* sd) ;
+ static LLSD analyzeMetricPerformanceLog(std::istream& is) ;
+
+ std::string mName ; // Name of this tester instance
+ S32 mCount ; // Current record count
+ bool mValidInstance; // true if the instance is managed by the map
+ std::vector< std::string > mMetricStrings ; // Metrics strings
+
+// Static members managing the collection of testers
+public:
+ // Map of all the tester instances in use
+ typedef std::map< std::string, LLMetricPerformanceTesterBasic* > name_tester_map_t;
+ static name_tester_map_t sTesterMap ;
+
+ /**
+ * @return Returns a pointer to the tester
+ * @param[in] name - Name of the tester instance queried.
+ */
+ static LLMetricPerformanceTesterBasic* getTester(std::string name) ;
+
+ /**
+ * @return Delete the named tester from the list
+ * @param[in] name - Name of the tester instance to delete.
+ */
+ static void deleteTester(std::string name);
+
+ /**
+ * @return Returns true if that metric *or* the default catch all metric has been requested to be logged
+ * @param[in] name - Name of the tester queried.
+ */
+ static bool isMetricLogRequested(std::string name);
+
+ /**
+ * @return Returns true if there's a tester defined, false otherwise.
+ */
+ static bool hasMetricPerformanceTesters() { return !sTesterMap.empty() ;}
+ /**
+ * @brief Delete all testers and reset the tester map
+ */
+ static void cleanupClass() ;
+
+private:
+ // Add a tester to the map. Returns false if adding fails.
+ static bool addTester(LLMetricPerformanceTesterBasic* tester) ;
+};
+
+/**
+ * @class LLMetricPerformanceTesterWithSession
+ * @brief Performance Metric Class with custom session
+ */
+class LL_COMMON_API LLMetricPerformanceTesterWithSession : public LLMetricPerformanceTesterBasic
+{
+public:
+ /**
+ * @param[in] name - Unique string identifying this tester instance.
+ */
+ LLMetricPerformanceTesterWithSession(std::string name);
+ virtual ~LLMetricPerformanceTesterWithSession();
+
+ /**
+ * @brief Compare the test results.
+ * This will be loading the base and current sessions and compare them using the virtual
+ * abstract methods loadTestSession() and compareTestSessions()
+ */
+ virtual void analyzePerformance(llofstream* os, LLSD* base, LLSD* current) ;
+
+protected:
+ /**
+ * @class LLMetricPerformanceTesterWithSession::LLTestSession
+ * @brief Defines an interface for the two abstract virtual functions loadTestSession() and compareTestSessions()
+ */
+ class LL_COMMON_API LLTestSession
+ {
+ public:
+ virtual ~LLTestSession() ;
+ };
+
+ /**
+ * @brief Convert an LLSD log into a test session.
+ * @param[in] log - The LLSD record
+ * @return Returns the record as a test session
+ */
+ virtual LLMetricPerformanceTesterWithSession::LLTestSession* loadTestSession(LLSD* log) = 0;
+
+ /**
+ * @brief Compare the base session and the target session. Assumes base and current sessions have been loaded.
+ * @param[out] os - The comparison result as a standard stream
+ */
+ virtual void compareTestSessions(llofstream* os) = 0;
+
+ LLTestSession* mBaseSessionp;
+ LLTestSession* mCurrentSessionp;
+};
+
+#endif
+
diff --git a/indra/llcommon/llmortician.cpp b/indra/llcommon/llmortician.cpp index 00d4a32553..7ba023f052 100644 --- a/indra/llcommon/llmortician.cpp +++ b/indra/llcommon/llmortician.cpp @@ -1,106 +1,106 @@ -/** - * @file llmortician.cpp - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" -#include "llmortician.h" - -#include <list> - -std::list<LLMortician*> LLMortician::sGraveyard; - -BOOL LLMortician::sDestroyImmediate = FALSE; - -LLMortician::~LLMortician() -{ - sGraveyard.remove(this); -} - -size_t LLMortician::logClass(std::stringstream &str) -{ - auto size = sGraveyard.size(); - str << "Mortician graveyard count: " << size; - str << " Zealous: " << (sDestroyImmediate ? "True" : "False"); - if (size == 0) - { - return size; - } - str << " Output:\n"; - std::list<LLMortician*>::iterator iter = sGraveyard.begin(); - std::list<LLMortician*>::iterator end = sGraveyard.end(); - while (iter!=end) - { - LLMortician* dead = *iter; - iter++; - // Be as detailed and safe as possible to figure out issues - str << "Pointer: " << dead; - if (dead) - { - try - { - str << " Is dead: " << (dead->isDead() ? "True" : "False"); - str << " Name: " << typeid(*dead).name(); - } - catch (...) - { - - } - } - str << "\n"; - } - str << "--------------------------------------------"; - return size; -} - -void LLMortician::updateClass() -{ - while (!sGraveyard.empty()) - { - LLMortician* dead = sGraveyard.front(); - delete dead; - } -} - -void LLMortician::die() -{ - // It is valid to call die() more than once on something that hasn't died yet - if (sDestroyImmediate) - { - // *NOTE: This is a hack to ensure destruction order on shutdown (relative to non-mortician controlled classes). - mIsDead = TRUE; - delete this; - return; - } - else if (!mIsDead) - { - mIsDead = TRUE; - sGraveyard.push_back(this); - } -} - -// static -void LLMortician::setZealous(BOOL b) -{ - sDestroyImmediate = b; -} +/**
+ * @file llmortician.cpp
+ *
+ * $LicenseInfo:firstyear=2005&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+#include "llmortician.h"
+
+#include <list>
+
+std::list<LLMortician*> LLMortician::sGraveyard;
+
+bool LLMortician::sDestroyImmediate = false;
+
+LLMortician::~LLMortician()
+{
+ sGraveyard.remove(this);
+}
+
+size_t LLMortician::logClass(std::stringstream &str)
+{
+ auto size = sGraveyard.size();
+ str << "Mortician graveyard count: " << size;
+ str << " Zealous: " << (sDestroyImmediate ? "True" : "False");
+ if (size == 0)
+ {
+ return size;
+ }
+ str << " Output:\n";
+ std::list<LLMortician*>::iterator iter = sGraveyard.begin();
+ std::list<LLMortician*>::iterator end = sGraveyard.end();
+ while (iter!=end)
+ {
+ LLMortician* dead = *iter;
+ iter++;
+ // Be as detailed and safe as possible to figure out issues
+ str << "Pointer: " << dead;
+ if (dead)
+ {
+ try
+ {
+ str << " Is dead: " << (dead->isDead() ? "True" : "False");
+ str << " Name: " << typeid(*dead).name();
+ }
+ catch (...)
+ {
+
+ }
+ }
+ str << "\n";
+ }
+ str << "--------------------------------------------";
+ return size;
+}
+
+void LLMortician::updateClass()
+{
+ while (!sGraveyard.empty())
+ {
+ LLMortician* dead = sGraveyard.front();
+ delete dead;
+ }
+}
+
+void LLMortician::die()
+{
+ // It is valid to call die() more than once on something that hasn't died yet
+ if (sDestroyImmediate)
+ {
+ // *NOTE: This is a hack to ensure destruction order on shutdown (relative to non-mortician controlled classes).
+ mIsDead = true;
+ delete this;
+ return;
+ }
+ else if (!mIsDead)
+ {
+ mIsDead = true;
+ sGraveyard.push_back(this);
+ }
+}
+
+// static
+void LLMortician::setZealous(bool b)
+{
+ sDestroyImmediate = b;
+}
diff --git a/indra/llcommon/llmortician.h b/indra/llcommon/llmortician.h index 6dca4da0c9..4aea4e0292 100644 --- a/indra/llcommon/llmortician.h +++ b/indra/llcommon/llmortician.h @@ -1,56 +1,56 @@ -/** - * @file llmortician.h - * @brief Base class for delayed deletions. - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 LLMORTICIAN_H -#define LLMORTICIAN_H - -#include "stdtypes.h" -#include <list> - -class LL_COMMON_API LLMortician -{ -public: - LLMortician() { mIsDead = FALSE; } - static auto graveyardCount() { return sGraveyard.size(); }; - static size_t logClass(std::stringstream &str); - static void updateClass(); - virtual ~LLMortician(); - void die(); - BOOL isDead() { return mIsDead; } - - // sets destroy immediate true - static void setZealous(BOOL b); - -private: - static BOOL sDestroyImmediate; - - BOOL mIsDead; - - static std::list<LLMortician*> sGraveyard; -}; - -#endif - +/**
+ * @file llmortician.h
+ * @brief Base class for delayed deletions.
+ *
+ * $LicenseInfo:firstyear=2005&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 LLMORTICIAN_H
+#define LLMORTICIAN_H
+
+#include "stdtypes.h"
+#include <list>
+
+class LL_COMMON_API LLMortician
+{
+public:
+ LLMortician() { mIsDead = false; }
+ static auto graveyardCount() { return sGraveyard.size(); };
+ static size_t logClass(std::stringstream &str);
+ static void updateClass();
+ virtual ~LLMortician();
+ void die();
+ bool isDead() { return mIsDead; }
+
+ // sets destroy immediate true
+ static void setZealous(bool b);
+
+private:
+ static bool sDestroyImmediate;
+
+ bool mIsDead;
+
+ static std::list<LLMortician*> sGraveyard;
+};
+
+#endif
+
diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index db14abb1fe..a7c2817e2f 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -1,239 +1,418 @@ -/** - * @file llmutex.cpp - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llmutex.h" -#include "llthread.h" -#include "lltimer.h" - -//============================================================================ - -LLMutex::LLMutex() : - mCount(0) -{ -} - - -LLMutex::~LLMutex() -{ -} - - -void LLMutex::lock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(isSelfLocked()) - { //redundant lock - mCount++; - return; - } - - mMutex.lock(); - -#if MUTEX_DEBUG - // Have to have the lock before we can access the debug info - auto id = LLThread::currentID(); - if (mIsLocked[id] != FALSE) - LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; - mIsLocked[id] = TRUE; -#endif - - mLockingThread = LLThread::currentID(); -} - -void LLMutex::unlock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (mCount > 0) - { //not the root unlock - mCount--; - return; - } - -#if MUTEX_DEBUG - // Access the debug info while we have the lock - auto id = LLThread::currentID(); - if (mIsLocked[id] != TRUE) - LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL; - mIsLocked[id] = FALSE; -#endif - - mLockingThread = LLThread::id_t(); - mMutex.unlock(); -} - -bool LLMutex::isLocked() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (!mMutex.try_lock()) - { - return true; - } - else - { - mMutex.unlock(); - return false; - } -} - -bool LLMutex::isSelfLocked() -{ - return mLockingThread == LLThread::currentID(); -} - -LLThread::id_t LLMutex::lockingThread() const -{ - return mLockingThread; -} - -bool LLMutex::trylock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(isSelfLocked()) - { //redundant lock - mCount++; - return true; - } - - if (!mMutex.try_lock()) - { - return false; - } - -#if MUTEX_DEBUG - // Have to have the lock before we can access the debug info - auto id = LLThread::currentID(); - if (mIsLocked[id] != FALSE) - LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; - mIsLocked[id] = TRUE; -#endif - - mLockingThread = LLThread::currentID(); - return true; -} - -//============================================================================ - -LLCondition::LLCondition() : - LLMutex() -{ -} - - -LLCondition::~LLCondition() -{ -} - - -void LLCondition::wait() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - std::unique_lock< std::mutex > lock(mMutex); - mCond.wait(lock); -} - -void LLCondition::signal() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mCond.notify_one(); -} - -void LLCondition::broadcast() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mCond.notify_all(); -} - - - -LLMutexTrylock::LLMutexTrylock(LLMutex* mutex) - : mMutex(mutex), - mLocked(false) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (mMutex) - mLocked = mMutex->trylock(); -} - -LLMutexTrylock::LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms) - : mMutex(mutex), - mLocked(false) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (!mMutex) - return; - - for (U32 i = 0; i < aTries; ++i) - { - mLocked = mMutex->trylock(); - if (mLocked) - break; - ms_sleep(delay_ms); - } -} - -LLMutexTrylock::~LLMutexTrylock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if (mMutex && mLocked) - mMutex->unlock(); -} - - -//--------------------------------------------------------------------- -// -// LLScopedLock -// -LLScopedLock::LLScopedLock(std::mutex* mutex) : mMutex(mutex) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(mutex) - { - mutex->lock(); - mLocked = true; - } - else - { - mLocked = false; - } -} - -LLScopedLock::~LLScopedLock() -{ - unlock(); -} - -void LLScopedLock::unlock() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(mLocked) - { - mMutex->unlock(); - mLocked = false; - } -} - -//============================================================================ +/**
+ * @file llmutex.cpp
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llmutex.h"
+#include "llthread.h"
+#include "lltimer.h"
+
+
+//---------------------------------------------------------------------
+//
+// LLMutex
+//
+LLMutex::LLMutex() :
+ mCount(0)
+{
+}
+
+LLMutex::~LLMutex()
+{
+}
+
+void LLMutex::lock()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if(isSelfLocked())
+ { //redundant lock
+ mCount++;
+ return;
+ }
+
+ mMutex.lock();
+
+#if MUTEX_DEBUG
+ // Have to have the lock before we can access the debug info
+ auto id = LLThread::currentID();
+ if (mIsLocked[id])
+ LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL;
+ mIsLocked[id] = true;
+#endif
+
+ mLockingThread = LLThread::currentID();
+}
+
+void LLMutex::unlock()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if (mCount > 0)
+ { //not the root unlock
+ mCount--;
+ return;
+ }
+
+#if MUTEX_DEBUG
+ // Access the debug info while we have the lock
+ auto id = LLThread::currentID();
+ if (!mIsLocked[id])
+ LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL;
+ mIsLocked[id] = false;
+#endif
+
+ mLockingThread = LLThread::id_t();
+ mMutex.unlock();
+}
+
+bool LLMutex::isLocked()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if (!mMutex.try_lock())
+ {
+ return true;
+ }
+ else
+ {
+ mMutex.unlock();
+ return false;
+ }
+}
+
+bool LLMutex::isSelfLocked()
+{
+ return mLockingThread == LLThread::currentID();
+}
+
+LLThread::id_t LLMutex::lockingThread() const
+{
+ return mLockingThread;
+}
+
+bool LLMutex::trylock()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if (isSelfLocked())
+ { //redundant lock
+ mCount++;
+ return true;
+ }
+
+ if (!mMutex.try_lock())
+ {
+ return false;
+ }
+
+#if MUTEX_DEBUG
+ // Have to have the lock before we can access the debug info
+ auto id = LLThread::currentID();
+ if (mIsLocked[id])
+ LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL;
+ mIsLocked[id] = true;
+#endif
+
+ mLockingThread = LLThread::currentID();
+ return true;
+}
+
+
+//---------------------------------------------------------------------
+//
+// LLSharedMutex
+//
+LLSharedMutex::LLSharedMutex()
+: mLockingThreads(2) // Reserve 2 slots in the map hash table
+, mIsShared(false)
+{
+}
+
+bool LLSharedMutex::isLocked() const
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ std::lock_guard<std::mutex> lock(mLockMutex);
+
+ return !mLockingThreads.empty();
+}
+
+bool LLSharedMutex::isThreadLocked() const
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ LLThread::id_t current_thread = LLThread::currentID();
+ std::lock_guard<std::mutex> lock(mLockMutex);
+
+ const_iterator it = mLockingThreads.find(current_thread);
+ return it != mLockingThreads.end();
+}
+
+void LLSharedMutex::lockShared()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ LLThread::id_t current_thread = LLThread::currentID();
+
+ mLockMutex.lock();
+ iterator it = mLockingThreads.find(current_thread);
+ if (it != mLockingThreads.end())
+ {
+ it->second++;
+ }
+ else
+ {
+ // Acquire the mutex immediately if the mutex is not locked exclusively
+ // or enter a locking state if the mutex is already locked exclusively
+ mLockMutex.unlock();
+ mSharedMutex.lock_shared();
+ mLockMutex.lock();
+ // Continue after acquiring the mutex
+ mLockingThreads.emplace(std::make_pair(current_thread, 1));
+ mIsShared = true;
+ }
+ mLockMutex.unlock();
+}
+
+void LLSharedMutex::lockExclusive()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ LLThread::id_t current_thread = LLThread::currentID();
+
+ mLockMutex.lock();
+ iterator it = mLockingThreads.find(current_thread);
+ if (it != mLockingThreads.end())
+ {
+ if (mIsShared)
+ {
+ // The mutex is already locked in the current thread
+ // but this lock is SHARED (not EXCLISIVE)
+ // We can't lock it again, the lock stays shared
+ // This can lead to a collision (theoretically)
+ llassert_always(!"The current thread is already locked SHARED and can't be locked EXCLUSIVE");
+ }
+ it->second++;
+ }
+ else
+ {
+ // Acquire the mutex immediately if mLockingThreads is empty
+ // or enter a locking state if mLockingThreads is not empty
+ mLockMutex.unlock();
+ mSharedMutex.lock();
+ mLockMutex.lock();
+ // Continue after acquiring the mutex (and possible quitting the locking state)
+ mLockingThreads.emplace(std::make_pair(current_thread, 1));
+ mIsShared = false;
+ }
+ mLockMutex.unlock();
+}
+
+bool LLSharedMutex::trylockShared()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ LLThread::id_t current_thread = LLThread::currentID();
+ std::lock_guard<std::mutex> lock(mLockMutex);
+
+ iterator it = mLockingThreads.find(current_thread);
+ if (it != mLockingThreads.end())
+ {
+ it->second++;
+ }
+ else
+ {
+ if (!mSharedMutex.try_lock_shared())
+ return false;
+
+ mLockingThreads.emplace(std::make_pair(current_thread, 1));
+ mIsShared = true;
+ }
+
+ return true;
+}
+
+bool LLSharedMutex::trylockExclusive()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ LLThread::id_t current_thread = LLThread::currentID();
+ std::lock_guard<std::mutex> lock(mLockMutex);
+
+ if (mLockingThreads.size() == 1 && mLockingThreads.begin()->first == current_thread)
+ {
+ mLockingThreads.begin()->second++;
+ }
+ else
+ {
+ if (!mSharedMutex.try_lock())
+ return false;
+
+ mLockingThreads.emplace(std::make_pair(current_thread, 1));
+ mIsShared = false;
+ }
+
+ return true;
+}
+
+void LLSharedMutex::unlockShared()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ LLThread::id_t current_thread = LLThread::currentID();
+ std::lock_guard<std::mutex> lock(mLockMutex);
+
+ iterator it = mLockingThreads.find(current_thread);
+ if (it != mLockingThreads.end())
+ {
+ if (it->second > 1)
+ {
+ it->second--;
+ }
+ else
+ {
+ mLockingThreads.erase(it);
+ mSharedMutex.unlock_shared();
+ }
+ }
+}
+
+void LLSharedMutex::unlockExclusive()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ LLThread::id_t current_thread = LLThread::currentID();
+ std::lock_guard<std::mutex> lock(mLockMutex);
+
+ iterator it = mLockingThreads.find(current_thread);
+ if (it != mLockingThreads.end())
+ {
+ if (it->second > 1)
+ {
+ it->second--;
+ }
+ else
+ {
+ mLockingThreads.erase(it);
+ mSharedMutex.unlock();
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------
+//
+// LLCondition
+//
+LLCondition::LLCondition() :
+ LLMutex()
+{
+}
+
+LLCondition::~LLCondition()
+{
+}
+
+void LLCondition::wait()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ std::unique_lock< std::mutex > lock(mMutex);
+ mCond.wait(lock);
+}
+
+void LLCondition::signal()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ mCond.notify_one();
+}
+
+void LLCondition::broadcast()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ mCond.notify_all();
+}
+
+
+//---------------------------------------------------------------------
+//
+// LLMutexTrylock
+//
+LLMutexTrylock::LLMutexTrylock(LLMutex* mutex)
+ : mMutex(mutex),
+ mLocked(false)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if (mMutex)
+ mLocked = mMutex->trylock();
+}
+
+LLMutexTrylock::LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms)
+ : mMutex(mutex),
+ mLocked(false)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if (!mMutex)
+ return;
+
+ for (U32 i = 0; i < aTries; ++i)
+ {
+ mLocked = mMutex->trylock();
+ if (mLocked)
+ break;
+ ms_sleep(delay_ms);
+ }
+}
+
+LLMutexTrylock::~LLMutexTrylock()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if (mMutex && mLocked)
+ mMutex->unlock();
+}
+
+
+//---------------------------------------------------------------------
+//
+// LLScopedLock
+//
+LLScopedLock::LLScopedLock(std::mutex* mutex) : mMutex(mutex)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if(mutex)
+ {
+ mutex->lock();
+ mLocked = true;
+ }
+ else
+ {
+ mLocked = false;
+ }
+}
+
+LLScopedLock::~LLScopedLock()
+{
+ unlock();
+}
+
+void LLScopedLock::unlock()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if(mLocked)
+ {
+ mMutex->unlock();
+ mLocked = false;
+ }
+}
+
+//============================================================================
diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index 9a888f1220..898ddab284 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -1,168 +1,271 @@ -/** - * @file llmutex.h - * @brief Base classes for mutex and condition handling. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, 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_LLMUTEX_H -#define LL_LLMUTEX_H - -#include "stdtypes.h" -#include "llthread.h" -#include <boost/noncopyable.hpp> - -#include "mutex.h" -#include <condition_variable> - -//============================================================================ - -//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO) -#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles - -#if MUTEX_DEBUG -#include <map> -#endif - -class LL_COMMON_API LLMutex -{ -public: - LLMutex(); - virtual ~LLMutex(); - - void lock(); // blocks - bool trylock(); // non-blocking, returns true if lock held. - void unlock(); // undefined behavior when called on mutex not being held - bool isLocked(); // non-blocking, but does do a lock/unlock so not free - bool isSelfLocked(); //return true if locked in a same thread - LLThread::id_t lockingThread() const; //get ID of locking thread - -protected: - std::mutex mMutex; - mutable U32 mCount; - mutable LLThread::id_t mLockingThread; - -#if MUTEX_DEBUG - std::unordered_map<LLThread::id_t, BOOL> mIsLocked; -#endif -}; - -// Actually a condition/mutex pair (since each condition needs to be associated with a mutex). -class LL_COMMON_API LLCondition : public LLMutex -{ -public: - LLCondition(); - ~LLCondition(); - - void wait(); // blocks - void signal(); - void broadcast(); - -protected: - std::condition_variable mCond; -}; - -class LLMutexLock -{ -public: - LLMutexLock(LLMutex* mutex) - { - mMutex = mutex; - - if(mMutex) - mMutex->lock(); - } - ~LLMutexLock() - { - if(mMutex) - mMutex->unlock(); - } -private: - LLMutex* mMutex; -}; - -//============================================================================ - -// Scoped locking class similar in function to LLMutexLock but uses -// the trylock() method to conditionally acquire lock without -// blocking. Caller resolves the resulting condition by calling -// the isLocked() method and either punts or continues as indicated. -// -// Mostly of interest to callers needing to avoid stalls who can -// guarantee another attempt at a later time. - -class LLMutexTrylock -{ -public: - LLMutexTrylock(LLMutex* mutex); - LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms = 10); - ~LLMutexTrylock(); - - bool isLocked() const - { - return mLocked; - } - -private: - LLMutex* mMutex; - bool mLocked; -}; - -/** -* @class LLScopedLock -* @brief Small class to help lock and unlock mutexes. -* -* The constructor handles the lock, and the destructor handles -* the unlock. Instances of this class are <b>not</b> thread safe. -*/ -class LL_COMMON_API LLScopedLock : private boost::noncopyable -{ -public: - /** - * @brief Constructor which accepts a mutex, and locks it. - * - * @param mutex An allocated mutex. If you pass in NULL, - * this wrapper will not lock. - */ - LLScopedLock(std::mutex* mutex); - - /** - * @brief Destructor which unlocks the mutex if still locked. - */ - ~LLScopedLock(); - - /** - * @brief Check lock. - */ - bool isLocked() const { return mLocked; } - - /** - * @brief This method unlocks the mutex. - */ - void unlock(); - -protected: - bool mLocked; - std::mutex* mMutex; -}; - -#endif // LL_LLMUTEX_H +/**
+ * @file llmutex.h
+ * @brief Base classes for mutex and condition handling.
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2012, 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_LLMUTEX_H
+#define LL_LLMUTEX_H
+
+#include "stdtypes.h"
+#include "llthread.h"
+#include <boost/noncopyable.hpp>
+
+#include "mutex.h"
+#include <shared_mutex>
+#include <unordered_map>
+#include <condition_variable>
+
+//============================================================================
+
+//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
+#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles
+
+#if MUTEX_DEBUG
+#include <map>
+#endif
+
+class LL_COMMON_API LLMutex
+{
+public:
+ LLMutex();
+ virtual ~LLMutex();
+
+ void lock(); // blocks
+ bool trylock(); // non-blocking, returns true if lock held.
+ void unlock(); // undefined behavior when called on mutex not being held
+ bool isLocked(); // non-blocking, but does do a lock/unlock so not free
+ bool isSelfLocked(); //return true if locked in a same thread
+ LLThread::id_t lockingThread() const; //get ID of locking thread
+
+protected:
+ std::mutex mMutex;
+ mutable U32 mCount;
+ mutable LLThread::id_t mLockingThread;
+
+#if MUTEX_DEBUG
+ std::unordered_map<LLThread::id_t, bool> mIsLocked;
+#endif
+};
+
+//============================================================================
+
+class LL_COMMON_API LLSharedMutex
+{
+public:
+ LLSharedMutex();
+
+ bool isLocked() const;
+ bool isThreadLocked() const;
+ bool isShared() const { return mIsShared; }
+
+ void lockShared();
+ void lockExclusive();
+ template<bool SHARED> void lock();
+
+ bool trylockShared();
+ bool trylockExclusive();
+ template<bool SHARED> bool trylock();
+
+ void unlockShared();
+ void unlockExclusive();
+ template<bool SHARED> void unlock();
+
+private:
+ std::shared_mutex mSharedMutex;
+ mutable std::mutex mLockMutex;
+ std::unordered_map<LLThread::id_t, U32> mLockingThreads;
+ bool mIsShared;
+
+ using iterator = std::unordered_map<LLThread::id_t, U32>::iterator;
+ using const_iterator = std::unordered_map<LLThread::id_t, U32>::const_iterator;
+};
+
+template<>
+inline void LLSharedMutex::lock<true>()
+{
+ lockShared();
+}
+
+template<>
+inline void LLSharedMutex::lock<false>()
+{
+ lockExclusive();
+}
+
+template<>
+inline bool LLSharedMutex::trylock<true>()
+{
+ return trylockShared();
+}
+
+template<>
+inline bool LLSharedMutex::trylock<false>()
+{
+ return trylockExclusive();
+}
+
+template<>
+inline void LLSharedMutex::unlock<true>()
+{
+ unlockShared();
+}
+
+template<>
+inline void LLSharedMutex::unlock<false>()
+{
+ unlockExclusive();
+}
+
+// Actually a condition/mutex pair (since each condition needs to be associated with a mutex).
+class LL_COMMON_API LLCondition : public LLMutex
+{
+public:
+ LLCondition();
+ ~LLCondition();
+
+ void wait(); // blocks
+ void signal();
+ void broadcast();
+
+protected:
+ std::condition_variable mCond;
+};
+
+//============================================================================
+
+class LLMutexLock
+{
+public:
+ LLMutexLock(LLMutex* mutex)
+ {
+ mMutex = mutex;
+
+ if (mMutex)
+ mMutex->lock();
+ }
+
+ ~LLMutexLock()
+ {
+ if (mMutex)
+ mMutex->unlock();
+ }
+
+private:
+ LLMutex* mMutex;
+};
+
+//============================================================================
+
+template<bool SHARED>
+class LLSharedMutexLockTemplate
+{
+public:
+ LLSharedMutexLockTemplate(LLSharedMutex* mutex)
+ : mSharedMutex(mutex)
+ {
+ if (mSharedMutex)
+ mSharedMutex->lock<SHARED>();
+ }
+
+ ~LLSharedMutexLockTemplate()
+ {
+ if (mSharedMutex)
+ mSharedMutex->unlock<SHARED>();
+ }
+
+private:
+ LLSharedMutex* mSharedMutex;
+};
+
+using LLSharedMutexLock = LLSharedMutexLockTemplate<true>;
+using LLExclusiveMutexLock = LLSharedMutexLockTemplate<false>;
+
+//============================================================================
+
+// Scoped locking class similar in function to LLMutexLock but uses
+// the trylock() method to conditionally acquire lock without
+// blocking. Caller resolves the resulting condition by calling
+// the isLocked() method and either punts or continues as indicated.
+//
+// Mostly of interest to callers needing to avoid stalls who can
+// guarantee another attempt at a later time.
+
+class LLMutexTrylock
+{
+public:
+ LLMutexTrylock(LLMutex* mutex);
+ LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms = 10);
+ ~LLMutexTrylock();
+
+ bool isLocked() const
+ {
+ return mLocked;
+ }
+
+private:
+ LLMutex* mMutex;
+ bool mLocked;
+};
+
+//============================================================================
+
+/**
+* @class LLScopedLock
+* @brief Small class to help lock and unlock mutexes.
+*
+* The constructor handles the lock, and the destructor handles
+* the unlock. Instances of this class are <b>not</b> thread safe.
+*/
+class LL_COMMON_API LLScopedLock : private boost::noncopyable
+{
+public:
+ /**
+ * @brief Constructor which accepts a mutex, and locks it.
+ *
+ * @param mutex An allocated mutex. If you pass in NULL,
+ * this wrapper will not lock.
+ */
+ LLScopedLock(std::mutex* mutex);
+
+ /**
+ * @brief Destructor which unlocks the mutex if still locked.
+ */
+ ~LLScopedLock();
+
+ /**
+ * @brief Check lock.
+ */
+ bool isLocked() const { return mLocked; }
+
+ /**
+ * @brief This method unlocks the mutex.
+ */
+ void unlock();
+
+protected:
+ bool mLocked;
+ std::mutex* mMutex;
+};
+
+#endif // LL_LLMUTEX_H
diff --git a/indra/llcommon/llnametable.h b/indra/llcommon/llnametable.h index b3a9df8fc3..bcc6267ec5 100644 --- a/indra/llcommon/llnametable.h +++ b/indra/llcommon/llnametable.h @@ -1,103 +1,103 @@ -/** - * @file llnametable.h - * @brief LLNameTable class is a table to associate pointers with string names - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLNAMETABLE_H -#define LL_LLNAMETABLE_H - -#include <map> - -#include "string_table.h" - -template <class DATA> -class LLNameTable -{ -public: - LLNameTable() - : mNameMap() - { - } - - ~LLNameTable() - { - } - - void addEntry(const std::string& name, DATA data) - { - addEntry(name.c_str(), data); - } - - void addEntry(const char *name, DATA data) - { - char *tablename = gStringTable.addString(name); - mNameMap[tablename] = data; - } - - BOOL checkName(const std::string& name) const - { - return checkName(name.c_str()); - } - - // "logically const" even though it modifies the global nametable - BOOL checkName(const char *name) const - { - char *tablename = gStringTable.addString(name); - return mNameMap.count(tablename) ? TRUE : FALSE; - } - - DATA resolveName(const std::string& name) const - { - return resolveName(name.c_str()); - } - - // "logically const" even though it modifies the global nametable - DATA resolveName(const char *name) const - { - char *tablename = gStringTable.addString(name); - const_iter_t iter = mNameMap.find(tablename); - if (iter != mNameMap.end()) - return iter->second; - else - return 0; - } - - // O(N)! (currently only used in one place... (newsim/llstate.cpp)) - const char *resolveData(const DATA &data) const - { - for (const name_map_t::value_type& pair : mNameMap) - { - if (pair.second == data) - return pair.first; - } - return NULL; - } - - typedef std::map<const char *, DATA> name_map_t; - typedef typename std::map<const char *,DATA>::iterator iter_t; - typedef typename std::map<const char *,DATA>::const_iterator const_iter_t; - name_map_t mNameMap; -}; - -#endif +/**
+ * @file llnametable.h
+ * @brief LLNameTable class is a table to associate pointers with string names
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLNAMETABLE_H
+#define LL_LLNAMETABLE_H
+
+#include <map>
+
+#include "string_table.h"
+
+template <class DATA>
+class LLNameTable
+{
+public:
+ LLNameTable()
+ : mNameMap()
+ {
+ }
+
+ ~LLNameTable()
+ {
+ }
+
+ void addEntry(const std::string& name, DATA data)
+ {
+ addEntry(name.c_str(), data);
+ }
+
+ void addEntry(const char *name, DATA data)
+ {
+ char *tablename = gStringTable.addString(name);
+ mNameMap[tablename] = data;
+ }
+
+ bool checkName(const std::string& name) const
+ {
+ return checkName(name.c_str());
+ }
+
+ // "logically const" even though it modifies the global nametable
+ bool checkName(const char *name) const
+ {
+ char *tablename = gStringTable.addString(name);
+ return mNameMap.find(tablename) != mNameMap.end();
+ }
+
+ DATA resolveName(const std::string& name) const
+ {
+ return resolveName(name.c_str());
+ }
+
+ // "logically const" even though it modifies the global nametable
+ DATA resolveName(const char *name) const
+ {
+ char *tablename = gStringTable.addString(name);
+ const_iter_t iter = mNameMap.find(tablename);
+ if (iter != mNameMap.end())
+ return iter->second;
+ else
+ return 0;
+ }
+
+ // O(N)! (currently only used in one place... (newsim/llstate.cpp))
+ const char *resolveData(const DATA &data) const
+ {
+ for (const name_map_t::value_type& pair : mNameMap)
+ {
+ if (pair.second == data)
+ return pair.first;
+ }
+ return NULL;
+ }
+
+ typedef std::map<const char *, DATA> name_map_t;
+ typedef typename std::map<const char *,DATA>::iterator iter_t;
+ typedef typename std::map<const char *,DATA>::const_iterator const_iter_t;
+ name_map_t mNameMap;
+};
+
+#endif
diff --git a/indra/llcommon/llpriqueuemap.h b/indra/llcommon/llpriqueuemap.h index 79934d094b..a0a0855109 100644 --- a/indra/llcommon/llpriqueuemap.h +++ b/indra/llcommon/llpriqueuemap.h @@ -1,145 +1,145 @@ -/** - * @file llpriqueuemap.h - * @brief Priority queue implementation - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLPRIQUEUEMAP_H -#define LL_LLPRIQUEUEMAP_H - -#include <map> - -// -// Priority queue, implemented under the hood as a -// map. Needs to be done this way because none of the -// standard STL containers provide a representation -// where it's easy to reprioritize. -// - -template <class DATA> -class LLPQMKey -{ -public: - LLPQMKey(const F32 priority, DATA data) : mPriority(priority), mData(data) - { - } - - bool operator<(const LLPQMKey &b) const - { - if (mPriority > b.mPriority) - { - return TRUE; - } - if (mPriority < b.mPriority) - { - return FALSE; - } - if (mData > b.mData) - { - return TRUE; - } - return FALSE; - } - - F32 mPriority; - DATA mData; -}; - -template <class DATA_TYPE> -class LLPriQueueMap -{ -public: - typedef typename std::map<LLPQMKey<DATA_TYPE>, DATA_TYPE>::iterator pqm_iter; - typedef std::pair<LLPQMKey<DATA_TYPE>, DATA_TYPE> pqm_pair; - typedef void (*set_pri_fn)(DATA_TYPE &data, const F32 priority); - typedef F32 (*get_pri_fn)(DATA_TYPE &data); - - - LLPriQueueMap(set_pri_fn set_pri, get_pri_fn get_pri) : mSetPriority(set_pri), mGetPriority(get_pri) - { - } - - void push(const F32 priority, DATA_TYPE data) - { -#ifdef _DEBUG - pqm_iter iter = mMap.find(LLPQMKey<DATA_TYPE>(priority, data)); - if (iter != mMap.end()) - { - LL_ERRS() << "Pushing already existing data onto queue!" << LL_ENDL; - } -#endif - mMap.insert(pqm_pair(LLPQMKey<DATA_TYPE>(priority, data), data)); - } - - BOOL pop(DATA_TYPE *datap) - { - pqm_iter iter; - iter = mMap.begin(); - if (iter == mMap.end()) - { - return FALSE; - } - *datap = (*(iter)).second; - mMap.erase(iter); - - return TRUE; - } - - void reprioritize(const F32 new_priority, DATA_TYPE data) - { - pqm_iter iter; - F32 cur_priority = mGetPriority(data); - LLPQMKey<DATA_TYPE> cur_key(cur_priority, data); - iter = mMap.find(cur_key); - if (iter == mMap.end()) - { - LL_WARNS() << "Data not on priority queue!" << LL_ENDL; - // OK, try iterating through all of the data and seeing if we just screwed up the priority - // somehow. - for (pqm_pair pair : mMap) - { - if (pair.second == data) - { - LL_ERRS() << "Data on priority queue but priority not matched!" << LL_ENDL; - } - } - return; - } - - mMap.erase(iter); - mSetPriority(data, new_priority); - push(new_priority, data); - } - - S32 getLength() const - { - return (S32)mMap.size(); - } - - // Hack: public for use by the transfer manager, ugh. - std::map<LLPQMKey<DATA_TYPE>, DATA_TYPE> mMap; -protected: - void (*mSetPriority)(DATA_TYPE &data, const F32 priority); - F32 (*mGetPriority)(DATA_TYPE &data); -}; - -#endif // LL_LLPRIQUEUEMAP_H +/**
+ * @file llpriqueuemap.h
+ * @brief Priority queue implementation
+ *
+ * $LicenseInfo:firstyear=2003&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLPRIQUEUEMAP_H
+#define LL_LLPRIQUEUEMAP_H
+
+#include <map>
+
+//
+// Priority queue, implemented under the hood as a
+// map. Needs to be done this way because none of the
+// standard STL containers provide a representation
+// where it's easy to reprioritize.
+//
+
+template <class DATA>
+class LLPQMKey
+{
+public:
+ LLPQMKey(const F32 priority, DATA data) : mPriority(priority), mData(data)
+ {
+ }
+
+ bool operator<(const LLPQMKey &b) const
+ {
+ if (mPriority > b.mPriority)
+ {
+ return true;
+ }
+ if (mPriority < b.mPriority)
+ {
+ return false;
+ }
+ if (mData > b.mData)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ F32 mPriority;
+ DATA mData;
+};
+
+template <class DATA_TYPE>
+class LLPriQueueMap
+{
+public:
+ typedef typename std::map<LLPQMKey<DATA_TYPE>, DATA_TYPE>::iterator pqm_iter;
+ typedef std::pair<LLPQMKey<DATA_TYPE>, DATA_TYPE> pqm_pair;
+ typedef void (*set_pri_fn)(DATA_TYPE &data, const F32 priority);
+ typedef F32 (*get_pri_fn)(DATA_TYPE &data);
+
+
+ LLPriQueueMap(set_pri_fn set_pri, get_pri_fn get_pri) : mSetPriority(set_pri), mGetPriority(get_pri)
+ {
+ }
+
+ void push(const F32 priority, DATA_TYPE data)
+ {
+#ifdef _DEBUG
+ pqm_iter iter = mMap.find(LLPQMKey<DATA_TYPE>(priority, data));
+ if (iter != mMap.end())
+ {
+ LL_ERRS() << "Pushing already existing data onto queue!" << LL_ENDL;
+ }
+#endif
+ mMap.insert(pqm_pair(LLPQMKey<DATA_TYPE>(priority, data), data));
+ }
+
+ bool pop(DATA_TYPE *datap)
+ {
+ pqm_iter iter;
+ iter = mMap.begin();
+ if (iter == mMap.end())
+ {
+ return false;
+ }
+ *datap = (*(iter)).second;
+ mMap.erase(iter);
+
+ return true;
+ }
+
+ void reprioritize(const F32 new_priority, DATA_TYPE data)
+ {
+ pqm_iter iter;
+ F32 cur_priority = mGetPriority(data);
+ LLPQMKey<DATA_TYPE> cur_key(cur_priority, data);
+ iter = mMap.find(cur_key);
+ if (iter == mMap.end())
+ {
+ LL_WARNS() << "Data not on priority queue!" << LL_ENDL;
+ // OK, try iterating through all of the data and seeing if we just screwed up the priority
+ // somehow.
+ for (pqm_pair pair : mMap)
+ {
+ if (pair.second == data)
+ {
+ LL_ERRS() << "Data on priority queue but priority not matched!" << LL_ENDL;
+ }
+ }
+ return;
+ }
+
+ mMap.erase(iter);
+ mSetPriority(data, new_priority);
+ push(new_priority, data);
+ }
+
+ S32 getLength() const
+ {
+ return (S32)mMap.size();
+ }
+
+ // Hack: public for use by the transfer manager, ugh.
+ std::map<LLPQMKey<DATA_TYPE>, DATA_TYPE> mMap;
+protected:
+ void (*mSetPriority)(DATA_TYPE &data, const F32 priority);
+ F32 (*mGetPriority)(DATA_TYPE &data);
+};
+
+#endif // LL_LLPRIQUEUEMAP_H
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 2208b33b94..80413bb841 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -1,1358 +1,1358 @@ -/** - * @file llprocess.cpp - * @brief Utility class for launching, terminating, and tracking the state of processes. - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" -#include "llprocess.h" -#include "llsdutil.h" -#include "llsdserialize.h" -#include "llsingleton.h" -#include "llstring.h" -#include "stringize.h" -#include "llapr.h" -#include "apr_signal.h" -#include "llevents.h" -#include "llexception.h" - -#include <boost/bind.hpp> -#include <boost/asio/streambuf.hpp> -#include <boost/asio/buffers_iterator.hpp> -#include <iostream> -#include <stdexcept> -#include <limits> -#include <algorithm> -#include <vector> -#include <typeinfo> -#include <utility> - -/***************************************************************************** -* Helpers -*****************************************************************************/ -static const char* whichfile_[] = { "stdin", "stdout", "stderr" }; -static std::string empty; -static LLProcess::Status interpret_status(int status); -static std::string getDesc(const LLProcess::Params& params); - -static std::string whichfile(LLProcess::FILESLOT index) -{ - if (index < LL_ARRAY_SIZE(whichfile_)) - return whichfile_[index]; - return STRINGIZE("file slot " << index); -} - -/** - * Ref-counted "mainloop" listener. As long as there are still outstanding - * LLProcess objects, keep listening on "mainloop" so we can keep polling APR - * for process status. - */ -class LLProcessListener -{ - LOG_CLASS(LLProcessListener); -public: - LLProcessListener(): - mCount(0) - {} - - void addPoll(const LLProcess&) - { - // Unconditionally increment mCount. If it was zero before - // incrementing, listen on "mainloop". - if (mCount++ == 0) - { - LL_DEBUGS("LLProcess") << "listening on \"mainloop\"" << LL_ENDL; - mConnection = LLEventPumps::instance().obtain("mainloop") - .listen("LLProcessListener", boost::bind(&LLProcessListener::tick, this, _1)); - } - } - - void dropPoll(const LLProcess&) - { - // Unconditionally decrement mCount. If it's zero after decrementing, - // stop listening on "mainloop". - if (--mCount == 0) - { - LL_DEBUGS("LLProcess") << "disconnecting from \"mainloop\"" << LL_ENDL; - mConnection.disconnect(); - } - } - -private: - /// called once per frame by the "mainloop" LLEventPump - bool tick(const LLSD&) - { - // Tell APR to sense whether each registered LLProcess is still - // running and call handle_status() appropriately. We should be able - // to get the same info from an apr_proc_wait(APR_NOWAIT) call; but at - // least in APR 1.4.2, testing suggests that even with APR_NOWAIT, - // apr_proc_wait() blocks the caller. We can't have that in the - // viewer. Hence the callback rigmarole. (Once we update APR, it's - // probably worth testing again.) Also -- although there's an - // apr_proc_other_child_refresh() call, i.e. get that information for - // one specific child, it accepts an 'apr_other_child_rec_t*' that's - // mentioned NOWHERE else in the documentation or header files! I - // would use the specific call in LLProcess::getStatus() if I knew - // how. As it is, each call to apr_proc_other_child_refresh_all() will - // call callbacks for ALL still-running child processes. That's why we - // centralize such calls, using "mainloop" to ensure it happens once - // per frame, and refcounting running LLProcess objects to remain - // registered only while needed. - LL_DEBUGS("LLProcess") << "calling apr_proc_other_child_refresh_all()" << LL_ENDL; - apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); - return false; - } - - /// If this object is destroyed before mCount goes to zero, stop - /// listening on "mainloop" anyway. - LLTempBoundListener mConnection; - unsigned mCount; -}; -static LLProcessListener sProcessListener; - -/***************************************************************************** -* WritePipe and ReadPipe -*****************************************************************************/ -LLProcess::BasePipe::~BasePipe() {} -const LLProcess::BasePipe::size_type - // use funky syntax to call max() to avoid blighted max() macros - LLProcess::BasePipe::npos((std::numeric_limits<LLProcess::BasePipe::size_type>::max)()); - -class WritePipeImpl: public LLProcess::WritePipe -{ - LOG_CLASS(WritePipeImpl); -public: - WritePipeImpl(const std::string& desc, apr_file_t* pipe): - mDesc(desc), - mPipe(pipe), - // Essential to initialize our std::ostream with our special streambuf! - mStream(&mStreambuf) - { - mConnection = LLEventPumps::instance().obtain("mainloop") - .listen(LLEventPump::inventName("WritePipe"), - boost::bind(&WritePipeImpl::tick, this, _1)); - -#if ! LL_WINDOWS - // We can't count on every child process reading everything we try to - // write to it. And if the child terminates with WritePipe data still - // pending, unless we explicitly suppress it, Posix will hit us with - // SIGPIPE. That would terminate the viewer, boom. "Ignoring" it means - // APR gets the correct errno, passes it back to us, we log it, etc. - signal(SIGPIPE, SIG_IGN); -#endif - } - - virtual std::ostream& get_ostream() { return mStream; } - virtual size_type size() const { return mStreambuf.size(); } - - bool tick(const LLSD&) - { - typedef boost::asio::streambuf::const_buffers_type const_buffer_sequence; - // If there's anything to send, try to send it. - std::size_t total(mStreambuf.size()), consumed(0); - if (total) - { - const_buffer_sequence bufs = mStreambuf.data(); - // In general, our streambuf might contain a number of different - // physical buffers; iterate over those. - bool keepwriting = true; - for (const_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); - bufi != bufend && keepwriting; ++bufi) - { - // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents - // Although apr_file_write() accepts const void*, we - // manipulate const char* so we can increment the pointer. - const char* remainptr = boost::asio::buffer_cast<const char*>(*bufi); - std::size_t remainlen = boost::asio::buffer_size(*bufi); - while (remainlen) - { - // Tackle the current buffer in discrete chunks. On - // Windows, we've observed strange failures when trying to - // write big lengths (~1 MB) in a single operation. Even a - // 32K chunk seems too large. At some point along the way - // apr_file_write() returns 11 (Resource temporarily - // unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- - // even though it did write the chunk! Our next write - // attempt retries with the same chunk, resulting in the - // chunk being duplicated at the child end. Using smaller - // chunks is empirically more reliable. - std::size_t towrite((std::min)(remainlen, std::size_t(4*1024))); - apr_size_t written(towrite); - apr_status_t err = apr_file_write(mPipe, remainptr, &written); - // EAGAIN is exactly what we want from a nonblocking pipe. - // Rather than waiting for data, it should return immediately. - if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) - { - LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc - << " got " << err << ":" << LL_ENDL; - ll_apr_warn_status(err); - } - - // 'written' is modified to reflect the number of bytes actually - // written. Make sure we consume those later. (Don't consume them - // now, that would invalidate the buffer iterator sequence!) - consumed += written; - // don't forget to advance to next chunk of current buffer - remainptr += written; - remainlen -= written; - - char msgbuf[512]; - LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite - << " bytes to " << mDesc - << " (original " << total << ")," - << " code " << err << ": " - << apr_strerror(err, msgbuf, sizeof(msgbuf)) - << LL_ENDL; - - // The parent end of this pipe is nonblocking. If we weren't able - // to write everything we wanted, don't keep banging on it -- that - // won't change until the child reads some. Wait for next tick(). - if (written < towrite) - { - keepwriting = false; // break outer loop over buffers too - break; - } - } // next chunk of current buffer - } // next buffer - // In all, we managed to write 'consumed' bytes. Remove them from the - // streambuf so we don't keep trying to send them. This could be - // anywhere from 0 up to mStreambuf.size(); anything we haven't yet - // sent, we'll try again later. - mStreambuf.consume(consumed); - } - - return false; - } - -private: - std::string mDesc; - apr_file_t* mPipe; - LLTempBoundListener mConnection; - boost::asio::streambuf mStreambuf; - std::ostream mStream; -}; - -class ReadPipeImpl: public LLProcess::ReadPipe -{ - LOG_CLASS(ReadPipeImpl); -public: - ReadPipeImpl(const std::string& desc, apr_file_t* pipe, LLProcess::FILESLOT index): - mDesc(desc), - mPipe(pipe), - mIndex(index), - // Essential to initialize our std::istream with our special streambuf! - mStream(&mStreambuf), - mPump("ReadPipe", true), // tweak name as needed to avoid collisions - mLimit(0), - mEOF(false) - { - mConnection = LLEventPumps::instance().obtain("mainloop") - .listen(LLEventPump::inventName("ReadPipe"), - boost::bind(&ReadPipeImpl::tick, this, _1)); - } - - ~ReadPipeImpl() - { - if (mConnection.connected()) - { - mConnection.disconnect(); - } - } - - // Much of the implementation is simply connecting the abstract virtual - // methods with implementation data concealed from the base class. - virtual std::istream& get_istream() { return mStream; } - virtual std::string getline() { return LLProcess::getline(mStream); } - virtual LLEventPump& getPump() { return mPump; } - virtual void setLimit(size_type limit) { mLimit = limit; } - virtual size_type getLimit() const { return mLimit; } - virtual size_type size() const { return mStreambuf.size(); } - - virtual std::string read(size_type len) - { - // Read specified number of bytes into a buffer. - size_type readlen((std::min)(size(), len)); - // Formally, &buffer[0] is invalid for a vector of size() 0. Exit - // early in that situation. - if (! readlen) - return ""; - // Make a buffer big enough. - std::vector<char> buffer(readlen); - mStream.read(&buffer[0], readlen); - // Since we've already clamped 'readlen', we can think of no reason - // why mStream.read() should read fewer than 'readlen' bytes. - // Nonetheless, use the actual retrieved length. - return std::string(&buffer[0], mStream.gcount()); - } - - virtual std::string peek(size_type offset=0, size_type len=npos) const - { - // Constrain caller's offset and len to overlap actual buffer content. - std::size_t real_offset = (std::min)(mStreambuf.size(), std::size_t(offset)); - size_type want_end = (len == npos)? npos : (real_offset + len); - std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(want_end)); - boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); - return std::string(boost::asio::buffers_begin(cbufs) + real_offset, - boost::asio::buffers_begin(cbufs) + real_end); - } - - virtual size_type find(const std::string& seek, size_type offset=0) const - { - // If we're passing a string of length 1, use find(char), which can - // use an O(n) std::find() rather than the O(n^2) std::search(). - if (seek.length() == 1) - { - return find(seek[0], offset); - } - - // If offset is beyond the whole buffer, can't even construct a valid - // iterator range; can't possibly find the string we seek. - if (offset > mStreambuf.size()) - { - return npos; - } - - boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); - boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type> - begin(boost::asio::buffers_begin(cbufs)), - end (boost::asio::buffers_end(cbufs)), - found(std::search(begin + offset, end, seek.begin(), seek.end())); - return (found == end)? npos : (found - begin); - } - - virtual size_type find(char seek, size_type offset=0) const - { - // If offset is beyond the whole buffer, can't even construct a valid - // iterator range; can't possibly find the char we seek. - if (offset > mStreambuf.size()) - { - return npos; - } - - boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); - boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type> - begin(boost::asio::buffers_begin(cbufs)), - end (boost::asio::buffers_end(cbufs)), - found(std::find(begin + offset, end, seek)); - return (found == end)? npos : (found - begin); - } - - bool tick(const LLSD&) - { - // Once we've hit EOF, skip all the rest of this. - if (mEOF) - return false; - - typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence; - // Try, every time, to read into our streambuf. In fact, we have no - // idea how much data the child might be trying to send: keep trying - // until we're convinced we've temporarily exhausted the pipe. - enum PipeState { RETRY, EXHAUSTED, CLOSED }; - PipeState state = RETRY; - std::size_t committed(0); - do - { - // attempt to read an arbitrary size - mutable_buffer_sequence bufs = mStreambuf.prepare(4096); - // In general, the mutable_buffer_sequence returned by prepare() might - // contain a number of different physical buffers; iterate over those. - std::size_t tocommit(0); - for (mutable_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); - bufi != bufend; ++bufi) - { - // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents - std::size_t toread(boost::asio::buffer_size(*bufi)); - apr_size_t gotten(toread); - apr_status_t err = apr_file_read(mPipe, - boost::asio::buffer_cast<void*>(*bufi), - &gotten); - // EAGAIN is exactly what we want from a nonblocking pipe. - // Rather than waiting for data, it should return immediately. - if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) - { - // Handle EOF specially: it's part of normal-case processing. - if (err == APR_EOF) - { - LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL; - } - else - { - LL_WARNS("LLProcess") << "apr_file_read(" << toread << ") on " << mDesc - << " got " << err << ":" << LL_ENDL; - ll_apr_warn_status(err); - } - // Either way, though, we won't need any more tick() calls. - mConnection.disconnect(); - // Ignore any subsequent calls we might get anyway. - mEOF = true; - state = CLOSED; // also break outer retry loop - break; - } - - // 'gotten' was modified to reflect the number of bytes actually - // received. Make sure we commit those later. (Don't commit them - // now, that would invalidate the buffer iterator sequence!) - tocommit += gotten; - LL_DEBUGS("LLProcess") << "filled " << gotten << " of " << toread - << " bytes from " << mDesc << LL_ENDL; - - // The parent end of this pipe is nonblocking. If we weren't even - // able to fill this buffer, don't loop to try to fill the next -- - // that won't change until the child writes more. Wait for next - // tick(). - if (gotten < toread) - { - // break outer retry loop too - state = EXHAUSTED; - break; - } - } - - // Don't forget to "commit" the data! - mStreambuf.commit(tocommit); - committed += tocommit; - - // state is changed from RETRY when we can't fill any one buffer - // of the mutable_buffer_sequence established by the current - // prepare() call -- whether due to error or not enough bytes. - // That is, if state is still RETRY, we've filled every physical - // buffer in the mutable_buffer_sequence. In that case, for all we - // know, the child might have still more data pending -- go for it! - } while (state == RETRY); - - // Once we recognize that the pipe is closed, make one more call to - // listener. The listener might be waiting for a particular substring - // to arrive, or a particular length of data or something. The event - // with "eof" == true announces that nothing further will arrive, so - // use it or lose it. - if (committed || state == CLOSED) - { - // If we actually received new data, publish it on our LLEventPump - // as advertised. Constrain it by mLimit. But show listener the - // actual accumulated buffer size, regardless of mLimit. - size_type datasize((std::min)(mLimit, size_type(mStreambuf.size()))); - mPump.post(LLSDMap - ("data", peek(0, datasize)) - ("len", LLSD::Integer(mStreambuf.size())) - ("slot", LLSD::Integer(mIndex)) - ("name", whichfile(mIndex)) - ("desc", mDesc) - ("eof", state == CLOSED)); - } - - return false; - } - -private: - std::string mDesc; - apr_file_t* mPipe; - LLProcess::FILESLOT mIndex; - LLTempBoundListener mConnection; - boost::asio::streambuf mStreambuf; - std::istream mStream; - LLEventStream mPump; - size_type mLimit; - bool mEOF; -}; - -/***************************************************************************** -* LLProcess itself -*****************************************************************************/ -/// Need an exception to avoid constructing an invalid LLProcess object, but -/// internal use only -struct LLProcessError: public LLException -{ - LLProcessError(const std::string& msg): LLException(msg) {} -}; - -LLProcessPtr LLProcess::create(const LLSDOrParams& params) -{ - try - { - return LLProcessPtr(new LLProcess(params)); - } - catch (const LLProcessError& e) - { - LL_WARNS("LLProcess") << e.what() << LL_ENDL; - - // If caller is requesting an event on process termination, send one - // indicating bad launch. This may prevent someone waiting forever for - // a termination post that can't arrive because the child never - // started. - if (params.postend.isProvided()) - { - LLEventPumps::instance().obtain(params.postend) - .post(LLSDMap - // no "id" - ("desc", getDesc(params)) - ("state", LLProcess::UNSTARTED) - // no "data" - ("string", e.what()) - ); - } - - return LLProcessPtr(); - } -} - -/// Call an apr function returning apr_status_t. On failure, log warning and -/// throw LLProcessError mentioning the function call that produced that -/// result. -#define chkapr(func) \ - if (ll_apr_warn_status(func)) \ - throw LLProcessError(#func " failed") - -LLProcess::LLProcess(const LLSDOrParams& params): - mAutokill(params.autokill), - // Because 'autokill' originally meant both 'autokill' and 'attached', to - // preserve existing semantics, we promise that mAttached defaults to the - // same setting as mAutokill. - mAttached(params.attached.isProvided()? params.attached : params.autokill), - mPool(NULL), - mPipes(NSLOTS) -{ - // Hmm, when you construct a ptr_vector with a size, it merely reserves - // space, it doesn't actually make it that big. Explicitly make it bigger. - // Because of ptr_vector's odd semantics, have to push_back(0) the right - // number of times! resize() wants to default-construct new BasePipe - // instances, which fails because it's pure virtual. But because of the - // constructor call, these push_back() calls should require no new - // allocation. - for (size_t i = 0; i < mPipes.capacity(); ++i) - mPipes.push_back(0); - - if (! params.validateBlock(true)) - { - LLTHROW(LLProcessError(STRINGIZE("not launched: failed parameter validation\n" - << LLSDNotationStreamer(params)))); - } - - mPostend = params.postend; - - apr_pool_create(&mPool, gAPRPoolp); - if (!mPool) - { - LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool"))); - } - - apr_procattr_t *procattr = NULL; - chkapr(apr_procattr_create(&procattr, mPool)); - - // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to - // constrain the set of handles passed to the child process. Before we - // changed to APR, the Windows implementation of LLProcessLauncher called - // CreateProcess(bInheritHandles=FALSE), meaning to pass NO open handles - // to the child process. Now that we support pipes, though, we must allow - // apr_proc_create() to pass bInheritHandles=TRUE. But without taking - // special pains, that causes trouble in a number of ways, due to the fact - // that the viewer is constantly opening and closing files -- most of - // which CreateProcess() passes to every child process! -#if ! defined(APR_HAS_PROCATTR_CONSTRAIN_HANDLE_SET) - // Our special preprocessor symbol isn't even defined -- wrong APR - LL_WARNS("LLProcess") << "This version of APR lacks Linden " - << "apr_procattr_constrain_handle_set() extension" << LL_ENDL; -#else - chkapr(apr_procattr_constrain_handle_set(procattr, 1)); -#endif - - // For which of stdin, stdout, stderr should we create a pipe to the - // child? In the viewer, there are only a couple viable - // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx - // handle (APR_NO_PIPE, e.g. for stdout, stderr), or create a pipe that's - // blocking on the child end but nonblocking at the viewer end - // (APR_CHILD_BLOCK). - // Other major options could include explicitly creating a single APR pipe - // and passing it as both stdout and stderr (apr_procattr_child_out_set(), - // apr_procattr_child_err_set()), or accepting a filename, opening it and - // passing that apr_file_t (simple <, >, 2> redirect emulation). - std::vector<apr_int32_t> select; - for (const FileParam& fparam : params.files) - { - // Every iteration, we're going to append an item to 'select'. At the - // top of the loop, its size() is, in effect, an index. Use that to - // pick a string description for messages. - std::string which(whichfile(FILESLOT(select.size()))); - if (fparam.type().empty()) // inherit our file descriptor - { - select.push_back(APR_NO_PIPE); - } - else if (fparam.type() == "pipe") // anonymous pipe - { - if (! fparam.name().empty()) - { - LL_WARNS("LLProcess") << "For " << params.executable() - << ": internal names for reusing pipes ('" - << fparam.name() << "' for " << which - << ") are not yet supported -- creating distinct pipe" - << LL_ENDL; - } - // The viewer can't block for anything: the parent end MUST be - // nonblocking. As the APR documentation itself points out, it - // makes very little sense to set nonblocking I/O for the child - // end of a pipe: only a specially-written child could deal with - // that. - select.push_back(APR_CHILD_BLOCK); - } - else - { - LLTHROW(LLProcessError(STRINGIZE("For " << params.executable() - << ": unsupported FileParam for " << which - << ": type='" << fparam.type() - << "', name='" << fparam.name() << "'"))); - } - } - // By default, pass APR_NO_PIPE for unspecified slots. - while (select.size() < NSLOTS) - { - select.push_back(APR_NO_PIPE); - } - chkapr(apr_procattr_io_set(procattr, select[STDIN], select[STDOUT], select[STDERR])); - - // Thumbs down on implicitly invoking the shell to invoke the child. From - // our point of view, the other major alternative to APR_PROGRAM_PATH - // would be APR_PROGRAM_ENV: still copy environment, but require full - // executable pathname. I don't see a downside to searching the PATH, - // though: if our caller wants (e.g.) a specific Python interpreter, s/he - // can still pass the full pathname. - chkapr(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH)); - // YES, do extra work if necessary to report child exec() failures back to - // parent process. - chkapr(apr_procattr_error_check_set(procattr, 1)); - // Do not start a non-autokill child in detached state. On Posix - // platforms, this setting attempts to daemonize the new child, closing - // std handles and the like, and that's a bit more detachment than we - // want. autokill=false just means not to implicitly kill the child when - // the parent terminates! -// chkapr(apr_procattr_detach_set(procattr, mAutokill? 0 : 1)); - - if (mAutokill) - { -#if ! defined(APR_HAS_PROCATTR_AUTOKILL_SET) - // Our special preprocessor symbol isn't even defined -- wrong APR - LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL; -#elif ! APR_HAS_PROCATTR_AUTOKILL_SET - // Symbol is defined, but to 0: expect apr_procattr_autokill_set() to - // return APR_ENOTIMPL. -#else // APR_HAS_PROCATTR_AUTOKILL_SET nonzero - ll_apr_warn_status(apr_procattr_autokill_set(procattr, 1)); -#endif - } - - // In preparation for calling apr_proc_create(), we collect a number of - // const char* pointers obtained from std::string::c_str(). Turns out - // LLInitParam::Block's helpers Optional, Mandatory, Multiple et al. - // guarantee that converting to the wrapped type (std::string in our - // case), e.g. by calling operator(), returns a reference to *the same - // instance* of the wrapped type that's stored in our Block subclass. - // That's important! We know 'params' persists throughout this method - // call; but without that guarantee, we'd have to assume that converting - // one of its members to std::string might return a different (temp) - // instance. Capturing the c_str() from a temporary std::string is Bad Bad - // Bad. But armed with this knowledge, when you see params.cwd().c_str(), - // grit your teeth and smile and carry on. - - if (params.cwd.isProvided()) - { - chkapr(apr_procattr_dir_set(procattr, params.cwd().c_str())); - } - - // create an argv vector for the child process - std::vector<const char*> argv; - - // Add the executable path. See above remarks about c_str(). - argv.push_back(params.executable().c_str()); - - // Add arguments. See above remarks about c_str(). - for (const std::string& arg : params.args) - { - argv.push_back(arg.c_str()); - } - - // terminate with a null pointer - argv.push_back(NULL); - - // Launch! The NULL would be the environment block, if we were passing - // one. Hand-expand chkapr() macro so we can fill in the actual command - // string instead of the variable names. - if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, - mPool))) - { - LLTHROW(LLProcessError(STRINGIZE(params << " failed"))); - } - - // arrange to call status_callback() - apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, - mPool); - // and make sure we poll it once per "mainloop" tick - sProcessListener.addPoll(*this); - mStatus.mState = RUNNING; - - mDesc = STRINGIZE(getDesc(params) << " (" << mProcess.pid << ')'); - LL_INFOS("LLProcess") << mDesc << ": launched " << params << LL_ENDL; - - // Unless caller explicitly turned off autokill (child should persist), - // take steps to terminate the child. This is all suspenders-and-belt: in - // theory our destructor should kill an autokill child, but in practice - // that doesn't always work (e.g. VWR-21538). - if (mAutokill) - { -/*==========================================================================*| - // NO: There may be an APR bug, not sure -- but at least on Mac, when - // gAPRPoolp is destroyed, OUR process receives SIGTERM! Apparently - // either our own PID is getting into the list of processes to kill() - // (unlikely), or somehow one of those PIDs is getting zeroed first, - // so that kill() sends SIGTERM to the whole process group -- this - // process included. I'd have to build and link with a debug version - // of APR to know for sure. It's too bad: this mechanism would be just - // right for dealing with static autokill LLProcessPtr variables, - // which aren't destroyed until after APR is no longer available. - - // Tie the lifespan of this child process to the lifespan of our APR - // pool: on destruction of the pool, forcibly kill the process. Tell - // APR to try SIGTERM and suspend 3 seconds. If that didn't work, use - // SIGKILL. - apr_pool_note_subprocess(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT); -|*==========================================================================*/ - - // On Windows, associate the new child process with our Job Object. - autokill(); - } - - // Instantiate the proper pipe I/O machinery - // want to be able to point to apr_proc_t::in, out, err by index - typedef apr_file_t* apr_proc_t::*apr_proc_file_ptr; - static apr_proc_file_ptr members[] = - { &apr_proc_t::in, &apr_proc_t::out, &apr_proc_t::err }; - for (size_t i = 0; i < NSLOTS; ++i) - { - if (select[i] != APR_CHILD_BLOCK) - continue; - std::string desc(STRINGIZE(mDesc << ' ' << whichfile(FILESLOT(i)))); - apr_file_t* pipe(mProcess.*(members[i])); - if (i == STDIN) - { - mPipes.replace(i, new WritePipeImpl(desc, pipe)); - } - else - { - mPipes.replace(i, new ReadPipeImpl(desc, pipe, FILESLOT(i))); - } - // Removed temporaily for Xcode 7 build tests: error was: - // "error: expression with side effects will be evaluated despite - // being used as an operand to 'typeid' [-Werror,-Wpotentially-evaluated-expression]"" - //LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name() - // << "('" << desc << "')" << LL_ENDL; - } -} - -// Helper to obtain a description string, given a Params block -static std::string getDesc(const LLProcess::Params& params) -{ - // If caller specified a description string, by all means use it. - if (params.desc.isProvided()) - return params.desc; - - // Caller didn't say. Use the executable name -- but use just the filename - // part. On Mac, for instance, full pathnames get cumbersome. - return LLProcess::basename(params.executable); -} - -//static -std::string LLProcess::basename(const std::string& path) -{ - // If there are Linden utility functions to manipulate pathnames, I - // haven't found them -- and for this usage, Boost.Filesystem seems kind - // of heavyweight. - std::string::size_type delim = path.find_last_of("\\/"); - // If path contains no pathname delimiters, return the whole thing. - if (delim == std::string::npos) - return path; - - // Return just the part beyond the last delimiter. - return path.substr(delim + 1); -} - -LLProcess::~LLProcess() -{ - // In the Linden viewer, there's at least one static LLProcessPtr. Its - // destructor will be called *after* ll_cleanup_apr(). In such a case, - // unregistering is pointless (and fatal!) -- and kill(), which also - // relies on APR, is impossible. - if (! gAPRPoolp) - return; - - // Only in state RUNNING are we registered for callback. In UNSTARTED we - // haven't yet registered. And since receiving the callback is the only - // way we detect child termination, we only change from state RUNNING at - // the same time we unregister. - if (mStatus.mState == RUNNING) - { - // We're still registered for a callback: unregister. Do it before - // we even issue the kill(): even if kill() somehow prompted an - // instantaneous callback (unlikely), this object is going away! Any - // information updated in this object by such a callback is no longer - // available to any consumer anyway. - apr_proc_other_child_unregister(this); - // One less LLProcess to poll for - sProcessListener.dropPoll(*this); - } - - if (mAttached) - { - kill("destructor"); - } - - if (mPool) - { - apr_pool_destroy(mPool); - mPool = NULL; - } -} - -bool LLProcess::kill(const std::string& who) -{ - if (isRunning()) - { - LL_INFOS("LLProcess") << who << " killing " << mDesc << LL_ENDL; - -#if LL_WINDOWS - int sig = -1; -#else // Posix - int sig = SIGTERM; -#endif - - ll_apr_warn_status(apr_proc_kill(&mProcess, sig)); - } - - return ! isRunning(); -} - -//static -bool LLProcess::kill(const LLProcessPtr& p, const std::string& who) -{ - if (! p) - return true; // process dead! (was never running) - return p->kill(who); -} - -bool LLProcess::isRunning() const -{ - return getStatus().mState == RUNNING; -} - -//static -bool LLProcess::isRunning(const LLProcessPtr& p) -{ - if (! p) - return false; - return p->isRunning(); -} - -LLProcess::Status LLProcess::getStatus() const -{ - return mStatus; -} - -//static -LLProcess::Status LLProcess::getStatus(const LLProcessPtr& p) -{ - if (! p) - { - // default-constructed Status has mState == UNSTARTED - return Status(); - } - return p->getStatus(); -} - -std::string LLProcess::getStatusString() const -{ - return getStatusString(getStatus()); -} - -std::string LLProcess::getStatusString(const Status& status) const -{ - return getStatusString(mDesc, status); -} - -//static -std::string LLProcess::getStatusString(const std::string& desc, const LLProcessPtr& p) -{ - if (! p) - { - // default-constructed Status has mState == UNSTARTED - return getStatusString(desc, Status()); - } - return desc + " " + p->getStatusString(); -} - -//static -std::string LLProcess::getStatusString(const std::string& desc, const Status& status) -{ - if (status.mState == UNSTARTED) - return desc + " was never launched"; - - if (status.mState == RUNNING) - return desc + " running"; - - if (status.mState == EXITED) - return STRINGIZE(desc << " exited with code " << status.mData); - - if (status.mState == KILLED) -#if LL_WINDOWS - return STRINGIZE(desc << " killed with exception " << std::hex << status.mData); -#else - return STRINGIZE(desc << " killed by signal " << status.mData - << " (" << apr_signal_description_get(status.mData) << ")"); -#endif - - return STRINGIZE(desc << " in unknown state " << status.mState << " (" << status.mData << ")"); -} - -// Classic-C-style APR callback -void LLProcess::status_callback(int reason, void* data, int status) -{ - // Our only role is to bounce this static method call back into object - // space. - static_cast<LLProcess*>(data)->handle_status(reason, status); -} - -#define tabent(symbol) { symbol, #symbol } -static struct ReasonCode -{ - int code; - const char* name; -} reasons[] = -{ - tabent(APR_OC_REASON_DEATH), - tabent(APR_OC_REASON_UNWRITABLE), - tabent(APR_OC_REASON_RESTART), - tabent(APR_OC_REASON_UNREGISTER), - tabent(APR_OC_REASON_LOST), - tabent(APR_OC_REASON_RUNNING) -}; -#undef tabent - -// Object-oriented callback -void LLProcess::handle_status(int reason, int status) -{ - { - // This odd appearance of LL_DEBUGS is just to bracket a lookup that will - // only be performed if in fact we're going to produce the log message. - LL_DEBUGS("LLProcess") << empty; - std::string reason_str; - for (const ReasonCode& rcp : reasons) - { - if (reason == rcp.code) - { - reason_str = rcp.name; - break; - } - } - if (reason_str.empty()) - { - reason_str = STRINGIZE("unknown reason " << reason); - } - LL_CONT << mDesc << ": handle_status(" << reason_str << ", " << status << ")" << LL_ENDL; - } - - if (! (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST)) - { - // We're only interested in the call when the child terminates. - return; - } - - // Somewhat oddly, APR requires that you explicitly unregister even when - // it already knows the child has terminated. We must pass the same 'data' - // pointer as for the register() call, which was our 'this'. - apr_proc_other_child_unregister(this); - // don't keep polling for a terminated process - sProcessListener.dropPoll(*this); - // We overload mStatus.mState to indicate whether the child is registered - // for APR callback: only RUNNING means registered. Track that we've - // unregistered. We know the child has terminated; might be EXITED or - // KILLED; refine below. - mStatus.mState = EXITED; - - // Make last-gasp calls for each of the ReadPipes we have on hand. Since - // they're listening on "mainloop", we can be sure they'll eventually - // collect all pending data from the child. But we want to be able to - // guarantee to our consumer that by the time we post on the "postend" - // LLEventPump, our ReadPipes are already buffering all the data there - // will ever be from the child. That lets the "postend" listener decide - // what to do with that final data. - for (size_t i = 0; i < mPipes.size(); ++i) - { - std::string error; - ReadPipeImpl* ppipe = getPipePtr<ReadPipeImpl>(error, FILESLOT(i)); - if (ppipe) - { - static LLSD trivial; - ppipe->tick(trivial); - } - } - -// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); - // It's just wrong to call apr_proc_wait() here. The only way APR knows to - // call us with APR_OC_REASON_DEATH is that it's already reaped this child - // process, so calling wait() will only produce "huh?" from the OS. We - // must rely on the status param passed in, which unfortunately comes - // straight from the OS wait() call, which means we have to decode it by - // hand. - mStatus = interpret_status(status); - LL_INFOS("LLProcess") << getStatusString() << LL_ENDL; - - // If caller requested notification on child termination, send it. - if (! mPostend.empty()) - { - LLEventPumps::instance().obtain(mPostend) - .post(LLSDMap - ("id", getProcessID()) - ("desc", mDesc) - ("state", mStatus.mState) - ("data", mStatus.mData) - ("string", getStatusString()) - ); - } -} - -LLProcess::id LLProcess::getProcessID() const -{ - return mProcess.pid; -} - -LLProcess::handle LLProcess::getProcessHandle() const -{ -#if LL_WINDOWS - return mProcess.hproc; -#else - return mProcess.pid; -#endif -} - -std::string LLProcess::getPipeName(FILESLOT) const -{ - // LLProcess::FileParam::type "npipe" is not yet implemented - return ""; -} - -template<class PIPETYPE> -PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot) -{ - if (slot >= NSLOTS) - { - error = STRINGIZE(mDesc << " has no slot " << slot); - return NULL; - } - if (mPipes.is_null(slot)) - { - error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a monitored pipe"); - return NULL; - } - // Make sure we dynamic_cast in pointer domain so we can test, rather than - // accepting runtime's exception. - PIPETYPE* ppipe = dynamic_cast<PIPETYPE*>(&mPipes[slot]); - if (! ppipe) - { - error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name()); - return NULL; - } - - error.clear(); - return ppipe; -} - -template <class PIPETYPE> -PIPETYPE& LLProcess::getPipe(FILESLOT slot) -{ - std::string error; - PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot); - if (! wp) - { - LLTHROW(NoPipe(error)); - } - return *wp; -} - -template <class PIPETYPE> -boost::optional<PIPETYPE&> LLProcess::getOptPipe(FILESLOT slot) -{ - std::string error; - PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot); - if (! wp) - { - LL_DEBUGS("LLProcess") << error << LL_ENDL; - return boost::optional<PIPETYPE&>(); - } - return *wp; -} - -LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot) -{ - return getPipe<WritePipe>(slot); -} - -boost::optional<LLProcess::WritePipe&> LLProcess::getOptWritePipe(FILESLOT slot) -{ - return getOptPipe<WritePipe>(slot); -} - -LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot) -{ - return getPipe<ReadPipe>(slot); -} - -boost::optional<LLProcess::ReadPipe&> LLProcess::getOptReadPipe(FILESLOT slot) -{ - return getOptPipe<ReadPipe>(slot); -} - -//static -std::string LLProcess::getline(std::istream& in) -{ - std::string line; - std::getline(in, line); - // Blur the distinction between "\r\n" and plain "\n". std::getline() will - // have eaten the "\n", but we could still end up with a trailing "\r". - std::string::size_type lastpos = line.find_last_not_of("\r"); - if (lastpos != std::string::npos) - { - // Found at least one character that's not a trailing '\r'. SKIP OVER - // IT and erase the rest of the line. - line.erase(lastpos+1); - } - return line; -} - -std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) -{ - if (params.cwd.isProvided()) - { - out << "cd " << LLStringUtil::quote(params.cwd) << ": "; - } - out << LLStringUtil::quote(params.executable); - for (const std::string& arg : params.args) - { - out << ' ' << LLStringUtil::quote(arg); - } - return out; -} - -/***************************************************************************** -* Windows specific -*****************************************************************************/ -#if LL_WINDOWS - -static std::string WindowsErrorString(const std::string& operation); - -void LLProcess::autokill() -{ - // hopefully now handled by apr_procattr_autokill_set() -} - -LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) -{ - // This direct Windows implementation is because we have no access to the - // apr_proc_t struct: we expect it's been destroyed. - if (! h) - return 0; - - DWORD waitresult = WaitForSingleObject(h, 0); - if(waitresult == WAIT_OBJECT_0) - { - // the process has completed. - if (! desc.empty()) - { - DWORD status = 0; - if (! GetExitCodeProcess(h, &status)) - { - LL_WARNS("LLProcess") << desc << " terminated, but " - << WindowsErrorString("GetExitCodeProcess()") << LL_ENDL; - } - { - LL_INFOS("LLProcess") << getStatusString(desc, interpret_status(status)) - << LL_ENDL; - } - } - CloseHandle(h); - return 0; - } - - return h; -} - -static LLProcess::Status interpret_status(int status) -{ - LLProcess::Status result; - - // This bit of code is cribbed from apr/threadproc/win32/proc.c, a - // function (unfortunately static) called why_from_exit_code(): - /* See WinNT.h STATUS_ACCESS_VIOLATION and family for how - * this class of failures was determined - */ - if ((status & 0xFFFF0000) == 0xC0000000) - { - result.mState = LLProcess::KILLED; - } - else - { - result.mState = LLProcess::EXITED; - } - result.mData = status; - - return result; -} - -/// GetLastError()/FormatMessage() boilerplate -static std::string WindowsErrorString(const std::string& operation) -{ - auto result = GetLastError(); - return STRINGIZE(operation << " failed (" << result << "): " - << windows_message<std::string>(result)); -} - -/***************************************************************************** -* Posix specific -*****************************************************************************/ -#else // Mac and linux - -#include <signal.h> -#include <fcntl.h> -#include <errno.h> -#include <sys/wait.h> - -void LLProcess::autokill() -{ - // What we ought to do here is to: - // 1. create a unique process group and run all autokill children in that - // group (see https://jira.secondlife.com/browse/SWAT-563); - // 2. figure out a way to intercept control when the viewer exits -- - // gracefully or not; - // 3. when the viewer exits, kill off the aforementioned process group. - - // It's point 2 that's troublesome. Although I've seen some signal- - // handling logic in the Posix viewer code, I haven't yet found any bit of - // code that's run no matter how the viewer exits (a try/finally for the - // whole process, as it were). -} - -// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise. -static bool reap_pid(pid_t pid, LLProcess::Status* pstatus=NULL) -{ - LLProcess::Status dummy; - if (! pstatus) - { - // If caller doesn't want to see Status, give us a target anyway so we - // don't have to have a bunch of conditionals. - pstatus = &dummy; - } - - int status = 0; - pid_t wait_result = ::waitpid(pid, &status, WNOHANG); - if (wait_result == pid) - { - *pstatus = interpret_status(status); - return true; - } - if (wait_result == 0) - { - pstatus->mState = LLProcess::RUNNING; - pstatus->mData = 0; - return false; - } - - // Clear caller's Status block; caller must interpret UNSTARTED to mean - // "if this PID was ever valid, it no longer is." - *pstatus = LLProcess::Status(); - - // We've dealt with the success cases: we were able to reap the child - // (wait_result == pid) or it's still running (wait_result == 0). It may - // be that the child terminated but didn't hang around long enough for us - // to reap. In that case we still have no Status to report, but we can at - // least state that it's not running. - if (wait_result == -1 && errno == ECHILD) - { - // No such process -- this may mean we're ignoring SIGCHILD. - return true; - } - - // Uh, should never happen?! - LL_WARNS("LLProcess") << "LLProcess::reap_pid(): waitpid(" << pid << ") returned " - << wait_result << "; not meaningful?" << LL_ENDL; - // If caller is looping until this pid terminates, and if we can't find - // out, better to break the loop than to claim it's still running. - return true; -} - -LLProcess::id LLProcess::isRunning(id pid, const std::string& desc) -{ - // This direct Posix implementation is because we have no access to the - // apr_proc_t struct: we expect it's been destroyed. - if (! pid) - return 0; - - // Check whether the process has exited, and reap it if it has. - LLProcess::Status status; - if(reap_pid(pid, &status)) - { - // the process has exited. - if (! desc.empty()) - { - std::string statstr(desc + " apparently terminated: no status available"); - // We don't just pass UNSTARTED to getStatusString() because, in - // the context of reap_pid(), that state has special meaning. - if (status.mState != UNSTARTED) - { - statstr = getStatusString(desc, status); - } - LL_INFOS("LLProcess") << statstr << LL_ENDL; - } - return 0; - } - - return pid; -} - -static LLProcess::Status interpret_status(int status) -{ - LLProcess::Status result; - - if (WIFEXITED(status)) - { - result.mState = LLProcess::EXITED; - result.mData = WEXITSTATUS(status); - } - else if (WIFSIGNALED(status)) - { - result.mState = LLProcess::KILLED; - result.mData = WTERMSIG(status); - } - else // uh, shouldn't happen? - { - result.mState = LLProcess::EXITED; - result.mData = status; // someone else will have to decode - } - - return result; -} - -#endif // Posix +/**
+ * @file llprocess.cpp
+ * @brief Utility class for launching, terminating, and tracking the state of processes.
+ *
+ * $LicenseInfo:firstyear=2008&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+#include "llprocess.h"
+#include "llsdutil.h"
+#include "llsdserialize.h"
+#include "llsingleton.h"
+#include "llstring.h"
+#include "stringize.h"
+#include "llapr.h"
+#include "apr_signal.h"
+#include "llevents.h"
+#include "llexception.h"
+
+#include <boost/bind.hpp>
+#include <boost/asio/streambuf.hpp>
+#include <boost/asio/buffers_iterator.hpp>
+#include <iostream>
+#include <stdexcept>
+#include <limits>
+#include <algorithm>
+#include <vector>
+#include <typeinfo>
+#include <utility>
+
+/*****************************************************************************
+* Helpers
+*****************************************************************************/
+static const char* whichfile_[] = { "stdin", "stdout", "stderr" };
+static std::string empty;
+static LLProcess::Status interpret_status(int status);
+static std::string getDesc(const LLProcess::Params& params);
+
+static std::string whichfile(LLProcess::FILESLOT index)
+{
+ if (index < LL_ARRAY_SIZE(whichfile_))
+ return whichfile_[index];
+ return STRINGIZE("file slot " << index);
+}
+
+/**
+ * Ref-counted "mainloop" listener. As long as there are still outstanding
+ * LLProcess objects, keep listening on "mainloop" so we can keep polling APR
+ * for process status.
+ */
+class LLProcessListener
+{
+ LOG_CLASS(LLProcessListener);
+public:
+ LLProcessListener():
+ mCount(0)
+ {}
+
+ void addPoll(const LLProcess&)
+ {
+ // Unconditionally increment mCount. If it was zero before
+ // incrementing, listen on "mainloop".
+ if (mCount++ == 0)
+ {
+ LL_DEBUGS("LLProcess") << "listening on \"mainloop\"" << LL_ENDL;
+ mConnection = LLEventPumps::instance().obtain("mainloop")
+ .listen("LLProcessListener", boost::bind(&LLProcessListener::tick, this, _1));
+ }
+ }
+
+ void dropPoll(const LLProcess&)
+ {
+ // Unconditionally decrement mCount. If it's zero after decrementing,
+ // stop listening on "mainloop".
+ if (--mCount == 0)
+ {
+ LL_DEBUGS("LLProcess") << "disconnecting from \"mainloop\"" << LL_ENDL;
+ mConnection.disconnect();
+ }
+ }
+
+private:
+ /// called once per frame by the "mainloop" LLEventPump
+ bool tick(const LLSD&)
+ {
+ // Tell APR to sense whether each registered LLProcess is still
+ // running and call handle_status() appropriately. We should be able
+ // to get the same info from an apr_proc_wait(APR_NOWAIT) call; but at
+ // least in APR 1.4.2, testing suggests that even with APR_NOWAIT,
+ // apr_proc_wait() blocks the caller. We can't have that in the
+ // viewer. Hence the callback rigmarole. (Once we update APR, it's
+ // probably worth testing again.) Also -- although there's an
+ // apr_proc_other_child_refresh() call, i.e. get that information for
+ // one specific child, it accepts an 'apr_other_child_rec_t*' that's
+ // mentioned NOWHERE else in the documentation or header files! I
+ // would use the specific call in LLProcess::getStatus() if I knew
+ // how. As it is, each call to apr_proc_other_child_refresh_all() will
+ // call callbacks for ALL still-running child processes. That's why we
+ // centralize such calls, using "mainloop" to ensure it happens once
+ // per frame, and refcounting running LLProcess objects to remain
+ // registered only while needed.
+ LL_DEBUGS("LLProcess") << "calling apr_proc_other_child_refresh_all()" << LL_ENDL;
+ apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING);
+ return false;
+ }
+
+ /// If this object is destroyed before mCount goes to zero, stop
+ /// listening on "mainloop" anyway.
+ LLTempBoundListener mConnection;
+ unsigned mCount;
+};
+static LLProcessListener sProcessListener;
+
+/*****************************************************************************
+* WritePipe and ReadPipe
+*****************************************************************************/
+LLProcess::BasePipe::~BasePipe() {}
+const LLProcess::BasePipe::size_type
+ // use funky syntax to call max() to avoid blighted max() macros
+ LLProcess::BasePipe::npos((std::numeric_limits<LLProcess::BasePipe::size_type>::max)());
+
+class WritePipeImpl: public LLProcess::WritePipe
+{
+ LOG_CLASS(WritePipeImpl);
+public:
+ WritePipeImpl(const std::string& desc, apr_file_t* pipe):
+ mDesc(desc),
+ mPipe(pipe),
+ // Essential to initialize our std::ostream with our special streambuf!
+ mStream(&mStreambuf)
+ {
+ mConnection = LLEventPumps::instance().obtain("mainloop")
+ .listen(LLEventPump::inventName("WritePipe"),
+ boost::bind(&WritePipeImpl::tick, this, _1));
+
+#if ! LL_WINDOWS
+ // We can't count on every child process reading everything we try to
+ // write to it. And if the child terminates with WritePipe data still
+ // pending, unless we explicitly suppress it, Posix will hit us with
+ // SIGPIPE. That would terminate the viewer, boom. "Ignoring" it means
+ // APR gets the correct errno, passes it back to us, we log it, etc.
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ }
+
+ virtual std::ostream& get_ostream() { return mStream; }
+ virtual size_type size() const { return mStreambuf.size(); }
+
+ bool tick(const LLSD&)
+ {
+ typedef boost::asio::streambuf::const_buffers_type const_buffer_sequence;
+ // If there's anything to send, try to send it.
+ std::size_t total(mStreambuf.size()), consumed(0);
+ if (total)
+ {
+ const_buffer_sequence bufs = mStreambuf.data();
+ // In general, our streambuf might contain a number of different
+ // physical buffers; iterate over those.
+ bool keepwriting = true;
+ for (const_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end());
+ bufi != bufend && keepwriting; ++bufi)
+ {
+ // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents
+ // Although apr_file_write() accepts const void*, we
+ // manipulate const char* so we can increment the pointer.
+ const char* remainptr = boost::asio::buffer_cast<const char*>(*bufi);
+ std::size_t remainlen = boost::asio::buffer_size(*bufi);
+ while (remainlen)
+ {
+ // Tackle the current buffer in discrete chunks. On
+ // Windows, we've observed strange failures when trying to
+ // write big lengths (~1 MB) in a single operation. Even a
+ // 32K chunk seems too large. At some point along the way
+ // apr_file_write() returns 11 (Resource temporarily
+ // unavailable, i.e. EAGAIN) and says it wrote 0 bytes --
+ // even though it did write the chunk! Our next write
+ // attempt retries with the same chunk, resulting in the
+ // chunk being duplicated at the child end. Using smaller
+ // chunks is empirically more reliable.
+ std::size_t towrite((std::min)(remainlen, std::size_t(4*1024)));
+ apr_size_t written(towrite);
+ apr_status_t err = apr_file_write(mPipe, remainptr, &written);
+ // EAGAIN is exactly what we want from a nonblocking pipe.
+ // Rather than waiting for data, it should return immediately.
+ if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err)))
+ {
+ LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc
+ << " got " << err << ":" << LL_ENDL;
+ ll_apr_warn_status(err);
+ }
+
+ // 'written' is modified to reflect the number of bytes actually
+ // written. Make sure we consume those later. (Don't consume them
+ // now, that would invalidate the buffer iterator sequence!)
+ consumed += written;
+ // don't forget to advance to next chunk of current buffer
+ remainptr += written;
+ remainlen -= written;
+
+ char msgbuf[512];
+ LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite
+ << " bytes to " << mDesc
+ << " (original " << total << "),"
+ << " code " << err << ": "
+ << apr_strerror(err, msgbuf, sizeof(msgbuf))
+ << LL_ENDL;
+
+ // The parent end of this pipe is nonblocking. If we weren't able
+ // to write everything we wanted, don't keep banging on it -- that
+ // won't change until the child reads some. Wait for next tick().
+ if (written < towrite)
+ {
+ keepwriting = false; // break outer loop over buffers too
+ break;
+ }
+ } // next chunk of current buffer
+ } // next buffer
+ // In all, we managed to write 'consumed' bytes. Remove them from the
+ // streambuf so we don't keep trying to send them. This could be
+ // anywhere from 0 up to mStreambuf.size(); anything we haven't yet
+ // sent, we'll try again later.
+ mStreambuf.consume(consumed);
+ }
+
+ return false;
+ }
+
+private:
+ std::string mDesc;
+ apr_file_t* mPipe;
+ LLTempBoundListener mConnection;
+ boost::asio::streambuf mStreambuf;
+ std::ostream mStream;
+};
+
+class ReadPipeImpl: public LLProcess::ReadPipe
+{
+ LOG_CLASS(ReadPipeImpl);
+public:
+ ReadPipeImpl(const std::string& desc, apr_file_t* pipe, LLProcess::FILESLOT index):
+ mDesc(desc),
+ mPipe(pipe),
+ mIndex(index),
+ // Essential to initialize our std::istream with our special streambuf!
+ mStream(&mStreambuf),
+ mPump("ReadPipe", true), // tweak name as needed to avoid collisions
+ mLimit(0),
+ mEOF(false)
+ {
+ mConnection = LLEventPumps::instance().obtain("mainloop")
+ .listen(LLEventPump::inventName("ReadPipe"),
+ boost::bind(&ReadPipeImpl::tick, this, _1));
+ }
+
+ ~ReadPipeImpl()
+ {
+ if (mConnection.connected())
+ {
+ mConnection.disconnect();
+ }
+ }
+
+ // Much of the implementation is simply connecting the abstract virtual
+ // methods with implementation data concealed from the base class.
+ virtual std::istream& get_istream() { return mStream; }
+ virtual std::string getline() { return LLProcess::getline(mStream); }
+ virtual LLEventPump& getPump() { return mPump; }
+ virtual void setLimit(size_type limit) { mLimit = limit; }
+ virtual size_type getLimit() const { return mLimit; }
+ virtual size_type size() const { return mStreambuf.size(); }
+
+ virtual std::string read(size_type len)
+ {
+ // Read specified number of bytes into a buffer.
+ size_type readlen((std::min)(size(), len));
+ // Formally, &buffer[0] is invalid for a vector of size() 0. Exit
+ // early in that situation.
+ if (! readlen)
+ return "";
+ // Make a buffer big enough.
+ std::vector<char> buffer(readlen);
+ mStream.read(&buffer[0], readlen);
+ // Since we've already clamped 'readlen', we can think of no reason
+ // why mStream.read() should read fewer than 'readlen' bytes.
+ // Nonetheless, use the actual retrieved length.
+ return std::string(&buffer[0], mStream.gcount());
+ }
+
+ virtual std::string peek(size_type offset=0, size_type len=npos) const
+ {
+ // Constrain caller's offset and len to overlap actual buffer content.
+ std::size_t real_offset = (std::min)(mStreambuf.size(), std::size_t(offset));
+ size_type want_end = (len == npos)? npos : (real_offset + len);
+ std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(want_end));
+ boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
+ return std::string(boost::asio::buffers_begin(cbufs) + real_offset,
+ boost::asio::buffers_begin(cbufs) + real_end);
+ }
+
+ virtual size_type find(const std::string& seek, size_type offset=0) const
+ {
+ // If we're passing a string of length 1, use find(char), which can
+ // use an O(n) std::find() rather than the O(n^2) std::search().
+ if (seek.length() == 1)
+ {
+ return find(seek[0], offset);
+ }
+
+ // If offset is beyond the whole buffer, can't even construct a valid
+ // iterator range; can't possibly find the string we seek.
+ if (offset > mStreambuf.size())
+ {
+ return npos;
+ }
+
+ boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
+ boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type>
+ begin(boost::asio::buffers_begin(cbufs)),
+ end (boost::asio::buffers_end(cbufs)),
+ found(std::search(begin + offset, end, seek.begin(), seek.end()));
+ return (found == end)? npos : (found - begin);
+ }
+
+ virtual size_type find(char seek, size_type offset=0) const
+ {
+ // If offset is beyond the whole buffer, can't even construct a valid
+ // iterator range; can't possibly find the char we seek.
+ if (offset > mStreambuf.size())
+ {
+ return npos;
+ }
+
+ boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
+ boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type>
+ begin(boost::asio::buffers_begin(cbufs)),
+ end (boost::asio::buffers_end(cbufs)),
+ found(std::find(begin + offset, end, seek));
+ return (found == end)? npos : (found - begin);
+ }
+
+ bool tick(const LLSD&)
+ {
+ // Once we've hit EOF, skip all the rest of this.
+ if (mEOF)
+ return false;
+
+ typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence;
+ // Try, every time, to read into our streambuf. In fact, we have no
+ // idea how much data the child might be trying to send: keep trying
+ // until we're convinced we've temporarily exhausted the pipe.
+ enum PipeState { RETRY, EXHAUSTED, CLOSED };
+ PipeState state = RETRY;
+ std::size_t committed(0);
+ do
+ {
+ // attempt to read an arbitrary size
+ mutable_buffer_sequence bufs = mStreambuf.prepare(4096);
+ // In general, the mutable_buffer_sequence returned by prepare() might
+ // contain a number of different physical buffers; iterate over those.
+ std::size_t tocommit(0);
+ for (mutable_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end());
+ bufi != bufend; ++bufi)
+ {
+ // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents
+ std::size_t toread(boost::asio::buffer_size(*bufi));
+ apr_size_t gotten(toread);
+ apr_status_t err = apr_file_read(mPipe,
+ boost::asio::buffer_cast<void*>(*bufi),
+ &gotten);
+ // EAGAIN is exactly what we want from a nonblocking pipe.
+ // Rather than waiting for data, it should return immediately.
+ if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err)))
+ {
+ // Handle EOF specially: it's part of normal-case processing.
+ if (err == APR_EOF)
+ {
+ LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("LLProcess") << "apr_file_read(" << toread << ") on " << mDesc
+ << " got " << err << ":" << LL_ENDL;
+ ll_apr_warn_status(err);
+ }
+ // Either way, though, we won't need any more tick() calls.
+ mConnection.disconnect();
+ // Ignore any subsequent calls we might get anyway.
+ mEOF = true;
+ state = CLOSED; // also break outer retry loop
+ break;
+ }
+
+ // 'gotten' was modified to reflect the number of bytes actually
+ // received. Make sure we commit those later. (Don't commit them
+ // now, that would invalidate the buffer iterator sequence!)
+ tocommit += gotten;
+ LL_DEBUGS("LLProcess") << "filled " << gotten << " of " << toread
+ << " bytes from " << mDesc << LL_ENDL;
+
+ // The parent end of this pipe is nonblocking. If we weren't even
+ // able to fill this buffer, don't loop to try to fill the next --
+ // that won't change until the child writes more. Wait for next
+ // tick().
+ if (gotten < toread)
+ {
+ // break outer retry loop too
+ state = EXHAUSTED;
+ break;
+ }
+ }
+
+ // Don't forget to "commit" the data!
+ mStreambuf.commit(tocommit);
+ committed += tocommit;
+
+ // state is changed from RETRY when we can't fill any one buffer
+ // of the mutable_buffer_sequence established by the current
+ // prepare() call -- whether due to error or not enough bytes.
+ // That is, if state is still RETRY, we've filled every physical
+ // buffer in the mutable_buffer_sequence. In that case, for all we
+ // know, the child might have still more data pending -- go for it!
+ } while (state == RETRY);
+
+ // Once we recognize that the pipe is closed, make one more call to
+ // listener. The listener might be waiting for a particular substring
+ // to arrive, or a particular length of data or something. The event
+ // with "eof" == true announces that nothing further will arrive, so
+ // use it or lose it.
+ if (committed || state == CLOSED)
+ {
+ // If we actually received new data, publish it on our LLEventPump
+ // as advertised. Constrain it by mLimit. But show listener the
+ // actual accumulated buffer size, regardless of mLimit.
+ size_type datasize((std::min)(mLimit, size_type(mStreambuf.size())));
+ mPump.post(LLSDMap
+ ("data", peek(0, datasize))
+ ("len", LLSD::Integer(mStreambuf.size()))
+ ("slot", LLSD::Integer(mIndex))
+ ("name", whichfile(mIndex))
+ ("desc", mDesc)
+ ("eof", state == CLOSED));
+ }
+
+ return false;
+ }
+
+private:
+ std::string mDesc;
+ apr_file_t* mPipe;
+ LLProcess::FILESLOT mIndex;
+ LLTempBoundListener mConnection;
+ boost::asio::streambuf mStreambuf;
+ std::istream mStream;
+ LLEventStream mPump;
+ size_type mLimit;
+ bool mEOF;
+};
+
+/*****************************************************************************
+* LLProcess itself
+*****************************************************************************/
+/// Need an exception to avoid constructing an invalid LLProcess object, but
+/// internal use only
+struct LLProcessError: public LLException
+{
+ LLProcessError(const std::string& msg): LLException(msg) {}
+};
+
+LLProcessPtr LLProcess::create(const LLSDOrParams& params)
+{
+ try
+ {
+ return LLProcessPtr(new LLProcess(params));
+ }
+ catch (const LLProcessError& e)
+ {
+ LL_WARNS("LLProcess") << e.what() << LL_ENDL;
+
+ // If caller is requesting an event on process termination, send one
+ // indicating bad launch. This may prevent someone waiting forever for
+ // a termination post that can't arrive because the child never
+ // started.
+ if (params.postend.isProvided())
+ {
+ LLEventPumps::instance().obtain(params.postend)
+ .post(LLSDMap
+ // no "id"
+ ("desc", getDesc(params))
+ ("state", LLProcess::UNSTARTED)
+ // no "data"
+ ("string", e.what())
+ );
+ }
+
+ return LLProcessPtr();
+ }
+}
+
+/// Call an apr function returning apr_status_t. On failure, log warning and
+/// throw LLProcessError mentioning the function call that produced that
+/// result.
+#define chkapr(func) \
+ if (ll_apr_warn_status(func)) \
+ throw LLProcessError(#func " failed")
+
+LLProcess::LLProcess(const LLSDOrParams& params):
+ mAutokill(params.autokill),
+ // Because 'autokill' originally meant both 'autokill' and 'attached', to
+ // preserve existing semantics, we promise that mAttached defaults to the
+ // same setting as mAutokill.
+ mAttached(params.attached.isProvided()? params.attached : params.autokill),
+ mPool(NULL),
+ mPipes(NSLOTS)
+{
+ // Hmm, when you construct a ptr_vector with a size, it merely reserves
+ // space, it doesn't actually make it that big. Explicitly make it bigger.
+ // Because of ptr_vector's odd semantics, have to push_back(0) the right
+ // number of times! resize() wants to default-construct new BasePipe
+ // instances, which fails because it's pure virtual. But because of the
+ // constructor call, these push_back() calls should require no new
+ // allocation.
+ for (size_t i = 0; i < mPipes.capacity(); ++i)
+ mPipes.push_back(0);
+
+ if (! params.validateBlock(true))
+ {
+ LLTHROW(LLProcessError(STRINGIZE("not launched: failed parameter validation\n"
+ << LLSDNotationStreamer(params))));
+ }
+
+ mPostend = params.postend;
+
+ apr_pool_create(&mPool, gAPRPoolp);
+ if (!mPool)
+ {
+ LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool")));
+ }
+
+ apr_procattr_t *procattr = NULL;
+ chkapr(apr_procattr_create(&procattr, mPool));
+
+ // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to
+ // constrain the set of handles passed to the child process. Before we
+ // changed to APR, the Windows implementation of LLProcessLauncher called
+ // CreateProcess(bInheritHandles=false), meaning to pass NO open handles
+ // to the child process. Now that we support pipes, though, we must allow
+ // apr_proc_create() to pass bInheritHandles=true. But without taking
+ // special pains, that causes trouble in a number of ways, due to the fact
+ // that the viewer is constantly opening and closing files -- most of
+ // which CreateProcess() passes to every child process!
+#if ! defined(APR_HAS_PROCATTR_CONSTRAIN_HANDLE_SET)
+ // Our special preprocessor symbol isn't even defined -- wrong APR
+ LL_WARNS("LLProcess") << "This version of APR lacks Linden "
+ << "apr_procattr_constrain_handle_set() extension" << LL_ENDL;
+#else
+ chkapr(apr_procattr_constrain_handle_set(procattr, 1));
+#endif
+
+ // For which of stdin, stdout, stderr should we create a pipe to the
+ // child? In the viewer, there are only a couple viable
+ // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx
+ // handle (APR_NO_PIPE, e.g. for stdout, stderr), or create a pipe that's
+ // blocking on the child end but nonblocking at the viewer end
+ // (APR_CHILD_BLOCK).
+ // Other major options could include explicitly creating a single APR pipe
+ // and passing it as both stdout and stderr (apr_procattr_child_out_set(),
+ // apr_procattr_child_err_set()), or accepting a filename, opening it and
+ // passing that apr_file_t (simple <, >, 2> redirect emulation).
+ std::vector<apr_int32_t> select;
+ for (const FileParam& fparam : params.files)
+ {
+ // Every iteration, we're going to append an item to 'select'. At the
+ // top of the loop, its size() is, in effect, an index. Use that to
+ // pick a string description for messages.
+ std::string which(whichfile(FILESLOT(select.size())));
+ if (fparam.type().empty()) // inherit our file descriptor
+ {
+ select.push_back(APR_NO_PIPE);
+ }
+ else if (fparam.type() == "pipe") // anonymous pipe
+ {
+ if (! fparam.name().empty())
+ {
+ LL_WARNS("LLProcess") << "For " << params.executable()
+ << ": internal names for reusing pipes ('"
+ << fparam.name() << "' for " << which
+ << ") are not yet supported -- creating distinct pipe"
+ << LL_ENDL;
+ }
+ // The viewer can't block for anything: the parent end MUST be
+ // nonblocking. As the APR documentation itself points out, it
+ // makes very little sense to set nonblocking I/O for the child
+ // end of a pipe: only a specially-written child could deal with
+ // that.
+ select.push_back(APR_CHILD_BLOCK);
+ }
+ else
+ {
+ LLTHROW(LLProcessError(STRINGIZE("For " << params.executable()
+ << ": unsupported FileParam for " << which
+ << ": type='" << fparam.type()
+ << "', name='" << fparam.name() << "'")));
+ }
+ }
+ // By default, pass APR_NO_PIPE for unspecified slots.
+ while (select.size() < NSLOTS)
+ {
+ select.push_back(APR_NO_PIPE);
+ }
+ chkapr(apr_procattr_io_set(procattr, select[STDIN], select[STDOUT], select[STDERR]));
+
+ // Thumbs down on implicitly invoking the shell to invoke the child. From
+ // our point of view, the other major alternative to APR_PROGRAM_PATH
+ // would be APR_PROGRAM_ENV: still copy environment, but require full
+ // executable pathname. I don't see a downside to searching the PATH,
+ // though: if our caller wants (e.g.) a specific Python interpreter, s/he
+ // can still pass the full pathname.
+ chkapr(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH));
+ // YES, do extra work if necessary to report child exec() failures back to
+ // parent process.
+ chkapr(apr_procattr_error_check_set(procattr, 1));
+ // Do not start a non-autokill child in detached state. On Posix
+ // platforms, this setting attempts to daemonize the new child, closing
+ // std handles and the like, and that's a bit more detachment than we
+ // want. autokill=false just means not to implicitly kill the child when
+ // the parent terminates!
+// chkapr(apr_procattr_detach_set(procattr, mAutokill? 0 : 1));
+
+ if (mAutokill)
+ {
+#if ! defined(APR_HAS_PROCATTR_AUTOKILL_SET)
+ // Our special preprocessor symbol isn't even defined -- wrong APR
+ LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL;
+#elif ! APR_HAS_PROCATTR_AUTOKILL_SET
+ // Symbol is defined, but to 0: expect apr_procattr_autokill_set() to
+ // return APR_ENOTIMPL.
+#else // APR_HAS_PROCATTR_AUTOKILL_SET nonzero
+ ll_apr_warn_status(apr_procattr_autokill_set(procattr, 1));
+#endif
+ }
+
+ // In preparation for calling apr_proc_create(), we collect a number of
+ // const char* pointers obtained from std::string::c_str(). Turns out
+ // LLInitParam::Block's helpers Optional, Mandatory, Multiple et al.
+ // guarantee that converting to the wrapped type (std::string in our
+ // case), e.g. by calling operator(), returns a reference to *the same
+ // instance* of the wrapped type that's stored in our Block subclass.
+ // That's important! We know 'params' persists throughout this method
+ // call; but without that guarantee, we'd have to assume that converting
+ // one of its members to std::string might return a different (temp)
+ // instance. Capturing the c_str() from a temporary std::string is Bad Bad
+ // Bad. But armed with this knowledge, when you see params.cwd().c_str(),
+ // grit your teeth and smile and carry on.
+
+ if (params.cwd.isProvided())
+ {
+ chkapr(apr_procattr_dir_set(procattr, params.cwd().c_str()));
+ }
+
+ // create an argv vector for the child process
+ std::vector<const char*> argv;
+
+ // Add the executable path. See above remarks about c_str().
+ argv.push_back(params.executable().c_str());
+
+ // Add arguments. See above remarks about c_str().
+ for (const std::string& arg : params.args)
+ {
+ argv.push_back(arg.c_str());
+ }
+
+ // terminate with a null pointer
+ argv.push_back(NULL);
+
+ // Launch! The NULL would be the environment block, if we were passing
+ // one. Hand-expand chkapr() macro so we can fill in the actual command
+ // string instead of the variable names.
+ if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr,
+ mPool)))
+ {
+ LLTHROW(LLProcessError(STRINGIZE(params << " failed")));
+ }
+
+ // arrange to call status_callback()
+ apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in,
+ mPool);
+ // and make sure we poll it once per "mainloop" tick
+ sProcessListener.addPoll(*this);
+ mStatus.mState = RUNNING;
+
+ mDesc = STRINGIZE(getDesc(params) << " (" << mProcess.pid << ')');
+ LL_INFOS("LLProcess") << mDesc << ": launched " << params << LL_ENDL;
+
+ // Unless caller explicitly turned off autokill (child should persist),
+ // take steps to terminate the child. This is all suspenders-and-belt: in
+ // theory our destructor should kill an autokill child, but in practice
+ // that doesn't always work (e.g. VWR-21538).
+ if (mAutokill)
+ {
+/*==========================================================================*|
+ // NO: There may be an APR bug, not sure -- but at least on Mac, when
+ // gAPRPoolp is destroyed, OUR process receives SIGTERM! Apparently
+ // either our own PID is getting into the list of processes to kill()
+ // (unlikely), or somehow one of those PIDs is getting zeroed first,
+ // so that kill() sends SIGTERM to the whole process group -- this
+ // process included. I'd have to build and link with a debug version
+ // of APR to know for sure. It's too bad: this mechanism would be just
+ // right for dealing with static autokill LLProcessPtr variables,
+ // which aren't destroyed until after APR is no longer available.
+
+ // Tie the lifespan of this child process to the lifespan of our APR
+ // pool: on destruction of the pool, forcibly kill the process. Tell
+ // APR to try SIGTERM and suspend 3 seconds. If that didn't work, use
+ // SIGKILL.
+ apr_pool_note_subprocess(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT);
+|*==========================================================================*/
+
+ // On Windows, associate the new child process with our Job Object.
+ autokill();
+ }
+
+ // Instantiate the proper pipe I/O machinery
+ // want to be able to point to apr_proc_t::in, out, err by index
+ typedef apr_file_t* apr_proc_t::*apr_proc_file_ptr;
+ static apr_proc_file_ptr members[] =
+ { &apr_proc_t::in, &apr_proc_t::out, &apr_proc_t::err };
+ for (size_t i = 0; i < NSLOTS; ++i)
+ {
+ if (select[i] != APR_CHILD_BLOCK)
+ continue;
+ std::string desc(STRINGIZE(mDesc << ' ' << whichfile(FILESLOT(i))));
+ apr_file_t* pipe(mProcess.*(members[i]));
+ if (i == STDIN)
+ {
+ mPipes.replace(i, new WritePipeImpl(desc, pipe));
+ }
+ else
+ {
+ mPipes.replace(i, new ReadPipeImpl(desc, pipe, FILESLOT(i)));
+ }
+ // Removed temporaily for Xcode 7 build tests: error was:
+ // "error: expression with side effects will be evaluated despite
+ // being used as an operand to 'typeid' [-Werror,-Wpotentially-evaluated-expression]""
+ //LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name()
+ // << "('" << desc << "')" << LL_ENDL;
+ }
+}
+
+// Helper to obtain a description string, given a Params block
+static std::string getDesc(const LLProcess::Params& params)
+{
+ // If caller specified a description string, by all means use it.
+ if (params.desc.isProvided())
+ return params.desc;
+
+ // Caller didn't say. Use the executable name -- but use just the filename
+ // part. On Mac, for instance, full pathnames get cumbersome.
+ return LLProcess::basename(params.executable);
+}
+
+//static
+std::string LLProcess::basename(const std::string& path)
+{
+ // If there are Linden utility functions to manipulate pathnames, I
+ // haven't found them -- and for this usage, Boost.Filesystem seems kind
+ // of heavyweight.
+ std::string::size_type delim = path.find_last_of("\\/");
+ // If path contains no pathname delimiters, return the whole thing.
+ if (delim == std::string::npos)
+ return path;
+
+ // Return just the part beyond the last delimiter.
+ return path.substr(delim + 1);
+}
+
+LLProcess::~LLProcess()
+{
+ // In the Linden viewer, there's at least one static LLProcessPtr. Its
+ // destructor will be called *after* ll_cleanup_apr(). In such a case,
+ // unregistering is pointless (and fatal!) -- and kill(), which also
+ // relies on APR, is impossible.
+ if (! gAPRPoolp)
+ return;
+
+ // Only in state RUNNING are we registered for callback. In UNSTARTED we
+ // haven't yet registered. And since receiving the callback is the only
+ // way we detect child termination, we only change from state RUNNING at
+ // the same time we unregister.
+ if (mStatus.mState == RUNNING)
+ {
+ // We're still registered for a callback: unregister. Do it before
+ // we even issue the kill(): even if kill() somehow prompted an
+ // instantaneous callback (unlikely), this object is going away! Any
+ // information updated in this object by such a callback is no longer
+ // available to any consumer anyway.
+ apr_proc_other_child_unregister(this);
+ // One less LLProcess to poll for
+ sProcessListener.dropPoll(*this);
+ }
+
+ if (mAttached)
+ {
+ kill("destructor");
+ }
+
+ if (mPool)
+ {
+ apr_pool_destroy(mPool);
+ mPool = NULL;
+ }
+}
+
+bool LLProcess::kill(const std::string& who)
+{
+ if (isRunning())
+ {
+ LL_INFOS("LLProcess") << who << " killing " << mDesc << LL_ENDL;
+
+#if LL_WINDOWS
+ int sig = -1;
+#else // Posix
+ int sig = SIGTERM;
+#endif
+
+ ll_apr_warn_status(apr_proc_kill(&mProcess, sig));
+ }
+
+ return ! isRunning();
+}
+
+//static
+bool LLProcess::kill(const LLProcessPtr& p, const std::string& who)
+{
+ if (! p)
+ return true; // process dead! (was never running)
+ return p->kill(who);
+}
+
+bool LLProcess::isRunning() const
+{
+ return getStatus().mState == RUNNING;
+}
+
+//static
+bool LLProcess::isRunning(const LLProcessPtr& p)
+{
+ if (! p)
+ return false;
+ return p->isRunning();
+}
+
+LLProcess::Status LLProcess::getStatus() const
+{
+ return mStatus;
+}
+
+//static
+LLProcess::Status LLProcess::getStatus(const LLProcessPtr& p)
+{
+ if (! p)
+ {
+ // default-constructed Status has mState == UNSTARTED
+ return Status();
+ }
+ return p->getStatus();
+}
+
+std::string LLProcess::getStatusString() const
+{
+ return getStatusString(getStatus());
+}
+
+std::string LLProcess::getStatusString(const Status& status) const
+{
+ return getStatusString(mDesc, status);
+}
+
+//static
+std::string LLProcess::getStatusString(const std::string& desc, const LLProcessPtr& p)
+{
+ if (! p)
+ {
+ // default-constructed Status has mState == UNSTARTED
+ return getStatusString(desc, Status());
+ }
+ return desc + " " + p->getStatusString();
+}
+
+//static
+std::string LLProcess::getStatusString(const std::string& desc, const Status& status)
+{
+ if (status.mState == UNSTARTED)
+ return desc + " was never launched";
+
+ if (status.mState == RUNNING)
+ return desc + " running";
+
+ if (status.mState == EXITED)
+ return STRINGIZE(desc << " exited with code " << status.mData);
+
+ if (status.mState == KILLED)
+#if LL_WINDOWS
+ return STRINGIZE(desc << " killed with exception " << std::hex << status.mData);
+#else
+ return STRINGIZE(desc << " killed by signal " << status.mData
+ << " (" << apr_signal_description_get(status.mData) << ")");
+#endif
+
+ return STRINGIZE(desc << " in unknown state " << status.mState << " (" << status.mData << ")");
+}
+
+// Classic-C-style APR callback
+void LLProcess::status_callback(int reason, void* data, int status)
+{
+ // Our only role is to bounce this static method call back into object
+ // space.
+ static_cast<LLProcess*>(data)->handle_status(reason, status);
+}
+
+#define tabent(symbol) { symbol, #symbol }
+static struct ReasonCode
+{
+ int code;
+ const char* name;
+} reasons[] =
+{
+ tabent(APR_OC_REASON_DEATH),
+ tabent(APR_OC_REASON_UNWRITABLE),
+ tabent(APR_OC_REASON_RESTART),
+ tabent(APR_OC_REASON_UNREGISTER),
+ tabent(APR_OC_REASON_LOST),
+ tabent(APR_OC_REASON_RUNNING)
+};
+#undef tabent
+
+// Object-oriented callback
+void LLProcess::handle_status(int reason, int status)
+{
+ {
+ // This odd appearance of LL_DEBUGS is just to bracket a lookup that will
+ // only be performed if in fact we're going to produce the log message.
+ LL_DEBUGS("LLProcess") << empty;
+ std::string reason_str;
+ for (const ReasonCode& rcp : reasons)
+ {
+ if (reason == rcp.code)
+ {
+ reason_str = rcp.name;
+ break;
+ }
+ }
+ if (reason_str.empty())
+ {
+ reason_str = STRINGIZE("unknown reason " << reason);
+ }
+ LL_CONT << mDesc << ": handle_status(" << reason_str << ", " << status << ")" << LL_ENDL;
+ }
+
+ if (! (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST))
+ {
+ // We're only interested in the call when the child terminates.
+ return;
+ }
+
+ // Somewhat oddly, APR requires that you explicitly unregister even when
+ // it already knows the child has terminated. We must pass the same 'data'
+ // pointer as for the register() call, which was our 'this'.
+ apr_proc_other_child_unregister(this);
+ // don't keep polling for a terminated process
+ sProcessListener.dropPoll(*this);
+ // We overload mStatus.mState to indicate whether the child is registered
+ // for APR callback: only RUNNING means registered. Track that we've
+ // unregistered. We know the child has terminated; might be EXITED or
+ // KILLED; refine below.
+ mStatus.mState = EXITED;
+
+ // Make last-gasp calls for each of the ReadPipes we have on hand. Since
+ // they're listening on "mainloop", we can be sure they'll eventually
+ // collect all pending data from the child. But we want to be able to
+ // guarantee to our consumer that by the time we post on the "postend"
+ // LLEventPump, our ReadPipes are already buffering all the data there
+ // will ever be from the child. That lets the "postend" listener decide
+ // what to do with that final data.
+ for (size_t i = 0; i < mPipes.size(); ++i)
+ {
+ std::string error;
+ ReadPipeImpl* ppipe = getPipePtr<ReadPipeImpl>(error, FILESLOT(i));
+ if (ppipe)
+ {
+ static LLSD trivial;
+ ppipe->tick(trivial);
+ }
+ }
+
+// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT);
+ // It's just wrong to call apr_proc_wait() here. The only way APR knows to
+ // call us with APR_OC_REASON_DEATH is that it's already reaped this child
+ // process, so calling wait() will only produce "huh?" from the OS. We
+ // must rely on the status param passed in, which unfortunately comes
+ // straight from the OS wait() call, which means we have to decode it by
+ // hand.
+ mStatus = interpret_status(status);
+ LL_INFOS("LLProcess") << getStatusString() << LL_ENDL;
+
+ // If caller requested notification on child termination, send it.
+ if (! mPostend.empty())
+ {
+ LLEventPumps::instance().obtain(mPostend)
+ .post(LLSDMap
+ ("id", getProcessID())
+ ("desc", mDesc)
+ ("state", mStatus.mState)
+ ("data", mStatus.mData)
+ ("string", getStatusString())
+ );
+ }
+}
+
+LLProcess::id LLProcess::getProcessID() const
+{
+ return mProcess.pid;
+}
+
+LLProcess::handle LLProcess::getProcessHandle() const
+{
+#if LL_WINDOWS
+ return mProcess.hproc;
+#else
+ return mProcess.pid;
+#endif
+}
+
+std::string LLProcess::getPipeName(FILESLOT) const
+{
+ // LLProcess::FileParam::type "npipe" is not yet implemented
+ return "";
+}
+
+template<class PIPETYPE>
+PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot)
+{
+ if (slot >= NSLOTS)
+ {
+ error = STRINGIZE(mDesc << " has no slot " << slot);
+ return NULL;
+ }
+ if (mPipes.is_null(slot))
+ {
+ error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a monitored pipe");
+ return NULL;
+ }
+ // Make sure we dynamic_cast in pointer domain so we can test, rather than
+ // accepting runtime's exception.
+ PIPETYPE* ppipe = dynamic_cast<PIPETYPE*>(&mPipes[slot]);
+ if (! ppipe)
+ {
+ error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name());
+ return NULL;
+ }
+
+ error.clear();
+ return ppipe;
+}
+
+template <class PIPETYPE>
+PIPETYPE& LLProcess::getPipe(FILESLOT slot)
+{
+ std::string error;
+ PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
+ if (! wp)
+ {
+ LLTHROW(NoPipe(error));
+ }
+ return *wp;
+}
+
+template <class PIPETYPE>
+boost::optional<PIPETYPE&> LLProcess::getOptPipe(FILESLOT slot)
+{
+ std::string error;
+ PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
+ if (! wp)
+ {
+ LL_DEBUGS("LLProcess") << error << LL_ENDL;
+ return boost::optional<PIPETYPE&>();
+ }
+ return *wp;
+}
+
+LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot)
+{
+ return getPipe<WritePipe>(slot);
+}
+
+boost::optional<LLProcess::WritePipe&> LLProcess::getOptWritePipe(FILESLOT slot)
+{
+ return getOptPipe<WritePipe>(slot);
+}
+
+LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot)
+{
+ return getPipe<ReadPipe>(slot);
+}
+
+boost::optional<LLProcess::ReadPipe&> LLProcess::getOptReadPipe(FILESLOT slot)
+{
+ return getOptPipe<ReadPipe>(slot);
+}
+
+//static
+std::string LLProcess::getline(std::istream& in)
+{
+ std::string line;
+ std::getline(in, line);
+ // Blur the distinction between "\r\n" and plain "\n". std::getline() will
+ // have eaten the "\n", but we could still end up with a trailing "\r".
+ std::string::size_type lastpos = line.find_last_not_of("\r");
+ if (lastpos != std::string::npos)
+ {
+ // Found at least one character that's not a trailing '\r'. SKIP OVER
+ // IT and erase the rest of the line.
+ line.erase(lastpos+1);
+ }
+ return line;
+}
+
+std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params)
+{
+ if (params.cwd.isProvided())
+ {
+ out << "cd " << LLStringUtil::quote(params.cwd) << ": ";
+ }
+ out << LLStringUtil::quote(params.executable);
+ for (const std::string& arg : params.args)
+ {
+ out << ' ' << LLStringUtil::quote(arg);
+ }
+ return out;
+}
+
+/*****************************************************************************
+* Windows specific
+*****************************************************************************/
+#if LL_WINDOWS
+
+static std::string WindowsErrorString(const std::string& operation);
+
+void LLProcess::autokill()
+{
+ // hopefully now handled by apr_procattr_autokill_set()
+}
+
+LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc)
+{
+ // This direct Windows implementation is because we have no access to the
+ // apr_proc_t struct: we expect it's been destroyed.
+ if (! h)
+ return 0;
+
+ DWORD waitresult = WaitForSingleObject(h, 0);
+ if(waitresult == WAIT_OBJECT_0)
+ {
+ // the process has completed.
+ if (! desc.empty())
+ {
+ DWORD status = 0;
+ if (! GetExitCodeProcess(h, &status))
+ {
+ LL_WARNS("LLProcess") << desc << " terminated, but "
+ << WindowsErrorString("GetExitCodeProcess()") << LL_ENDL;
+ }
+ {
+ LL_INFOS("LLProcess") << getStatusString(desc, interpret_status(status))
+ << LL_ENDL;
+ }
+ }
+ CloseHandle(h);
+ return 0;
+ }
+
+ return h;
+}
+
+static LLProcess::Status interpret_status(int status)
+{
+ LLProcess::Status result;
+
+ // This bit of code is cribbed from apr/threadproc/win32/proc.c, a
+ // function (unfortunately static) called why_from_exit_code():
+ /* See WinNT.h STATUS_ACCESS_VIOLATION and family for how
+ * this class of failures was determined
+ */
+ if ((status & 0xFFFF0000) == 0xC0000000)
+ {
+ result.mState = LLProcess::KILLED;
+ }
+ else
+ {
+ result.mState = LLProcess::EXITED;
+ }
+ result.mData = status;
+
+ return result;
+}
+
+/// GetLastError()/FormatMessage() boilerplate
+static std::string WindowsErrorString(const std::string& operation)
+{
+ auto result = GetLastError();
+ return STRINGIZE(operation << " failed (" << result << "): "
+ << windows_message<std::string>(result));
+}
+
+/*****************************************************************************
+* Posix specific
+*****************************************************************************/
+#else // Mac and linux
+
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+void LLProcess::autokill()
+{
+ // What we ought to do here is to:
+ // 1. create a unique process group and run all autokill children in that
+ // group (see https://jira.secondlife.com/browse/SWAT-563);
+ // 2. figure out a way to intercept control when the viewer exits --
+ // gracefully or not;
+ // 3. when the viewer exits, kill off the aforementioned process group.
+
+ // It's point 2 that's troublesome. Although I've seen some signal-
+ // handling logic in the Posix viewer code, I haven't yet found any bit of
+ // code that's run no matter how the viewer exits (a try/finally for the
+ // whole process, as it were).
+}
+
+// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise.
+static bool reap_pid(pid_t pid, LLProcess::Status* pstatus=NULL)
+{
+ LLProcess::Status dummy;
+ if (! pstatus)
+ {
+ // If caller doesn't want to see Status, give us a target anyway so we
+ // don't have to have a bunch of conditionals.
+ pstatus = &dummy;
+ }
+
+ int status = 0;
+ pid_t wait_result = ::waitpid(pid, &status, WNOHANG);
+ if (wait_result == pid)
+ {
+ *pstatus = interpret_status(status);
+ return true;
+ }
+ if (wait_result == 0)
+ {
+ pstatus->mState = LLProcess::RUNNING;
+ pstatus->mData = 0;
+ return false;
+ }
+
+ // Clear caller's Status block; caller must interpret UNSTARTED to mean
+ // "if this PID was ever valid, it no longer is."
+ *pstatus = LLProcess::Status();
+
+ // We've dealt with the success cases: we were able to reap the child
+ // (wait_result == pid) or it's still running (wait_result == 0). It may
+ // be that the child terminated but didn't hang around long enough for us
+ // to reap. In that case we still have no Status to report, but we can at
+ // least state that it's not running.
+ if (wait_result == -1 && errno == ECHILD)
+ {
+ // No such process -- this may mean we're ignoring SIGCHILD.
+ return true;
+ }
+
+ // Uh, should never happen?!
+ LL_WARNS("LLProcess") << "LLProcess::reap_pid(): waitpid(" << pid << ") returned "
+ << wait_result << "; not meaningful?" << LL_ENDL;
+ // If caller is looping until this pid terminates, and if we can't find
+ // out, better to break the loop than to claim it's still running.
+ return true;
+}
+
+LLProcess::id LLProcess::isRunning(id pid, const std::string& desc)
+{
+ // This direct Posix implementation is because we have no access to the
+ // apr_proc_t struct: we expect it's been destroyed.
+ if (! pid)
+ return 0;
+
+ // Check whether the process has exited, and reap it if it has.
+ LLProcess::Status status;
+ if(reap_pid(pid, &status))
+ {
+ // the process has exited.
+ if (! desc.empty())
+ {
+ std::string statstr(desc + " apparently terminated: no status available");
+ // We don't just pass UNSTARTED to getStatusString() because, in
+ // the context of reap_pid(), that state has special meaning.
+ if (status.mState != UNSTARTED)
+ {
+ statstr = getStatusString(desc, status);
+ }
+ LL_INFOS("LLProcess") << statstr << LL_ENDL;
+ }
+ return 0;
+ }
+
+ return pid;
+}
+
+static LLProcess::Status interpret_status(int status)
+{
+ LLProcess::Status result;
+
+ if (WIFEXITED(status))
+ {
+ result.mState = LLProcess::EXITED;
+ result.mData = WEXITSTATUS(status);
+ }
+ else if (WIFSIGNALED(status))
+ {
+ result.mState = LLProcess::KILLED;
+ result.mData = WTERMSIG(status);
+ }
+ else // uh, shouldn't happen?
+ {
+ result.mState = LLProcess::EXITED;
+ result.mData = status; // someone else will have to decode
+ }
+
+ return result;
+}
+
+#endif // Posix
diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index aaf13ac796..4a909f601a 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -1,587 +1,587 @@ -/** - * @file llqueuedthread.cpp - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" -#include "llqueuedthread.h" - -#include <chrono> - -#include "llstl.h" -#include "lltimer.h" // ms_sleep() -#include "llmutex.h" - -//============================================================================ - -// MAIN THREAD -LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) : - LLThread(name), - mIdleThread(TRUE), - mNextHandle(0), - mStarted(FALSE), - mThreaded(threaded), - mRequestQueue(name, 1024 * 1024) -{ - llassert(threaded); // not threaded implementation is deprecated - mMainQueue = LL::WorkQueue::getInstance("mainloop"); - - if (mThreaded) - { - if(should_pause) - { - pause() ; //call this before start the thread. - } - - start(); - } -} - -// MAIN THREAD -LLQueuedThread::~LLQueuedThread() -{ - if (!mThreaded) - { - endThread(); - } - shutdown(); - // ~LLThread() will be called here -} - -void LLQueuedThread::shutdown() -{ - setQuitting(); - - unpause(); // MAIN THREAD - if (mThreaded) - { - if (mRequestQueue.size() == 0) - { - mRequestQueue.close(); - } - - S32 timeout = 100; - for ( ; timeout>0; timeout--) - { - if (isStopped()) - { - break; - } - ms_sleep(100); - LLThread::yield(); - } - if (timeout == 0) - { - LL_WARNS() << "~LLQueuedThread (" << mName << ") timed out!" << LL_ENDL; - } - } - else - { - mStatus = STOPPED; - } - - QueuedRequest* req; - S32 active_count = 0; - while ( (req = (QueuedRequest*)mRequestHash.pop_element()) ) - { - if (req->getStatus() == STATUS_QUEUED || req->getStatus() == STATUS_INPROGRESS) - { - ++active_count; - req->setStatus(STATUS_ABORTED); // avoid assert in deleteRequest - } - req->deleteRequest(); - } - if (active_count) - { - LL_WARNS() << "~LLQueuedThread() called with active requests: " << active_count << LL_ENDL; - } - - mRequestQueue.close(); -} - -//---------------------------------------------------------------------------- - -// MAIN THREAD -// virtual -size_t LLQueuedThread::update(F32 max_time_ms) -{ - LL_PROFILE_ZONE_SCOPED; - if (!mStarted) - { - if (!mThreaded) - { - startThread(); - mStarted = TRUE; - } - } - return updateQueue(max_time_ms); -} - -size_t LLQueuedThread::updateQueue(F32 max_time_ms) -{ - LL_PROFILE_ZONE_SCOPED; - // Frame Update - if (mThreaded) - { - // schedule a call to threadedUpdate for every call to updateQueue - if (!isQuitting()) - { - mRequestQueue.post([=]() - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); - mIdleThread = FALSE; - threadedUpdate(); - mIdleThread = TRUE; - } - ); - } - - if(getPending() > 0) - { - unpause(); - } - } - else - { - mRequestQueue.runFor(std::chrono::microseconds((int) (max_time_ms*1000.f))); - threadedUpdate(); - } - return getPending(); -} - -void LLQueuedThread::incQueue() -{ - // Something has been added to the queue - if (!isPaused()) - { - if (mThreaded) - { - wake(); // Wake the thread up if necessary. - } - } -} - -//virtual -// May be called from any thread -size_t LLQueuedThread::getPending() -{ - return mRequestQueue.size(); -} - -// MAIN thread -void LLQueuedThread::waitOnPending() -{ - while(1) - { - update(0); - - if (mIdleThread) - { - break; - } - if (mThreaded) - { - yield(); - } - } - return; -} - -// MAIN thread -void LLQueuedThread::printQueueStats() -{ - U32 size = mRequestQueue.size(); - if (size > 0) - { - LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL; - } - else - { - LL_INFOS() << "Queued Thread Idle" << LL_ENDL; - } -} - -// MAIN thread -LLQueuedThread::handle_t LLQueuedThread::generateHandle() -{ - U32 res = ++mNextHandle; - return res; -} - -// MAIN thread -bool LLQueuedThread::addRequest(QueuedRequest* req) -{ - LL_PROFILE_ZONE_SCOPED; - if (mStatus == QUITTING) - { - return false; - } - - lockData(); - req->setStatus(STATUS_QUEUED); - mRequestHash.insert(req); -#if _DEBUG -// LL_INFOS() << llformat("LLQueuedThread::Added req [%08d]",handle) << LL_ENDL; -#endif - unlockData(); - - llassert(!mDataLock->isSelfLocked()); - mRequestQueue.post([this, req]() { processRequest(req); }); - - return true; -} - -// MAIN thread -bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete) -{ - LL_PROFILE_ZONE_SCOPED; - llassert (handle != nullHandle()); - bool res = false; - bool waspaused = isPaused(); - bool done = false; - while(!done) - { - update(0); // unpauses - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (!req) - { - done = true; // request does not exist - } - else if (req->getStatus() == STATUS_COMPLETE) - { - res = true; - if (auto_complete) - { - mRequestHash.erase(handle); - req->deleteRequest(); -// check(); - } - done = true; - } - unlockData(); - - if (!done && mThreaded) - { - yield(); - } - } - if (waspaused) - { - pause(); - } - return res; -} - -// MAIN thread -LLQueuedThread::QueuedRequest* LLQueuedThread::getRequest(handle_t handle) -{ - if (handle == nullHandle()) - { - return 0; - } - lockData(); - QueuedRequest* res = (QueuedRequest*)mRequestHash.find(handle); - unlockData(); - return res; -} - -LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle) -{ - status_t res = STATUS_EXPIRED; - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (req) - { - res = req->getStatus(); - } - unlockData(); - return res; -} - -void LLQueuedThread::abortRequest(handle_t handle, bool autocomplete) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (req) - { - req->setFlags(FLAG_ABORT | (autocomplete ? FLAG_AUTO_COMPLETE : 0)); - } - unlockData(); -} - -// MAIN thread -void LLQueuedThread::setFlags(handle_t handle, U32 flags) -{ - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (req) - { - req->setFlags(flags); - } - unlockData(); -} - -bool LLQueuedThread::completeRequest(handle_t handle) -{ - LL_PROFILE_ZONE_SCOPED; - bool res = false; - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (req) - { - llassert_always(req->getStatus() != STATUS_QUEUED); - llassert_always(req->getStatus() != STATUS_INPROGRESS); -#if _DEBUG -// LL_INFOS() << llformat("LLQueuedThread::Completed req [%08d]",handle) << LL_ENDL; -#endif - mRequestHash.erase(handle); - req->deleteRequest(); -// check(); - res = true; - } - unlockData(); - return res; -} - -bool LLQueuedThread::check() -{ -#if 0 // not a reliable check once mNextHandle wraps, just for quick and dirty debugging - for (int i=0; i<REQUEST_HASH_SIZE; i++) - { - LLSimpleHashEntry<handle_t>* entry = mRequestHash.get_element_at_index(i); - while (entry) - { - if (entry->getHashKey() > mNextHandle) - { - LL_ERRS() << "Hash Error" << LL_ENDL; - return false; - } - entry = entry->getNextEntry(); - } - } -#endif - return true; -} - -//============================================================================ -// Runs on its OWN thread - -void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; - - mIdleThread = FALSE; - //threadedUpdate(); - - // Get next request from pool - lockData(); - - if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING)) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - abort"); - req->setStatus(STATUS_ABORTED); - req->finishRequest(false); - if (req->getFlags() & FLAG_AUTO_COMPLETE) - { - mRequestHash.erase(req); - req->deleteRequest(); -// check(); - } - unlockData(); - } - else - { - llassert_always(req->getStatus() == STATUS_QUEUED); - - if (req) - { - req->setStatus(STATUS_INPROGRESS); - } - unlockData(); - - // This is the only place we will call req->setStatus() after - // it has initially been seet to STATUS_QUEUED, so it is - // safe to access req. - if (req) - { - // process request - bool complete = req->processRequest(); - - if (complete) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - complete"); - lockData(); - req->setStatus(STATUS_COMPLETE); - req->finishRequest(true); - if (req->getFlags() & FLAG_AUTO_COMPLETE) - { - mRequestHash.erase(req); - req->deleteRequest(); - // check(); - } - unlockData(); - } - else - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - retry"); - //put back on queue and try again in 0.1ms - lockData(); - req->setStatus(STATUS_QUEUED); - - unlockData(); - - llassert(!mDataLock->isSelfLocked()); - -#if 0 - // try again on next frame - // NOTE: tried using "post" with a time in the future, but this - // would invariably cause this thread to wait for a long time (10+ ms) - // while work is pending - bool ret = LL::WorkQueue::postMaybe( - mMainQueue, - [=]() - { - LL_PROFILE_ZONE_NAMED("processRequest - retry"); - mRequestQueue.post([=]() - { - LL_PROFILE_ZONE_NAMED("processRequest - retry"); // <-- not redundant, track retry on both queues - processRequest(req); - }); - }); - llassert(ret); -#else - using namespace std::chrono_literals; - auto retry_time = LL::WorkQueue::TimePoint::clock::now() + 16ms; - mRequestQueue.post([=] - { - LL_PROFILE_ZONE_NAMED("processRequest - retry"); - if (LL::WorkQueue::TimePoint::clock::now() < retry_time) - { - auto sleep_time = std::chrono::duration_cast<std::chrono::milliseconds>(retry_time - LL::WorkQueue::TimePoint::clock::now()); - - if (sleep_time.count() > 0) - { - ms_sleep(sleep_time.count()); - } - } - processRequest(req); - }); -#endif - - } - } - } - - mIdleThread = TRUE; -} - -// virtual -bool LLQueuedThread::runCondition() -{ - // mRunCondition must be locked here - if (mRequestQueue.size() == 0 && mIdleThread) - return false; - else - return true; -} - -// virtual -void LLQueuedThread::run() -{ - // call checPause() immediately so we don't try to do anything before the class is fully constructed - checkPause(); - startThread(); - mStarted = TRUE; - - - /*while (1) - { - LL_PROFILE_ZONE_SCOPED; - // this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state. - checkPause(); - - mIdleThread = FALSE; - - threadedUpdate(); - - auto pending_work = processNextRequest(); - - if (pending_work == 0) - { - //LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep"); - mIdleThread = TRUE; - //ms_sleep(1); - } - //LLThread::yield(); // thread should yield after each request - }*/ - mRequestQueue.runUntilClose(); - - endThread(); - LL_INFOS() << "LLQueuedThread " << mName << " EXITING." << LL_ENDL; - - -} - -// virtual -void LLQueuedThread::startThread() -{ -} - -// virtual -void LLQueuedThread::endThread() -{ -} - -// virtual -void LLQueuedThread::threadedUpdate() -{ -} - -//============================================================================ - -LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 flags) : - LLSimpleHashEntry<LLQueuedThread::handle_t>(handle), - mStatus(STATUS_UNKNOWN), - mFlags(flags) -{ -} - -LLQueuedThread::QueuedRequest::~QueuedRequest() -{ - llassert_always(mStatus == STATUS_DELETE); -} - -//virtual -void LLQueuedThread::QueuedRequest::finishRequest(bool completed) -{ -} - -//virtual -void LLQueuedThread::QueuedRequest::deleteRequest() -{ - llassert_always(mStatus != STATUS_INPROGRESS); - setStatus(STATUS_DELETE); - delete this; -} +/**
+ * @file llqueuedthread.cpp
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+#include "llqueuedthread.h"
+
+#include <chrono>
+
+#include "llstl.h"
+#include "lltimer.h" // ms_sleep()
+#include "llmutex.h"
+
+//============================================================================
+
+// MAIN THREAD
+LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) :
+ LLThread(name),
+ mIdleThread(true),
+ mNextHandle(0),
+ mStarted(false),
+ mThreaded(threaded),
+ mRequestQueue(name, 1024 * 1024)
+{
+ llassert(threaded); // not threaded implementation is deprecated
+ mMainQueue = LL::WorkQueue::getInstance("mainloop");
+
+ if (mThreaded)
+ {
+ if(should_pause)
+ {
+ pause() ; //call this before start the thread.
+ }
+
+ start();
+ }
+}
+
+// MAIN THREAD
+LLQueuedThread::~LLQueuedThread()
+{
+ if (!mThreaded)
+ {
+ endThread();
+ }
+ shutdown();
+ // ~LLThread() will be called here
+}
+
+void LLQueuedThread::shutdown()
+{
+ setQuitting();
+
+ unpause(); // MAIN THREAD
+ if (mThreaded)
+ {
+ if (mRequestQueue.size() == 0)
+ {
+ mRequestQueue.close();
+ }
+
+ S32 timeout = 100;
+ for ( ; timeout>0; timeout--)
+ {
+ if (isStopped())
+ {
+ break;
+ }
+ ms_sleep(100);
+ LLThread::yield();
+ }
+ if (timeout == 0)
+ {
+ LL_WARNS() << "~LLQueuedThread (" << mName << ") timed out!" << LL_ENDL;
+ }
+ }
+ else
+ {
+ mStatus = STOPPED;
+ }
+
+ QueuedRequest* req;
+ S32 active_count = 0;
+ while ( (req = (QueuedRequest*)mRequestHash.pop_element()) )
+ {
+ if (req->getStatus() == STATUS_QUEUED || req->getStatus() == STATUS_INPROGRESS)
+ {
+ ++active_count;
+ req->setStatus(STATUS_ABORTED); // avoid assert in deleteRequest
+ }
+ req->deleteRequest();
+ }
+ if (active_count)
+ {
+ LL_WARNS() << "~LLQueuedThread() called with active requests: " << active_count << LL_ENDL;
+ }
+
+ mRequestQueue.close();
+}
+
+//----------------------------------------------------------------------------
+
+// MAIN THREAD
+// virtual
+size_t LLQueuedThread::update(F32 max_time_ms)
+{
+ LL_PROFILE_ZONE_SCOPED;
+ if (!mStarted)
+ {
+ if (!mThreaded)
+ {
+ startThread();
+ mStarted = true;
+ }
+ }
+ return updateQueue(max_time_ms);
+}
+
+size_t LLQueuedThread::updateQueue(F32 max_time_ms)
+{
+ LL_PROFILE_ZONE_SCOPED;
+ // Frame Update
+ if (mThreaded)
+ {
+ // schedule a call to threadedUpdate for every call to updateQueue
+ if (!isQuitting())
+ {
+ mRequestQueue.post([=]()
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update");
+ mIdleThread = false;
+ threadedUpdate();
+ mIdleThread = true;
+ }
+ );
+ }
+
+ if(getPending() > 0)
+ {
+ unpause();
+ }
+ }
+ else
+ {
+ mRequestQueue.runFor(std::chrono::microseconds((int) (max_time_ms*1000.f)));
+ threadedUpdate();
+ }
+ return getPending();
+}
+
+void LLQueuedThread::incQueue()
+{
+ // Something has been added to the queue
+ if (!isPaused())
+ {
+ if (mThreaded)
+ {
+ wake(); // Wake the thread up if necessary.
+ }
+ }
+}
+
+//virtual
+// May be called from any thread
+size_t LLQueuedThread::getPending()
+{
+ return mRequestQueue.size();
+}
+
+// MAIN thread
+void LLQueuedThread::waitOnPending()
+{
+ while(1)
+ {
+ update(0);
+
+ if (mIdleThread)
+ {
+ break;
+ }
+ if (mThreaded)
+ {
+ yield();
+ }
+ }
+ return;
+}
+
+// MAIN thread
+void LLQueuedThread::printQueueStats()
+{
+ U32 size = mRequestQueue.size();
+ if (size > 0)
+ {
+ LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS() << "Queued Thread Idle" << LL_ENDL;
+ }
+}
+
+// MAIN thread
+LLQueuedThread::handle_t LLQueuedThread::generateHandle()
+{
+ U32 res = ++mNextHandle;
+ return res;
+}
+
+// MAIN thread
+bool LLQueuedThread::addRequest(QueuedRequest* req)
+{
+ LL_PROFILE_ZONE_SCOPED;
+ if (mStatus == QUITTING)
+ {
+ return false;
+ }
+
+ lockData();
+ req->setStatus(STATUS_QUEUED);
+ mRequestHash.insert(req);
+#if _DEBUG
+// LL_INFOS() << llformat("LLQueuedThread::Added req [%08d]",handle) << LL_ENDL;
+#endif
+ unlockData();
+
+ llassert(!mDataLock->isSelfLocked());
+ mRequestQueue.post([this, req]() { processRequest(req); });
+
+ return true;
+}
+
+// MAIN thread
+bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete)
+{
+ LL_PROFILE_ZONE_SCOPED;
+ llassert (handle != nullHandle());
+ bool res = false;
+ bool waspaused = isPaused();
+ bool done = false;
+ while(!done)
+ {
+ update(0); // unpauses
+ lockData();
+ QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
+ if (!req)
+ {
+ done = true; // request does not exist
+ }
+ else if (req->getStatus() == STATUS_COMPLETE)
+ {
+ res = true;
+ if (auto_complete)
+ {
+ mRequestHash.erase(handle);
+ req->deleteRequest();
+// check();
+ }
+ done = true;
+ }
+ unlockData();
+
+ if (!done && mThreaded)
+ {
+ yield();
+ }
+ }
+ if (waspaused)
+ {
+ pause();
+ }
+ return res;
+}
+
+// MAIN thread
+LLQueuedThread::QueuedRequest* LLQueuedThread::getRequest(handle_t handle)
+{
+ if (handle == nullHandle())
+ {
+ return 0;
+ }
+ lockData();
+ QueuedRequest* res = (QueuedRequest*)mRequestHash.find(handle);
+ unlockData();
+ return res;
+}
+
+LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle)
+{
+ status_t res = STATUS_EXPIRED;
+ lockData();
+ QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
+ if (req)
+ {
+ res = req->getStatus();
+ }
+ unlockData();
+ return res;
+}
+
+void LLQueuedThread::abortRequest(handle_t handle, bool autocomplete)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
+ lockData();
+ QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
+ if (req)
+ {
+ req->setFlags(FLAG_ABORT | (autocomplete ? FLAG_AUTO_COMPLETE : 0));
+ }
+ unlockData();
+}
+
+// MAIN thread
+void LLQueuedThread::setFlags(handle_t handle, U32 flags)
+{
+ lockData();
+ QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
+ if (req)
+ {
+ req->setFlags(flags);
+ }
+ unlockData();
+}
+
+bool LLQueuedThread::completeRequest(handle_t handle)
+{
+ LL_PROFILE_ZONE_SCOPED;
+ bool res = false;
+ lockData();
+ QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
+ if (req)
+ {
+ llassert_always(req->getStatus() != STATUS_QUEUED);
+ llassert_always(req->getStatus() != STATUS_INPROGRESS);
+#if _DEBUG
+// LL_INFOS() << llformat("LLQueuedThread::Completed req [%08d]",handle) << LL_ENDL;
+#endif
+ mRequestHash.erase(handle);
+ req->deleteRequest();
+// check();
+ res = true;
+ }
+ unlockData();
+ return res;
+}
+
+bool LLQueuedThread::check()
+{
+#if 0 // not a reliable check once mNextHandle wraps, just for quick and dirty debugging
+ for (int i=0; i<REQUEST_HASH_SIZE; i++)
+ {
+ LLSimpleHashEntry<handle_t>* entry = mRequestHash.get_element_at_index(i);
+ while (entry)
+ {
+ if (entry->getHashKey() > mNextHandle)
+ {
+ LL_ERRS() << "Hash Error" << LL_ENDL;
+ return false;
+ }
+ entry = entry->getNextEntry();
+ }
+ }
+#endif
+ return true;
+}
+
+//============================================================================
+// Runs on its OWN thread
+
+void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
+
+ mIdleThread = false;
+ //threadedUpdate();
+
+ // Get next request from pool
+ lockData();
+
+ if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING))
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - abort");
+ req->setStatus(STATUS_ABORTED);
+ req->finishRequest(false);
+ if (req->getFlags() & FLAG_AUTO_COMPLETE)
+ {
+ mRequestHash.erase(req);
+ req->deleteRequest();
+// check();
+ }
+ unlockData();
+ }
+ else
+ {
+ llassert_always(req->getStatus() == STATUS_QUEUED);
+
+ if (req)
+ {
+ req->setStatus(STATUS_INPROGRESS);
+ }
+ unlockData();
+
+ // This is the only place we will call req->setStatus() after
+ // it has initially been seet to STATUS_QUEUED, so it is
+ // safe to access req.
+ if (req)
+ {
+ // process request
+ bool complete = req->processRequest();
+
+ if (complete)
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - complete");
+ lockData();
+ req->setStatus(STATUS_COMPLETE);
+ req->finishRequest(true);
+ if (req->getFlags() & FLAG_AUTO_COMPLETE)
+ {
+ mRequestHash.erase(req);
+ req->deleteRequest();
+ // check();
+ }
+ unlockData();
+ }
+ else
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - retry");
+ //put back on queue and try again in 0.1ms
+ lockData();
+ req->setStatus(STATUS_QUEUED);
+
+ unlockData();
+
+ llassert(!mDataLock->isSelfLocked());
+
+#if 0
+ // try again on next frame
+ // NOTE: tried using "post" with a time in the future, but this
+ // would invariably cause this thread to wait for a long time (10+ ms)
+ // while work is pending
+ bool ret = LL::WorkQueue::postMaybe(
+ mMainQueue,
+ [=]()
+ {
+ LL_PROFILE_ZONE_NAMED("processRequest - retry");
+ mRequestQueue.post([=]()
+ {
+ LL_PROFILE_ZONE_NAMED("processRequest - retry"); // <-- not redundant, track retry on both queues
+ processRequest(req);
+ });
+ });
+ llassert(ret);
+#else
+ using namespace std::chrono_literals;
+ auto retry_time = LL::WorkQueue::TimePoint::clock::now() + 16ms;
+ mRequestQueue.post([=]
+ {
+ LL_PROFILE_ZONE_NAMED("processRequest - retry");
+ if (LL::WorkQueue::TimePoint::clock::now() < retry_time)
+ {
+ auto sleep_time = std::chrono::duration_cast<std::chrono::milliseconds>(retry_time - LL::WorkQueue::TimePoint::clock::now());
+
+ if (sleep_time.count() > 0)
+ {
+ ms_sleep(sleep_time.count());
+ }
+ }
+ processRequest(req);
+ });
+#endif
+
+ }
+ }
+ }
+
+ mIdleThread = true;
+}
+
+// virtual
+bool LLQueuedThread::runCondition()
+{
+ // mRunCondition must be locked here
+ if (mRequestQueue.size() == 0 && mIdleThread)
+ return false;
+ else
+ return true;
+}
+
+// virtual
+void LLQueuedThread::run()
+{
+ // call checPause() immediately so we don't try to do anything before the class is fully constructed
+ checkPause();
+ startThread();
+ mStarted = true;
+
+
+ /*while (1)
+ {
+ LL_PROFILE_ZONE_SCOPED;
+ // this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state.
+ checkPause();
+
+ mIdleThread = false;
+
+ threadedUpdate();
+
+ auto pending_work = processNextRequest();
+
+ if (pending_work == 0)
+ {
+ //LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep");
+ mIdleThread = true;
+ //ms_sleep(1);
+ }
+ //LLThread::yield(); // thread should yield after each request
+ }*/
+ mRequestQueue.runUntilClose();
+
+ endThread();
+ LL_INFOS() << "LLQueuedThread " << mName << " EXITING." << LL_ENDL;
+
+
+}
+
+// virtual
+void LLQueuedThread::startThread()
+{
+}
+
+// virtual
+void LLQueuedThread::endThread()
+{
+}
+
+// virtual
+void LLQueuedThread::threadedUpdate()
+{
+}
+
+//============================================================================
+
+LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 flags) :
+ LLSimpleHashEntry<LLQueuedThread::handle_t>(handle),
+ mStatus(STATUS_UNKNOWN),
+ mFlags(flags)
+{
+}
+
+LLQueuedThread::QueuedRequest::~QueuedRequest()
+{
+ llassert_always(mStatus == STATUS_DELETE);
+}
+
+//virtual
+void LLQueuedThread::QueuedRequest::finishRequest(bool completed)
+{
+}
+
+//virtual
+void LLQueuedThread::QueuedRequest::deleteRequest()
+{
+ llassert_always(mStatus != STATUS_INPROGRESS);
+ setStatus(STATUS_DELETE);
+ delete this;
+}
diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index 339299f081..0f69f7640f 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -1,178 +1,178 @@ -/** - * @file llqueuedthread.h - * @brief - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLQUEUEDTHREAD_H -#define LL_LLQUEUEDTHREAD_H - -#include <queue> -#include <string> -#include <map> -#include <set> - -#include "llatomic.h" - -#include "llthread.h" -#include "llsimplehash.h" -#include "workqueue.h" - -//============================================================================ -// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small -// It is assumed that LLQueuedThreads are rarely created/destroyed. - -class LL_COMMON_API LLQueuedThread : public LLThread -{ - //------------------------------------------------------------------------ -public: - enum status_t { - STATUS_EXPIRED = -1, - STATUS_UNKNOWN = 0, - STATUS_QUEUED = 1, - STATUS_INPROGRESS = 2, - STATUS_COMPLETE = 3, - STATUS_ABORTED = 4, - STATUS_DELETE = 5 - }; - enum flags_t { - FLAG_AUTO_COMPLETE = 1, - FLAG_AUTO_DELETE = 2, // child-class dependent - FLAG_ABORT = 4 - }; - - typedef U32 handle_t; - - //------------------------------------------------------------------------ -public: - - class LL_COMMON_API QueuedRequest : public LLSimpleHashEntry<handle_t> - { - friend class LLQueuedThread; - - protected: - virtual ~QueuedRequest(); // use deleteRequest() - - public: - QueuedRequest(handle_t handle, U32 flags = 0); - - status_t getStatus() - { - return mStatus; - } - U32 getFlags() const - { - return mFlags; - } - - protected: - status_t setStatus(status_t newstatus) - { - status_t oldstatus = mStatus; - mStatus = newstatus; - return oldstatus; - } - void setFlags(U32 flags) - { - // NOTE: flags are |'d - mFlags |= flags; - } - - virtual bool processRequest() = 0; // Return true when request has completed - virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted - virtual void deleteRequest(); // Only method to delete a request - - protected: - LLAtomicBase<status_t> mStatus; - U32 mFlags; - }; - - //------------------------------------------------------------------------ - -public: - static handle_t nullHandle() { return handle_t(0); } - -public: - LLQueuedThread(const std::string& name, bool threaded = true, bool should_pause = false); - virtual ~LLQueuedThread(); - virtual void shutdown(); - -private: - // No copy constructor or copy assignment - LLQueuedThread(const LLQueuedThread&); - LLQueuedThread& operator=(const LLQueuedThread&); - - virtual bool runCondition(void); - virtual void run(void); - virtual void startThread(void); - virtual void endThread(void); - virtual void threadedUpdate(void); - -protected: - handle_t generateHandle(); - bool addRequest(QueuedRequest* req); - void processRequest(QueuedRequest* req); - void incQueue(); - -public: - bool waitForResult(handle_t handle, bool auto_complete = true); - - virtual size_t update(F32 max_time_ms); - size_t updateQueue(F32 max_time_ms); - - void waitOnPending(); - void printQueueStats(); - - virtual size_t getPending(); - bool getThreaded() { return mThreaded ? true : false; } - - // Request accessors - status_t getRequestStatus(handle_t handle); - void abortRequest(handle_t handle, bool autocomplete); - void setFlags(handle_t handle, U32 flags); - bool completeRequest(handle_t handle); - // This is public for support classes like LLWorkerThread, - // but generally the methods above should be used. - QueuedRequest* getRequest(handle_t handle); - - // debug (see source) - bool check(); - -protected: - BOOL mThreaded; // if false, run on main thread and do updates during update() - BOOL mStarted; // required when mThreaded is false to call startThread() from update() - LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle - - //typedef std::set<QueuedRequest*, queued_request_less> request_queue_t; - //request_queue_t mRequestQueue; - LL::WorkQueue mRequestQueue; - LL::WorkQueue::weak_t mMainQueue; - - enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2 - typedef LLSimpleHash<handle_t, REQUEST_HASH_SIZE> request_hash_t; - request_hash_t mRequestHash; - - handle_t mNextHandle; -}; - -#endif // LL_LLQUEUEDTHREAD_H +/**
+ * @file llqueuedthread.h
+ * @brief
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLQUEUEDTHREAD_H
+#define LL_LLQUEUEDTHREAD_H
+
+#include <queue>
+#include <string>
+#include <map>
+#include <set>
+
+#include "llatomic.h"
+
+#include "llthread.h"
+#include "llsimplehash.h"
+#include "workqueue.h"
+
+//============================================================================
+// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small
+// It is assumed that LLQueuedThreads are rarely created/destroyed.
+
+class LL_COMMON_API LLQueuedThread : public LLThread
+{
+ //------------------------------------------------------------------------
+public:
+ enum status_t {
+ STATUS_EXPIRED = -1,
+ STATUS_UNKNOWN = 0,
+ STATUS_QUEUED = 1,
+ STATUS_INPROGRESS = 2,
+ STATUS_COMPLETE = 3,
+ STATUS_ABORTED = 4,
+ STATUS_DELETE = 5
+ };
+ enum flags_t {
+ FLAG_AUTO_COMPLETE = 1,
+ FLAG_AUTO_DELETE = 2, // child-class dependent
+ FLAG_ABORT = 4
+ };
+
+ typedef U32 handle_t;
+
+ //------------------------------------------------------------------------
+public:
+
+ class LL_COMMON_API QueuedRequest : public LLSimpleHashEntry<handle_t>
+ {
+ friend class LLQueuedThread;
+
+ protected:
+ virtual ~QueuedRequest(); // use deleteRequest()
+
+ public:
+ QueuedRequest(handle_t handle, U32 flags = 0);
+
+ status_t getStatus()
+ {
+ return mStatus;
+ }
+ U32 getFlags() const
+ {
+ return mFlags;
+ }
+
+ protected:
+ status_t setStatus(status_t newstatus)
+ {
+ status_t oldstatus = mStatus;
+ mStatus = newstatus;
+ return oldstatus;
+ }
+ void setFlags(U32 flags)
+ {
+ // NOTE: flags are |'d
+ mFlags |= flags;
+ }
+
+ virtual bool processRequest() = 0; // Return true when request has completed
+ virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted
+ virtual void deleteRequest(); // Only method to delete a request
+
+ protected:
+ LLAtomicBase<status_t> mStatus;
+ U32 mFlags;
+ };
+
+ //------------------------------------------------------------------------
+
+public:
+ static handle_t nullHandle() { return handle_t(0); }
+
+public:
+ LLQueuedThread(const std::string& name, bool threaded = true, bool should_pause = false);
+ virtual ~LLQueuedThread();
+ virtual void shutdown();
+
+private:
+ // No copy constructor or copy assignment
+ LLQueuedThread(const LLQueuedThread&);
+ LLQueuedThread& operator=(const LLQueuedThread&);
+
+ virtual bool runCondition(void);
+ virtual void run(void);
+ virtual void startThread(void);
+ virtual void endThread(void);
+ virtual void threadedUpdate(void);
+
+protected:
+ handle_t generateHandle();
+ bool addRequest(QueuedRequest* req);
+ void processRequest(QueuedRequest* req);
+ void incQueue();
+
+public:
+ bool waitForResult(handle_t handle, bool auto_complete = true);
+
+ virtual size_t update(F32 max_time_ms);
+ size_t updateQueue(F32 max_time_ms);
+
+ void waitOnPending();
+ void printQueueStats();
+
+ virtual size_t getPending();
+ bool getThreaded() { return mThreaded; }
+
+ // Request accessors
+ status_t getRequestStatus(handle_t handle);
+ void abortRequest(handle_t handle, bool autocomplete);
+ void setFlags(handle_t handle, U32 flags);
+ bool completeRequest(handle_t handle);
+ // This is public for support classes like LLWorkerThread,
+ // but generally the methods above should be used.
+ QueuedRequest* getRequest(handle_t handle);
+
+ // debug (see source)
+ bool check();
+
+protected:
+ bool mThreaded; // if false, run on main thread and do updates during update()
+ bool mStarted; // required when mThreaded is false to call startThread() from update()
+ LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle
+
+ //typedef std::set<QueuedRequest*, queued_request_less> request_queue_t;
+ //request_queue_t mRequestQueue;
+ LL::WorkQueue mRequestQueue;
+ LL::WorkQueue::weak_t mMainQueue;
+
+ enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2
+ typedef LLSimpleHash<handle_t, REQUEST_HASH_SIZE> request_hash_t;
+ request_hash_t mRequestHash;
+
+ handle_t mNextHandle;
+};
+
+#endif // LL_LLQUEUEDTHREAD_H
diff --git a/indra/llcommon/llregistry.h b/indra/llcommon/llregistry.h index 55dabd57a2..977c8d1935 100644 --- a/indra/llcommon/llregistry.h +++ b/indra/llcommon/llregistry.h @@ -1,353 +1,353 @@ -/** - * @file llregistry.h - * @brief template classes for registering name, value pairs in nested scopes, statically, etc. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLREGISTRY_H -#define LL_LLREGISTRY_H - -#include <list> - -#include "llsingleton.h" -#include "llstl.h" - -template <typename T> -struct LLRegistryDefaultComparator -{ - bool operator()(const T& lhs, const T& rhs) const - { - using std::less; - return less<T>()(lhs, rhs); - } -}; - -template <typename KEY, typename VALUE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> > -class LLRegistry -{ -public: - typedef LLRegistry<KEY, VALUE, COMPARATOR> registry_t; - typedef const KEY& ref_const_key_t; - typedef const VALUE& ref_const_value_t; - typedef const VALUE* ptr_const_value_t; - typedef VALUE* ptr_value_t; - - class Registrar - { - friend class LLRegistry<KEY, VALUE, COMPARATOR>; - public: - typedef std::map<KEY, VALUE, COMPARATOR> registry_map_t; - - bool add(ref_const_key_t key, ref_const_value_t value) - { - if (mMap.insert(std::make_pair(key, value)).second == false) - { - LL_WARNS() << "Tried to register " << key << " but it was already registered!" << LL_ENDL; - return false; - } - return true; - } - - void remove(ref_const_key_t key) - { - mMap.erase(key); - } - - void replace(ref_const_key_t key, ref_const_value_t value) - { - mMap[key] = value; - } - - typename registry_map_t::const_iterator beginItems() const - { - return mMap.begin(); - } - - typename registry_map_t::const_iterator endItems() const - { - return mMap.end(); - } - - protected: - ptr_value_t getValue(ref_const_key_t key) - { - typename registry_map_t::iterator found_it = mMap.find(key); - if (found_it != mMap.end()) - { - return &(found_it->second); - } - return NULL; - } - - ptr_const_value_t getValue(ref_const_key_t key) const - { - typename registry_map_t::const_iterator found_it = mMap.find(key); - if (found_it != mMap.end()) - { - return &(found_it->second); - } - return NULL; - } - - // if the registry is used to store pointers, and null values are valid entries - // then use this function to check the existence of an entry - bool exists(ref_const_key_t key) const - { - return mMap.find(key) != mMap.end(); - } - - bool empty() const - { - return mMap.empty(); - } - - protected: - // use currentRegistrar() or defaultRegistrar() - Registrar() {} - ~Registrar() {} - - private: - registry_map_t mMap; - }; - - typedef typename std::list<Registrar*> scope_list_t; - typedef typename std::list<Registrar*>::iterator scope_list_iterator_t; - typedef typename std::list<Registrar*>::const_iterator scope_list_const_iterator_t; - - LLRegistry() - {} - - ~LLRegistry() {} - - ptr_value_t getValue(ref_const_key_t key) - { - for(Registrar* scope : mActiveScopes) - { - ptr_value_t valuep = scope->getValue(key); - if (valuep != NULL) return valuep; - } - return mDefaultRegistrar.getValue(key); - } - - ptr_const_value_t getValue(ref_const_key_t key) const - { - for(const Registrar* scope : mActiveScopes) - { - ptr_const_value_t valuep = scope->getValue(key); - if (valuep != NULL) return valuep; - } - return mDefaultRegistrar.getValue(key); - } - - bool exists(ref_const_key_t key) const - { - for(const Registrar* scope : mActiveScopes) - { - if (scope->exists(key)) return true; - } - - return mDefaultRegistrar.exists(key); - } - - bool empty() const - { - for(const Registrar* scope : mActiveScopes) - { - if (!scope->empty()) return false; - } - - return mDefaultRegistrar.empty(); - } - - - Registrar& defaultRegistrar() - { - return mDefaultRegistrar; - } - - const Registrar& defaultRegistrar() const - { - return mDefaultRegistrar; - } - - - Registrar& currentRegistrar() - { - if (!mActiveScopes.empty()) - { - return *mActiveScopes.front(); - } - - return mDefaultRegistrar; - } - - const Registrar& currentRegistrar() const - { - if (!mActiveScopes.empty()) - { - return *mActiveScopes.front(); - } - - return mDefaultRegistrar; - } - - -protected: - void addScope(Registrar* scope) - { - // newer scopes go up front - mActiveScopes.insert(mActiveScopes.begin(), scope); - } - - void removeScope(Registrar* scope) - { - // O(N) but should be near the beggining and N should be small and this is safer than storing iterators - scope_list_iterator_t iter = std::find(mActiveScopes.begin(), mActiveScopes.end(), scope); - if (iter != mActiveScopes.end()) - { - mActiveScopes.erase(iter); - } - } - -private: - scope_list_t mActiveScopes; - Registrar mDefaultRegistrar; -}; - -template <typename KEY, typename VALUE, typename DERIVED_TYPE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> > -class LLRegistrySingleton - : public LLRegistry<KEY, VALUE, COMPARATOR>, - public LLSingleton<DERIVED_TYPE> -{ - // This LLRegistrySingleton doesn't use LLSINGLETON(LLRegistrySingleton) - // because the concrete class is actually DERIVED_TYPE, not - // LLRegistrySingleton. So each concrete subclass needs - // LLSINGLETON(whatever) -- not this intermediate base class. -public: - typedef LLRegistry<KEY, VALUE, COMPARATOR> registry_t; - typedef const KEY& ref_const_key_t; - typedef const VALUE& ref_const_value_t; - typedef VALUE* ptr_value_t; - typedef const VALUE* ptr_const_value_t; - typedef LLSingleton<DERIVED_TYPE> singleton_t; - - class ScopedRegistrar : public registry_t::Registrar - { - public: - ScopedRegistrar(bool push_scope = true) - { - if (push_scope) - { - pushScope(); - } - } - - ~ScopedRegistrar() - { - if (singleton_t::instanceExists()) - { - popScope(); - } - } - - void pushScope() - { - singleton_t::instance().addScope(this); - } - - void popScope() - { - singleton_t::instance().removeScope(this); - } - - ptr_value_t getValueFromScope(ref_const_key_t key) - { - return getValue(key); - } - - ptr_const_value_t getValueFromScope(ref_const_key_t key) const - { - return getValue(key); - } - - private: - typename std::list<typename registry_t::Registrar*>::iterator mListIt; - }; - - class StaticRegistrar : public registry_t::Registrar - { - public: - virtual ~StaticRegistrar() {} - StaticRegistrar(ref_const_key_t key, ref_const_value_t value) - { - if (singleton_t::instance().exists(key)) - { - LL_ERRS() << "Duplicate registry entry under key \"" << key << "\"" << LL_ENDL; - } - singleton_t::instance().mStaticScope->add(key, value); - } - }; - - // convenience functions - typedef typename LLRegistry<KEY, VALUE, COMPARATOR>::Registrar& ref_registrar_t; - static ref_registrar_t currentRegistrar() - { - return singleton_t::instance().registry_t::currentRegistrar(); - } - - static ref_registrar_t defaultRegistrar() - { - return singleton_t::instance().registry_t::defaultRegistrar(); - } - - static ptr_value_t getValue(ref_const_key_t key) - { - return singleton_t::instance().registry_t::getValue(key); - } - -protected: - // DERIVED_TYPE needs to derive from LLRegistrySingleton - LLRegistrySingleton() - : mStaticScope(NULL) - {} - - virtual void initSingleton() - { - mStaticScope = new ScopedRegistrar(); - } - - virtual ~LLRegistrySingleton() - { - delete mStaticScope; - } - -private: - ScopedRegistrar* mStaticScope; -}; - -// helper macro for doing static registration -#define GLUED_TOKEN(x, y) x ## y -#define GLUE_TOKENS(x, y) GLUED_TOKEN(x, y) -#define LLREGISTER_STATIC(REGISTRY, KEY, VALUE) static REGISTRY::StaticRegistrar GLUE_TOKENS(reg, __LINE__)(KEY, VALUE); - -#endif +/**
+ * @file llregistry.h
+ * @brief template classes for registering name, value pairs in nested scopes, statically, etc.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLREGISTRY_H
+#define LL_LLREGISTRY_H
+
+#include <list>
+
+#include "llsingleton.h"
+#include "llstl.h"
+
+template <typename T>
+struct LLRegistryDefaultComparator
+{
+ bool operator()(const T& lhs, const T& rhs) const
+ {
+ using std::less;
+ return less<T>()(lhs, rhs);
+ }
+};
+
+template <typename KEY, typename VALUE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> >
+class LLRegistry
+{
+public:
+ typedef LLRegistry<KEY, VALUE, COMPARATOR> registry_t;
+ typedef const KEY& ref_const_key_t;
+ typedef const VALUE& ref_const_value_t;
+ typedef const VALUE* ptr_const_value_t;
+ typedef VALUE* ptr_value_t;
+
+ class Registrar
+ {
+ friend class LLRegistry<KEY, VALUE, COMPARATOR>;
+ public:
+ typedef std::map<KEY, VALUE, COMPARATOR> registry_map_t;
+
+ bool add(ref_const_key_t key, ref_const_value_t value)
+ {
+ if (!mMap.insert(std::make_pair(key, value)).second)
+ {
+ LL_WARNS() << "Tried to register " << key << " but it was already registered!" << LL_ENDL;
+ return false;
+ }
+ return true;
+ }
+
+ void remove(ref_const_key_t key)
+ {
+ mMap.erase(key);
+ }
+
+ void replace(ref_const_key_t key, ref_const_value_t value)
+ {
+ mMap[key] = value;
+ }
+
+ typename registry_map_t::const_iterator beginItems() const
+ {
+ return mMap.begin();
+ }
+
+ typename registry_map_t::const_iterator endItems() const
+ {
+ return mMap.end();
+ }
+
+ protected:
+ ptr_value_t getValue(ref_const_key_t key)
+ {
+ typename registry_map_t::iterator found_it = mMap.find(key);
+ if (found_it != mMap.end())
+ {
+ return &(found_it->second);
+ }
+ return NULL;
+ }
+
+ ptr_const_value_t getValue(ref_const_key_t key) const
+ {
+ typename registry_map_t::const_iterator found_it = mMap.find(key);
+ if (found_it != mMap.end())
+ {
+ return &(found_it->second);
+ }
+ return NULL;
+ }
+
+ // if the registry is used to store pointers, and null values are valid entries
+ // then use this function to check the existence of an entry
+ bool exists(ref_const_key_t key) const
+ {
+ return mMap.find(key) != mMap.end();
+ }
+
+ bool empty() const
+ {
+ return mMap.empty();
+ }
+
+ protected:
+ // use currentRegistrar() or defaultRegistrar()
+ Registrar() {}
+ ~Registrar() {}
+
+ private:
+ registry_map_t mMap;
+ };
+
+ typedef typename std::list<Registrar*> scope_list_t;
+ typedef typename std::list<Registrar*>::iterator scope_list_iterator_t;
+ typedef typename std::list<Registrar*>::const_iterator scope_list_const_iterator_t;
+
+ LLRegistry()
+ {}
+
+ ~LLRegistry() {}
+
+ ptr_value_t getValue(ref_const_key_t key)
+ {
+ for(Registrar* scope : mActiveScopes)
+ {
+ ptr_value_t valuep = scope->getValue(key);
+ if (valuep != NULL) return valuep;
+ }
+ return mDefaultRegistrar.getValue(key);
+ }
+
+ ptr_const_value_t getValue(ref_const_key_t key) const
+ {
+ for(const Registrar* scope : mActiveScopes)
+ {
+ ptr_const_value_t valuep = scope->getValue(key);
+ if (valuep != NULL) return valuep;
+ }
+ return mDefaultRegistrar.getValue(key);
+ }
+
+ bool exists(ref_const_key_t key) const
+ {
+ for(const Registrar* scope : mActiveScopes)
+ {
+ if (scope->exists(key)) return true;
+ }
+
+ return mDefaultRegistrar.exists(key);
+ }
+
+ bool empty() const
+ {
+ for(const Registrar* scope : mActiveScopes)
+ {
+ if (!scope->empty()) return false;
+ }
+
+ return mDefaultRegistrar.empty();
+ }
+
+
+ Registrar& defaultRegistrar()
+ {
+ return mDefaultRegistrar;
+ }
+
+ const Registrar& defaultRegistrar() const
+ {
+ return mDefaultRegistrar;
+ }
+
+
+ Registrar& currentRegistrar()
+ {
+ if (!mActiveScopes.empty())
+ {
+ return *mActiveScopes.front();
+ }
+
+ return mDefaultRegistrar;
+ }
+
+ const Registrar& currentRegistrar() const
+ {
+ if (!mActiveScopes.empty())
+ {
+ return *mActiveScopes.front();
+ }
+
+ return mDefaultRegistrar;
+ }
+
+
+protected:
+ void addScope(Registrar* scope)
+ {
+ // newer scopes go up front
+ mActiveScopes.insert(mActiveScopes.begin(), scope);
+ }
+
+ void removeScope(Registrar* scope)
+ {
+ // O(N) but should be near the beggining and N should be small and this is safer than storing iterators
+ scope_list_iterator_t iter = std::find(mActiveScopes.begin(), mActiveScopes.end(), scope);
+ if (iter != mActiveScopes.end())
+ {
+ mActiveScopes.erase(iter);
+ }
+ }
+
+private:
+ scope_list_t mActiveScopes;
+ Registrar mDefaultRegistrar;
+};
+
+template <typename KEY, typename VALUE, typename DERIVED_TYPE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> >
+class LLRegistrySingleton
+ : public LLRegistry<KEY, VALUE, COMPARATOR>,
+ public LLSingleton<DERIVED_TYPE>
+{
+ // This LLRegistrySingleton doesn't use LLSINGLETON(LLRegistrySingleton)
+ // because the concrete class is actually DERIVED_TYPE, not
+ // LLRegistrySingleton. So each concrete subclass needs
+ // LLSINGLETON(whatever) -- not this intermediate base class.
+public:
+ typedef LLRegistry<KEY, VALUE, COMPARATOR> registry_t;
+ typedef const KEY& ref_const_key_t;
+ typedef const VALUE& ref_const_value_t;
+ typedef VALUE* ptr_value_t;
+ typedef const VALUE* ptr_const_value_t;
+ typedef LLSingleton<DERIVED_TYPE> singleton_t;
+
+ class ScopedRegistrar : public registry_t::Registrar
+ {
+ public:
+ ScopedRegistrar(bool push_scope = true)
+ {
+ if (push_scope)
+ {
+ pushScope();
+ }
+ }
+
+ ~ScopedRegistrar()
+ {
+ if (singleton_t::instanceExists())
+ {
+ popScope();
+ }
+ }
+
+ void pushScope()
+ {
+ singleton_t::instance().addScope(this);
+ }
+
+ void popScope()
+ {
+ singleton_t::instance().removeScope(this);
+ }
+
+ ptr_value_t getValueFromScope(ref_const_key_t key)
+ {
+ return getValue(key);
+ }
+
+ ptr_const_value_t getValueFromScope(ref_const_key_t key) const
+ {
+ return getValue(key);
+ }
+
+ private:
+ typename std::list<typename registry_t::Registrar*>::iterator mListIt;
+ };
+
+ class StaticRegistrar : public registry_t::Registrar
+ {
+ public:
+ virtual ~StaticRegistrar() {}
+ StaticRegistrar(ref_const_key_t key, ref_const_value_t value)
+ {
+ if (singleton_t::instance().exists(key))
+ {
+ LL_ERRS() << "Duplicate registry entry under key \"" << key << "\"" << LL_ENDL;
+ }
+ singleton_t::instance().mStaticScope->add(key, value);
+ }
+ };
+
+ // convenience functions
+ typedef typename LLRegistry<KEY, VALUE, COMPARATOR>::Registrar& ref_registrar_t;
+ static ref_registrar_t currentRegistrar()
+ {
+ return singleton_t::instance().registry_t::currentRegistrar();
+ }
+
+ static ref_registrar_t defaultRegistrar()
+ {
+ return singleton_t::instance().registry_t::defaultRegistrar();
+ }
+
+ static ptr_value_t getValue(ref_const_key_t key)
+ {
+ return singleton_t::instance().registry_t::getValue(key);
+ }
+
+protected:
+ // DERIVED_TYPE needs to derive from LLRegistrySingleton
+ LLRegistrySingleton()
+ : mStaticScope(NULL)
+ {}
+
+ virtual void initSingleton()
+ {
+ mStaticScope = new ScopedRegistrar();
+ }
+
+ virtual ~LLRegistrySingleton()
+ {
+ delete mStaticScope;
+ }
+
+private:
+ ScopedRegistrar* mStaticScope;
+};
+
+// helper macro for doing static registration
+#define GLUED_TOKEN(x, y) x ## y
+#define GLUE_TOKENS(x, y) GLUED_TOKEN(x, y)
+#define LLREGISTER_STATIC(REGISTRY, KEY, VALUE) static REGISTRY::StaticRegistrar GLUE_TOKENS(reg, __LINE__)(KEY, VALUE);
+
+#endif
diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index bb2b8681f7..2fe24693dd 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -1,25 +1,25 @@ -/** +/** * @file llsdjson.cpp * @brief LLSD flexible data system * * $LicenseInfo:firstyear=2015&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2015, 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$ */ @@ -31,46 +31,56 @@ #include "llsdjson.h" +#include "llsdutil.h" #include "llerror.h" #include "../llmath/llmath.h" +#if LL_WINDOWS +#pragma warning (push) +#pragma warning (disable : 4702) // compiler thinks unreachable code +#endif +#include <boost/json/src.hpp> +#if LL_WINDOWS +#pragma warning (pop) +#endif + + + //========================================================================= -LLSD LlsdFromJson(const Json::Value &val) +LLSD LlsdFromJson(const boost::json::value& val) { LLSD result; - switch (val.type()) + switch (val.kind()) { default: - case Json::nullValue: - break; - case Json::intValue: - result = LLSD(static_cast<LLSD::Integer>(val.asInt())); + case boost::json::kind::null: break; - case Json::uintValue: - result = LLSD(static_cast<LLSD::Integer>(val.asUInt())); + case boost::json::kind::int64: + case boost::json::kind::uint64: + result = LLSD(val.to_number<int64_t>()); break; - case Json::realValue: - result = LLSD(static_cast<LLSD::Real>(val.asDouble())); + case boost::json::kind::double_: + result = LLSD(val.to_number<double>()); break; - case Json::stringValue: - result = LLSD(static_cast<LLSD::String>(val.asString())); + case boost::json::kind::string: + result = LLSD(boost::json::value_to<std::string>(val)); break; - case Json::booleanValue: - result = LLSD(static_cast<LLSD::Boolean>(val.asBool())); + case boost::json::kind::bool_: + result = LLSD(val.as_bool()); break; - case Json::arrayValue: + case boost::json::kind::array: result = LLSD::emptyArray(); - for (Json::ValueConstIterator it = val.begin(); it != val.end(); ++it) + for (const auto &element : val.as_array()) { - result.append(LlsdFromJson((*it))); + result.append(LlsdFromJson(element)); } break; - case Json::objectValue: + case boost::json::kind::object: result = LLSD::emptyMap(); - for (Json::ValueConstIterator it = val.begin(); it != val.end(); ++it) + for (const auto& element : val.as_object()) { - result[it.memberName()] = LlsdFromJson((*it)); + result[element.key()] = LlsdFromJson(element.value()); } break; } @@ -78,44 +88,48 @@ LLSD LlsdFromJson(const Json::Value &val) } //========================================================================= -Json::Value LlsdToJson(const LLSD &val) +boost::json::value LlsdToJson(const LLSD &val) { - Json::Value result; + boost::json::value result; switch (val.type()) { case LLSD::TypeUndefined: - result = Json::Value::null; + result = nullptr; break; case LLSD::TypeBoolean: - result = Json::Value(static_cast<bool>(val.asBoolean())); + result = val.asBoolean(); break; case LLSD::TypeInteger: - result = Json::Value(static_cast<int>(val.asInteger())); + result = val.asInteger(); break; case LLSD::TypeReal: - result = Json::Value(static_cast<double>(val.asReal())); + result = val.asReal(); break; case LLSD::TypeURI: case LLSD::TypeDate: case LLSD::TypeUUID: case LLSD::TypeString: - result = Json::Value(val.asString()); + result = val.asString(); break; case LLSD::TypeMap: - result = Json::Value(Json::objectValue); - for (LLSD::map_const_iterator it = val.beginMap(); it != val.endMap(); ++it) + { + boost::json::object& obj = result.emplace_object(); + for (const auto& llsd_dat : llsd::inMap(val)) { - result[it->first] = LlsdToJson(it->second); + obj[llsd_dat.first] = LlsdToJson(llsd_dat.second); } break; + } case LLSD::TypeArray: - result = Json::Value(Json::arrayValue); - for (LLSD::array_const_iterator it = val.beginArray(); it != val.endArray(); ++it) + { + boost::json::array& json_array = result.emplace_array(); + for (const auto& llsd_dat : llsd::inArray(val)) { - result.append(LlsdToJson(*it)); + json_array.push_back(LlsdToJson(llsd_dat)); } break; + } case LLSD::TypeBinary: default: LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" << val.type() << ")." << LL_ENDL; diff --git a/indra/llcommon/llsdjson.h b/indra/llcommon/llsdjson.h index 79bf2c56fa..7173e26046 100644 --- a/indra/llcommon/llsdjson.h +++ b/indra/llcommon/llsdjson.h @@ -1,25 +1,25 @@ -/** +/** * @file llsdjson.cpp * @brief LLSD flexible data system * * $LicenseInfo:firstyear=2015&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2015, 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$ */ @@ -34,12 +34,12 @@ #include "stdtypes.h" #include "llsd.h" -#include "json/value.h" +#include <boost/json.hpp> -/// Convert a parsed JSON structure into LLSD maintaining member names and +/// Convert a parsed JSON structure into LLSD maintaining member names and /// array indexes. /// JSON/JavaScript types are converted as follows: -/// +/// /// JSON Type | LLSD Type /// --------------+-------------- /// null | undefined @@ -50,14 +50,14 @@ /// boolean | LLSD::Boolean /// array | LLSD::Array /// object | LLSD::Map -/// +/// /// For maps and arrays child entries will be converted and added to the structure. /// Order is preserved for an array but not for objects. -LLSD LlsdFromJson(const Json::Value &val); +LLSD LlsdFromJson(const boost::json::value &val); -/// Convert an LLSD object into Parsed JSON object maintaining member names and +/// Convert an LLSD object into Parsed JSON object maintaining member names and /// array indexs. -/// +/// /// Types are converted as follows: /// LLSD Type | JSON Type /// --------------+---------------- @@ -71,7 +71,7 @@ LLSD LlsdFromJson(const Json::Value &val); /// TypeUUID | string /// TypeMap | object /// TypeArray | array -/// TypeBinary | unsupported -Json::Value LlsdToJson(const LLSD &val); +/// TypeBinary | unsupported +boost::json::value LlsdToJson(const LLSD &val); #endif // LL_LLSDJSON_H diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index d5af31a28e..612f71e8bc 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -1,2470 +1,2470 @@ -/** - * @file llsdserialize.cpp - * @author Phoenix - * @date 2006-03-05 - * @brief Implementation of LLSD parsers and formatters - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" -#include "llsdserialize.h" -#include "llpointer.h" -#include "llstreamtools.h" // for fullread - -#include <iostream> -#include "apr_base64.h" - -#include <boost/iostreams/device/array.hpp> -#include <boost/iostreams/stream.hpp> - -#ifdef LL_USESYSTEMLIBS -# include <zlib.h> -#else -# include "zlib-ng/zlib.h" // for davep's dirty little zip functions -#endif - -#if !LL_WINDOWS -#include <netinet/in.h> // htonl & ntohl -#endif - -#include "lldate.h" -#include "llmemorystream.h" -#include "llsd.h" -#include "llstring.h" -#include "lluri.h" - -// File constants -static const size_t MAX_HDR_LEN = 20; -static const S32 UNZIP_LLSD_MAX_DEPTH = 96; -static const char LEGACY_NON_HEADER[] = "<llsd>"; -const std::string LLSD_BINARY_HEADER("LLSD/Binary"); -const std::string LLSD_XML_HEADER("LLSD/XML"); -const std::string LLSD_NOTATION_HEADER("llsd/notation"); - -//used to deflate a gzipped asset (currently used for navmeshes) -#define windowBits 15 -#define ENABLE_ZLIB_GZIP 32 - -// If we published this in llsdserialize.h, we could use it in the -// implementation of LLSDOStreamer's operator<<(). -template <class Formatter> -void format_using(const LLSD& data, std::ostream& ostr, - LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY) -{ - LLPointer<Formatter> f{ new Formatter }; - f->format(data, ostr, options); -} - -template <class Parser> -S32 parse_using(std::istream& istr, LLSD& data, size_t max_bytes, S32 max_depth=-1) -{ - LLPointer<Parser> p{ new Parser }; - return p->parse(istr, data, max_bytes, max_depth); -} - -/** - * LLSDSerialize - */ - -// static -void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type, - LLSDFormatter::EFormatterOptions options) -{ - LLPointer<LLSDFormatter> f = NULL; - - switch (type) - { - case LLSD_BINARY: - str << "<? " << LLSD_BINARY_HEADER << " ?>\n"; - f = new LLSDBinaryFormatter; - break; - - case LLSD_XML: - str << "<? " << LLSD_XML_HEADER << " ?>\n"; - f = new LLSDXMLFormatter; - break; - - case LLSD_NOTATION: - str << "<? " << LLSD_NOTATION_HEADER << " ?>\n"; - f = new LLSDNotationFormatter; - break; - - default: - LL_WARNS() << "serialize request for unknown ELLSD_Serialize" << LL_ENDL; - } - - if (f.notNull()) - { - f->format(sd, str, options); - } -} - -// static -bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str, llssize max_bytes) -{ - char hdr_buf[MAX_HDR_LEN + 1] = ""; /* Flawfinder: ignore */ - bool fail_if_not_legacy = false; - - /* - * Get the first line before anything. Don't read more than max_bytes: - * this get() overload reads no more than (count-1) bytes into the - * specified buffer. In the usual case when max_bytes exceeds - * sizeof(hdr_buf), get() will read no more than sizeof(hdr_buf)-2. - */ - llssize max_hdr_read = MAX_HDR_LEN; - if (max_bytes != LLSDSerialize::SIZE_UNLIMITED) - { - max_hdr_read = llmin(max_bytes + 1, max_hdr_read); - } - str.get(hdr_buf, max_hdr_read, '\n'); - auto inbuf = str.gcount(); - - // https://en.cppreference.com/w/cpp/io/basic_istream/get - // When the get() above sees the specified delimiter '\n', it stops there - // without pulling it from the stream. If it turns out that the stream - // does NOT contain a header, and the content includes meaningful '\n', - // it's important to pull that into hdr_buf too. - if (inbuf < max_bytes && str.get(hdr_buf[inbuf])) - { - // got the delimiting '\n' - ++inbuf; - // None of the following requires that hdr_buf contain a final '\0' - // byte. We could store one if needed, since even the incremented - // inbuf won't exceed sizeof(hdr_buf)-1, but there's no need. - } - std::string header{ hdr_buf, static_cast<std::string::size_type>(inbuf) }; - if (str.fail()) - { - str.clear(); - fail_if_not_legacy = true; - } - - if (!strncasecmp(LEGACY_NON_HEADER, hdr_buf, strlen(LEGACY_NON_HEADER))) /* Flawfinder: ignore */ - { // Create a LLSD XML parser, and parse the first chunk read above. - LLSDXMLParser x; - x.parsePart(hdr_buf, inbuf); // Parse the first part that was already read - auto parsed = x.parse(str, sd, max_bytes - inbuf); // Parse the rest of it - // Formally we should probably check (parsed != PARSE_FAILURE && - // parsed > 0), but since PARSE_FAILURE is -1, this suffices. - return (parsed > 0); - } - - if (fail_if_not_legacy) - { - LL_WARNS() << "deserialize LLSD parse failure" << LL_ENDL; - return false; - } - - /* - * Remove the newline chars - */ - std::string::size_type lastchar = header.find_last_not_of("\r\n"); - if (lastchar != std::string::npos) - { - // It's important that find_last_not_of() returns size_type, which is - // why lastchar explicitly declares the type above. erase(size_type) - // erases from that offset to the end of the string, whereas - // erase(iterator) erases only a single character. - header.erase(lastchar+1); - } - - // trim off the <? ... ?> header syntax - auto start = header.find_first_not_of("<? "); - if (start != std::string::npos) - { - auto end = header.find_first_of(" ?", start); - if (end != std::string::npos) - { - header = header.substr(start, end - start); - ws(str); - } - } - /* - * Create the parser as appropriate - */ - if (0 == LLStringUtil::compareInsensitive(header, LLSD_BINARY_HEADER)) - { - return (parse_using<LLSDBinaryParser>(str, sd, max_bytes-inbuf) > 0); - } - else if (0 == LLStringUtil::compareInsensitive(header, LLSD_XML_HEADER)) - { - return (parse_using<LLSDXMLParser>(str, sd, max_bytes-inbuf) > 0); - } - else if (0 == LLStringUtil::compareInsensitive(header, LLSD_NOTATION_HEADER)) - { - return (parse_using<LLSDNotationParser>(str, sd, max_bytes-inbuf) > 0); - } - else // no header we recognize - { - LLPointer<LLSDParser> p; - if (inbuf && hdr_buf[0] == '<') - { - // looks like XML - LL_DEBUGS() << "deserialize request with no header, assuming XML" << LL_ENDL; - p = new LLSDXMLParser; - } - else - { - // assume notation - LL_DEBUGS() << "deserialize request with no header, assuming notation" << LL_ENDL; - p = new LLSDNotationParser; - } - // Since we've already read 'inbuf' bytes into 'hdr_buf', prepend that - // data to whatever remains in 'str'. - LLMemoryStreamBuf already(reinterpret_cast<const U8*>(hdr_buf), inbuf); - cat_streambuf prebuff(&already, str.rdbuf()); - std::istream prepend(&prebuff); -#if 1 - return (p->parse(prepend, sd, max_bytes) > 0); -#else - // debugging the reconstituted 'prepend' stream - // allocate a buffer that we hope is big enough for the whole thing - std::vector<char> wholemsg((max_bytes == size_t(SIZE_UNLIMITED))? 1024 : max_bytes); - prepend.read(wholemsg.data(), std::min(max_bytes, wholemsg.size())); - LLMemoryStream replay(reinterpret_cast<const U8*>(wholemsg.data()), prepend.gcount()); - auto success{ p->parse(replay, sd, prepend.gcount()) > 0 }; - { - LL_DEBUGS() << (success? "parsed: $$" : "failed: '") - << std::string(wholemsg.data(), llmin(prepend.gcount(), 100)) << "$$" - << LL_ENDL; - } - return success; -#endif - } -} - -/** - * Endian handlers - */ -#if LL_BIG_ENDIAN -U64 ll_htonll(U64 hostlonglong) { return hostlonglong; } -U64 ll_ntohll(U64 netlonglong) { return netlonglong; } -F64 ll_htond(F64 hostlonglong) { return hostlonglong; } -F64 ll_ntohd(F64 netlonglong) { return netlonglong; } -#else -// I read some comments one a indicating that doing an integer add -// here would be faster than a bitwise or. For now, the or has -// programmer clarity, since the intended outcome matches the -// operation. -U64 ll_htonll(U64 hostlonglong) -{ - return ((U64)(htonl((U32)((hostlonglong >> 32) & 0xFFFFFFFF))) | - ((U64)(htonl((U32)(hostlonglong & 0xFFFFFFFF))) << 32)); -} -U64 ll_ntohll(U64 netlonglong) -{ - return ((U64)(ntohl((U32)((netlonglong >> 32) & 0xFFFFFFFF))) | - ((U64)(ntohl((U32)(netlonglong & 0xFFFFFFFF))) << 32)); -} -union LLEndianSwapper -{ - F64 d; - U64 i; -}; -F64 ll_htond(F64 hostdouble) -{ - LLEndianSwapper tmp; - tmp.d = hostdouble; - tmp.i = ll_htonll(tmp.i); - return tmp.d; -} -F64 ll_ntohd(F64 netdouble) -{ - LLEndianSwapper tmp; - tmp.d = netdouble; - tmp.i = ll_ntohll(tmp.i); - return tmp.d; -} -#endif - -/** - * Local functions. - */ -/** - * @brief Figure out what kind of string it is (raw or delimited) and handoff. - * - * @param istr The stream to read from. - * @param value [out] The string which was found. - * @param max_bytes The maximum possible length of the string. Passing in - * a negative value will skip this check. - * @return Returns number of bytes read off of the stream. Returns - * PARSE_FAILURE (-1) on failure. - */ -llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes); - -/** - * @brief Parse a delimited string. - * - * @param istr The stream to read from, with the delimiter already popped. - * @param value [out] The string which was found. - * @param d The delimiter to use. - * @return Returns number of bytes read off of the stream. Returns - * PARSE_FAILURE (-1) on failure. - */ -llssize deserialize_string_delim(std::istream& istr, std::string& value, char d); - -/** - * @brief Read a raw string off the stream. - * - * @param istr The stream to read from, with the (len) parameter - * leading the stream. - * @param value [out] The string which was found. - * @param d The delimiter to use. - * @param max_bytes The maximum possible length of the string. Passing in - * a negative value will skip this check. - * @return Returns number of bytes read off of the stream. Returns - * PARSE_FAILURE (-1) on failure. - */ -llssize deserialize_string_raw( - std::istream& istr, - std::string& value, - llssize max_bytes); - -/** - * @brief helper method for dealing with the different notation boolean format. - * - * @param istr The stream to read from with the leading character stripped. - * @param data [out] the result of the parse. - * @param compare The string to compare the boolean against - * @param vale The value to assign to data if the parse succeeds. - * @return Returns number of bytes read off of the stream. Returns - * PARSE_FAILURE (-1) on failure. - */ -llssize deserialize_boolean( - std::istream& istr, - LLSD& data, - const std::string& compare, - bool value); - -/** - * @brief Do notation escaping of a string to an ostream. - * - * @param value The string to escape and serialize - * @param str The stream to serialize to. - */ -void serialize_string(const std::string& value, std::ostream& str); - - -/** - * Local constants. - */ -static const std::string NOTATION_TRUE_SERIAL("true"); -static const std::string NOTATION_FALSE_SERIAL("false"); - -static const char BINARY_TRUE_SERIAL = '1'; -static const char BINARY_FALSE_SERIAL = '0'; - - -/** - * LLSDParser - */ -LLSDParser::LLSDParser() - : mCheckLimits(true), mMaxBytesLeft(0), mParseLines(false) -{ -} - -// virtual -LLSDParser::~LLSDParser() -{ } - -S32 LLSDParser::parse(std::istream& istr, LLSD& data, llssize max_bytes, S32 max_depth) -{ - mCheckLimits = (LLSDSerialize::SIZE_UNLIMITED == max_bytes) ? false : true; - mMaxBytesLeft = max_bytes; - return doParse(istr, data, max_depth); -} - - -// Parse using routine to get() lines, faster than parse() -S32 LLSDParser::parseLines(std::istream& istr, LLSD& data) -{ - mCheckLimits = false; - mParseLines = true; - return doParse(istr, data); -} - - -int LLSDParser::get(std::istream& istr) const -{ - if(mCheckLimits) --mMaxBytesLeft; - return istr.get(); -} - -std::istream& LLSDParser::get( - std::istream& istr, - char* s, - std::streamsize n, - char delim) const -{ - istr.get(s, n, delim); - if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); - return istr; -} - -std::istream& LLSDParser::get( - std::istream& istr, - std::streambuf& sb, - char delim) const -{ - istr.get(sb, delim); - if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); - return istr; -} - -std::istream& LLSDParser::ignore(std::istream& istr) const -{ - istr.ignore(); - if(mCheckLimits) --mMaxBytesLeft; - return istr; -} - -std::istream& LLSDParser::putback(std::istream& istr, char c) const -{ - istr.putback(c); - if(mCheckLimits) ++mMaxBytesLeft; - return istr; -} - -std::istream& LLSDParser::read( - std::istream& istr, - char* s, - std::streamsize n) const -{ - istr.read(s, n); - if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); - return istr; -} - -void LLSDParser::account(llssize bytes) const -{ - if(mCheckLimits) mMaxBytesLeft -= bytes; -} - - -/** - * LLSDNotationParser - */ -LLSDNotationParser::LLSDNotationParser() -{ -} - -// virtual -LLSDNotationParser::~LLSDNotationParser() -{ } - -// virtual -S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - // map: { string:object, string:object } - // array: [ object, object, object ] - // undef: ! - // boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE - // integer: i#### - // real: r#### - // uuid: u#### - // string: "g'day" | 'have a "nice" day' | s(size)"raw data" - // uri: l"escaped" - // date: d"YYYY-MM-DDTHH:MM:SS.FFZ" - // binary: b##"ff3120ab1" | b(size)"raw data" - char c; - c = istr.peek(); - if (max_depth == 0) - { - return PARSE_FAILURE; - } - while(isspace(c)) - { - // pop the whitespace. - c = get(istr); - c = istr.peek(); - continue; - } - if(!istr.good()) - { - return 0; - } - S32 parse_count = 1; - switch(c) - { - case '{': - { - S32 child_count = parseMap(istr, data, max_depth - 1); - if((child_count == PARSE_FAILURE) || data.isUndefined()) - { - parse_count = PARSE_FAILURE; - } - else - { - parse_count += child_count; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading map." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '[': - { - S32 child_count = parseArray(istr, data, max_depth - 1); - if((child_count == PARSE_FAILURE) || data.isUndefined()) - { - parse_count = PARSE_FAILURE; - } - else - { - parse_count += child_count; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading array." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '!': - c = get(istr); - data.clear(); - break; - - case '0': - c = get(istr); - data = false; - break; - - case 'F': - case 'f': - ignore(istr); - c = istr.peek(); - if(isalpha(c)) - { - auto cnt = deserialize_boolean( - istr, - data, - NOTATION_FALSE_SERIAL, - false); - if(PARSE_FAILURE == cnt) parse_count = cnt; - else account(cnt); - } - else - { - data = false; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - - case '1': - c = get(istr); - data = true; - break; - - case 'T': - case 't': - ignore(istr); - c = istr.peek(); - if(isalpha(c)) - { - auto cnt = deserialize_boolean(istr,data,NOTATION_TRUE_SERIAL,true); - if(PARSE_FAILURE == cnt) parse_count = cnt; - else account(cnt); - } - else - { - data = true; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - - case 'i': - { - c = get(istr); - S32 integer = 0; - istr >> integer; - data = integer; - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading integer." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'r': - { - c = get(istr); - F64 real = 0.0; - istr >> real; - data = real; - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading real." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'u': - { - c = get(istr); - LLUUID id; - istr >> id; - data = id; - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading uuid." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '\"': - case '\'': - case 's': - if(!parseString(istr, data)) - { - parse_count = PARSE_FAILURE; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading string." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - - case 'l': - { - c = get(istr); // pop the 'l' - c = get(istr); // pop the delimiter - std::string str; - auto cnt = deserialize_string_delim(istr, str, c); - if(PARSE_FAILURE == cnt) - { - parse_count = PARSE_FAILURE; - } - else - { - data = LLURI(str); - account(cnt); - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading link." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'd': - { - c = get(istr); // pop the 'd' - c = get(istr); // pop the delimiter - std::string str; - auto cnt = deserialize_string_delim(istr, str, c); - if(PARSE_FAILURE == cnt) - { - parse_count = PARSE_FAILURE; - } - else - { - data = LLDate(str); - account(cnt); - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading date." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'b': - if(!parseBinary(istr, data)) - { - parse_count = PARSE_FAILURE; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading data." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - - default: - parse_count = PARSE_FAILURE; - LL_INFOS() << "Unrecognized character while parsing: int(" << int(c) - << ")" << LL_ENDL; - break; - } - if(PARSE_FAILURE == parse_count) - { - data.clear(); - } - return parse_count; -} - -S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - // map: { string:object, string:object } - map = LLSD::emptyMap(); - S32 parse_count = 0; - char c = get(istr); - if(c == '{') - { - // eat commas, white - bool found_name = false; - std::string name; - c = get(istr); - while(c != '}' && istr.good()) - { - if(!found_name) - { - if((c == '\"') || (c == '\'') || (c == 's')) - { - putback(istr, c); - found_name = true; - auto count = deserialize_string(istr, name, mMaxBytesLeft); - if(PARSE_FAILURE == count) return PARSE_FAILURE; - account(count); - } - c = get(istr); - } - else - { - if(isspace(c) || (c == ':')) - { - c = get(istr); - continue; - } - putback(istr, c); - LLSD child; - S32 count = doParse(istr, child, max_depth); - if(count > 0) - { - // There must be a value for every key, thus - // child_count must be greater than 0. - parse_count += count; - map.insert(name, child); - } - else - { - return PARSE_FAILURE; - } - found_name = false; - c = get(istr); - } - } - if(c != '}') - { - map.clear(); - return PARSE_FAILURE; - } - } - return parse_count; -} - -S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - // array: [ object, object, object ] - array = LLSD::emptyArray(); - S32 parse_count = 0; - char c = get(istr); - if(c == '[') - { - // eat commas, white - c = get(istr); - while((c != ']') && istr.good()) - { - LLSD child; - if(isspace(c) || (c == ',')) - { - c = get(istr); - continue; - } - putback(istr, c); - S32 count = doParse(istr, child, max_depth); - if(PARSE_FAILURE == count) - { - return PARSE_FAILURE; - } - else - { - parse_count += count; - array.append(child); - } - c = get(istr); - } - if(c != ']') - { - return PARSE_FAILURE; - } - } - return parse_count; -} - -bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - std::string value; - auto count = deserialize_string(istr, value, mMaxBytesLeft); - if(PARSE_FAILURE == count) return false; - account(count); - data = value; - return true; -} - -bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD - // binary: b##"ff3120ab1" - // or: b(len)"..." - - // I want to manually control those values here to make sure the - // parser doesn't break when someone changes a constant somewhere - // else. - const U32 BINARY_BUFFER_SIZE = 256; - const U32 STREAM_GET_COUNT = 255; - - // need to read the base out. - char buf[BINARY_BUFFER_SIZE]; /* Flawfinder: ignore */ - get(istr, buf, STREAM_GET_COUNT, '"'); - char c = get(istr); - if(c != '"') return false; - if(0 == strncmp("b(", buf, 2)) - { - // We probably have a valid raw binary stream. determine - // the size, and read it. - auto len = strtol(buf + 2, NULL, 0); - if(mCheckLimits && (len > mMaxBytesLeft)) return false; - std::vector<U8> value; - if(len) - { - value.resize(len); - account(fullread(istr, (char *)&value[0], len)); - } - c = get(istr); // strip off the trailing double-quote - data = value; - } - else if(0 == strncmp("b64", buf, 3)) - { - // *FIX: A bit inefficient, but works for now. To make the - // format better, I would need to add a hint into the - // serialization format that indicated how long it was. - std::stringstream coded_stream; - get(istr, *(coded_stream.rdbuf()), '\"'); - c = get(istr); - std::string encoded(coded_stream.str()); - S32 len = apr_base64_decode_len(encoded.c_str()); - std::vector<U8> value; - if(len) - { - value.resize(len); - len = apr_base64_decode_binary(&value[0], encoded.c_str()); - value.resize(len); - } - data = value; - } - else if(0 == strncmp("b16", buf, 3)) - { - // yay, base 16. We pop the next character which is either a - // double quote or base 16 data. If it's a double quote, we're - // done parsing. If it's not, put the data back, and read the - // stream until the next double quote. - char* read; /*Flawfinder: ignore*/ - U8 byte; - U8 byte_buffer[BINARY_BUFFER_SIZE]; - U8* write; - std::vector<U8> value; - c = get(istr); - while(c != '"') - { - putback(istr, c); - read = buf; - write = byte_buffer; - get(istr, buf, STREAM_GET_COUNT, '"'); - c = get(istr); - while(*read != '\0') /*Flawfinder: ignore*/ - { - byte = hex_as_nybble(*read++); - byte = byte << 4; - byte |= hex_as_nybble(*read++); - *write++ = byte; - } - // copy the data out of the byte buffer - value.insert(value.end(), byte_buffer, write); - } - data = value; - } - else - { - return false; - } - return true; -} - - -/** - * LLSDBinaryParser - */ -LLSDBinaryParser::LLSDBinaryParser() -{ -} - -// virtual -LLSDBinaryParser::~LLSDBinaryParser() -{ -} - -// virtual -S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD -/** - * Undefined: '!'<br> - * Boolean: '1' for true '0' for false<br> - * Integer: 'i' + 4 bytes network byte order<br> - * Real: 'r' + 8 bytes IEEE double<br> - * UUID: 'u' + 16 byte unsigned integer<br> - * String: 's' + 4 byte integer size + string<br> - * strings also secretly support the notation format - * Date: 'd' + 8 byte IEEE double for seconds since epoch<br> - * URI: 'l' + 4 byte integer size + string uri<br> - * Binary: 'b' + 4 byte integer size + binary data<br> - * Array: '[' + 4 byte integer size + all values + ']'<br> - * Map: '{' + 4 byte integer size every(key + value) + '}'<br> - * map keys are serialized as s + 4 byte integer size + string or in the - * notation format. - */ - char c; - c = get(istr); - if(!istr.good()) - { - return 0; - } - if (max_depth == 0) - { - return PARSE_FAILURE; - } - S32 parse_count = 1; - switch(c) - { - case '{': - { - S32 child_count = parseMap(istr, data, max_depth - 1); - if((child_count == PARSE_FAILURE) || data.isUndefined()) - { - parse_count = PARSE_FAILURE; - } - else - { - parse_count += child_count; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary map." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '[': - { - S32 child_count = parseArray(istr, data, max_depth - 1); - if((child_count == PARSE_FAILURE) || data.isUndefined()) - { - parse_count = PARSE_FAILURE; - } - else - { - parse_count += child_count; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary array." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case '!': - data.clear(); - break; - - case '0': - data = false; - break; - - case '1': - data = true; - break; - - case 'i': - { - U32 value_nbo = 0; - read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - data = (S32)ntohl(value_nbo); - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary integer." << LL_ENDL; - } - break; - } - - case 'r': - { - F64 real_nbo = 0.0; - read(istr, (char*)&real_nbo, sizeof(F64)); /*Flawfinder: ignore*/ - data = ll_ntohd(real_nbo); - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary real." << LL_ENDL; - } - break; - } - - case 'u': - { - LLUUID id; - read(istr, (char*)(&id.mData), UUID_BYTES); /*Flawfinder: ignore*/ - data = id; - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary uuid." << LL_ENDL; - } - break; - } - - case '\'': - case '"': - { - std::string value; - auto cnt = deserialize_string_delim(istr, value, c); - if(PARSE_FAILURE == cnt) - { - parse_count = PARSE_FAILURE; - } - else - { - data = value; - account(cnt); - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary (notation-style) string." - << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 's': - { - std::string value; - if(parseString(istr, value)) - { - data = value; - } - else - { - parse_count = PARSE_FAILURE; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary string." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'l': - { - std::string value; - if(parseString(istr, value)) - { - data = LLURI(value); - } - else - { - parse_count = PARSE_FAILURE; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary link." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'd': - { - F64 real = 0.0; - read(istr, (char*)&real, sizeof(F64)); /*Flawfinder: ignore*/ - data = LLDate(real); - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary date." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - case 'b': - { - // We probably have a valid raw binary stream. determine - // the size, and read it. - U32 size_nbo = 0; - read(istr, (char*)&size_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - S32 size = (S32)ntohl(size_nbo); - if(mCheckLimits && (size > mMaxBytesLeft)) - { - parse_count = PARSE_FAILURE; - } - else - { - std::vector<U8> value; - if(size > 0) - { - value.resize(size); - account(fullread(istr, (char*)&value[0], size)); - } - data = value; - } - if(istr.fail()) - { - LL_INFOS() << "STREAM FAILURE reading binary." << LL_ENDL; - parse_count = PARSE_FAILURE; - } - break; - } - - default: - parse_count = PARSE_FAILURE; - LL_INFOS() << "Unrecognized character while parsing: int(" << int(c) - << ")" << LL_ENDL; - break; - } - if(PARSE_FAILURE == parse_count) - { - data.clear(); - } - return parse_count; -} - -S32 LLSDBinaryParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const -{ - map = LLSD::emptyMap(); - U32 value_nbo = 0; - read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - S32 size = (S32)ntohl(value_nbo); - S32 parse_count = 0; - S32 count = 0; - char c = get(istr); - while(c != '}' && (count < size) && istr.good()) - { - std::string name; - switch(c) - { - case 'k': - if(!parseString(istr, name)) - { - return PARSE_FAILURE; - } - break; - case '\'': - case '"': - { - auto cnt = deserialize_string_delim(istr, name, c); - if(PARSE_FAILURE == cnt) return PARSE_FAILURE; - account(cnt); - break; - } - } - LLSD child; - S32 child_count = doParse(istr, child, max_depth); - if(child_count > 0) - { - // There must be a value for every key, thus child_count - // must be greater than 0. - parse_count += child_count; - map.insert(name, child); - } - else - { - return PARSE_FAILURE; - } - ++count; - c = get(istr); - } - if((c != '}') || (count < size)) - { - // Make sure it is correctly terminated and we parsed as many - // as were said to be there. - return PARSE_FAILURE; - } - return parse_count; -} - -S32 LLSDBinaryParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const -{ - array = LLSD::emptyArray(); - U32 value_nbo = 0; - read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - S32 size = (S32)ntohl(value_nbo); - - // *FIX: This would be a good place to reserve some space in the - // array... - - S32 parse_count = 0; - S32 count = 0; - char c = istr.peek(); - while((c != ']') && (count < size) && istr.good()) - { - LLSD child; - S32 child_count = doParse(istr, child, max_depth); - if(PARSE_FAILURE == child_count) - { - return PARSE_FAILURE; - } - if(child_count) - { - parse_count += child_count; - array.append(child); - } - ++count; - c = istr.peek(); - } - c = get(istr); - if((c != ']') || (count < size)) - { - // Make sure it is correctly terminated and we parsed as many - // as were said to be there. - return PARSE_FAILURE; - } - return parse_count; -} - -bool LLSDBinaryParser::parseString( - std::istream& istr, - std::string& value) const -{ - // *FIX: This is memory inefficient. - U32 value_nbo = 0; - read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ - S32 size = (S32)ntohl(value_nbo); - if(mCheckLimits && (size > mMaxBytesLeft)) return false; - if(size < 0) return false; - std::vector<char> buf; - if(size) - { - buf.resize(size); - account(fullread(istr, &buf[0], size)); - value.assign(buf.begin(), buf.end()); - } - return true; -} - - -/** - * LLSDFormatter - */ -LLSDFormatter::LLSDFormatter(bool boolAlpha, const std::string& realFmt, EFormatterOptions options): - mOptions(options) -{ - boolalpha(boolAlpha); - realFormat(realFmt); -} - -// virtual -LLSDFormatter::~LLSDFormatter() -{ } - -void LLSDFormatter::boolalpha(bool alpha) -{ - mBoolAlpha = alpha; -} - -void LLSDFormatter::realFormat(const std::string& format) -{ - mRealFormat = format; -} - -S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr) const -{ - // pass options captured by constructor - return format(data, ostr, mOptions); -} - -S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const -{ - return format_impl(data, ostr, options, 0); -} - -void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const -{ - std::string buffer = llformat(mRealFormat.c_str(), real); - ostr << buffer; -} - -/** - * LLSDNotationFormatter - */ -LLSDNotationFormatter::LLSDNotationFormatter(bool boolAlpha, const std::string& realFormat, - EFormatterOptions options): - LLSDFormatter(boolAlpha, realFormat, options) -{ -} - -// virtual -LLSDNotationFormatter::~LLSDNotationFormatter() -{ } - -// static -std::string LLSDNotationFormatter::escapeString(const std::string& in) -{ - std::ostringstream ostr; - serialize_string(in, ostr); - return ostr.str(); -} - -S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, - EFormatterOptions options, U32 level) const -{ - S32 format_count = 1; - std::string pre; - std::string post; - - if (options & LLSDFormatter::OPTIONS_PRETTY) - { - for (U32 i = 0; i < level; i++) - { - pre += " "; - } - post = "\n"; - } - - switch(data.type()) - { - case LLSD::TypeMap: - { - if (0 != level) ostr << post << pre; - ostr << "{"; - std::string inner_pre; - if (options & LLSDFormatter::OPTIONS_PRETTY) - { - inner_pre = pre + " "; - } - - bool need_comma = false; - LLSD::map_const_iterator iter = data.beginMap(); - LLSD::map_const_iterator end = data.endMap(); - for(; iter != end; ++iter) - { - if(need_comma) ostr << ","; - need_comma = true; - ostr << post << inner_pre << '\''; - serialize_string((*iter).first, ostr); - ostr << "':"; - format_count += format_impl((*iter).second, ostr, options, level + 2); - } - ostr << post << pre << "}"; - break; - } - - case LLSD::TypeArray: - { - ostr << post << pre << "["; - bool need_comma = false; - LLSD::array_const_iterator iter = data.beginArray(); - LLSD::array_const_iterator end = data.endArray(); - for(; iter != end; ++iter) - { - if(need_comma) ostr << ","; - need_comma = true; - format_count += format_impl(*iter, ostr, options, level + 1); - } - ostr << "]"; - break; - } - - case LLSD::TypeUndefined: - ostr << "!"; - break; - - case LLSD::TypeBoolean: - if(mBoolAlpha || -#if( LL_WINDOWS || __GNUC__ > 2) - (ostr.flags() & std::ios::boolalpha) -#else - (ostr.flags() & 0x0100) -#endif - ) - { - ostr << (data.asBoolean() - ? NOTATION_TRUE_SERIAL : NOTATION_FALSE_SERIAL); - } - else - { - ostr << (data.asBoolean() ? 1 : 0); - } - break; - - case LLSD::TypeInteger: - ostr << "i" << data.asInteger(); - break; - - case LLSD::TypeReal: - ostr << "r"; - if(mRealFormat.empty()) - { - ostr << data.asReal(); - } - else - { - formatReal(data.asReal(), ostr); - } - break; - - case LLSD::TypeUUID: - ostr << "u" << data.asUUID(); - break; - - case LLSD::TypeString: - ostr << '\''; - serialize_string(data.asStringRef(), ostr); - ostr << '\''; - break; - - case LLSD::TypeDate: - ostr << "d\"" << data.asDate() << "\""; - break; - - case LLSD::TypeURI: - ostr << "l\""; - serialize_string(data.asString(), ostr); - ostr << "\""; - break; - - case LLSD::TypeBinary: - { - // *FIX: memory inefficient. - const std::vector<U8>& buffer = data.asBinary(); - if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY) - { - ostr << "b16\""; - if (! buffer.empty()) - { - std::ios_base::fmtflags old_flags = ostr.flags(); - ostr.setf( std::ios::hex, std::ios::basefield ); - // It shouldn't strictly matter whether the emitted hex digits - // are uppercase; LLSDNotationParser handles either; but as of - // 2020-05-13, Python's llbase.llsd requires uppercase hex. - ostr << std::uppercase; - auto oldfill(ostr.fill('0')); - auto oldwidth(ostr.width()); - for (size_t i = 0; i < buffer.size(); i++) - { - // have to restate setw() before every conversion - ostr << std::setw(2) << (int) buffer[i]; - } - ostr.width(oldwidth); - ostr.fill(oldfill); - ostr.flags(old_flags); - } - } - else // ! OPTIONS_PRETTY_BINARY - { - ostr << "b(" << buffer.size() << ")\""; - if (! buffer.empty()) - { - ostr.write((const char*)&buffer[0], buffer.size()); - } - } - ostr << "\""; - break; - } - - default: - // *NOTE: This should never happen. - ostr << "!"; - break; - } - return format_count; -} - -/** - * LLSDBinaryFormatter - */ -LLSDBinaryFormatter::LLSDBinaryFormatter(bool boolAlpha, const std::string& realFormat, - EFormatterOptions options): - LLSDFormatter(boolAlpha, realFormat, options) -{ -} - -// virtual -LLSDBinaryFormatter::~LLSDBinaryFormatter() -{ } - -// virtual -S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr, - EFormatterOptions options, U32 level) const -{ - S32 format_count = 1; - switch(data.type()) - { - case LLSD::TypeMap: - { - ostr.put('{'); - U32 size_nbo = htonl(data.size()); - ostr.write((const char*)(&size_nbo), sizeof(U32)); - LLSD::map_const_iterator iter = data.beginMap(); - LLSD::map_const_iterator end = data.endMap(); - for(; iter != end; ++iter) - { - ostr.put('k'); - formatString((*iter).first, ostr); - format_count += format_impl((*iter).second, ostr, options, level+1); - } - ostr.put('}'); - break; - } - - case LLSD::TypeArray: - { - ostr.put('['); - U32 size_nbo = htonl(data.size()); - ostr.write((const char*)(&size_nbo), sizeof(U32)); - LLSD::array_const_iterator iter = data.beginArray(); - LLSD::array_const_iterator end = data.endArray(); - for(; iter != end; ++iter) - { - format_count += format_impl(*iter, ostr, options, level+1); - } - ostr.put(']'); - break; - } - - case LLSD::TypeUndefined: - ostr.put('!'); - break; - - case LLSD::TypeBoolean: - if(data.asBoolean()) ostr.put(BINARY_TRUE_SERIAL); - else ostr.put(BINARY_FALSE_SERIAL); - break; - - case LLSD::TypeInteger: - { - ostr.put('i'); - U32 value_nbo = htonl(data.asInteger()); - ostr.write((const char*)(&value_nbo), sizeof(U32)); - break; - } - - case LLSD::TypeReal: - { - ostr.put('r'); - F64 value_nbo = ll_htond(data.asReal()); - ostr.write((const char*)(&value_nbo), sizeof(F64)); - break; - } - - case LLSD::TypeUUID: - { - ostr.put('u'); - LLUUID temp = data.asUUID(); - ostr.write((const char*)(&(temp.mData)), UUID_BYTES); - break; - } - - case LLSD::TypeString: - ostr.put('s'); - formatString(data.asStringRef(), ostr); - break; - - case LLSD::TypeDate: - { - ostr.put('d'); - F64 value = data.asReal(); - ostr.write((const char*)(&value), sizeof(F64)); - break; - } - - case LLSD::TypeURI: - ostr.put('l'); - formatString(data.asString(), ostr); - break; - - case LLSD::TypeBinary: - { - ostr.put('b'); - const std::vector<U8>& buffer = data.asBinary(); - U32 size_nbo = htonl(buffer.size()); - ostr.write((const char*)(&size_nbo), sizeof(U32)); - if(buffer.size()) ostr.write((const char*)&buffer[0], buffer.size()); - break; - } - - default: - // *NOTE: This should never happen. - ostr.put('!'); - break; - } - return format_count; -} - -void LLSDBinaryFormatter::formatString( - const std::string& string, - std::ostream& ostr) const -{ - U32 size_nbo = htonl(string.size()); - ostr.write((const char*)(&size_nbo), sizeof(U32)); - ostr.write(string.c_str(), string.size()); -} - -/** - * local functions - */ -llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes) -{ - int c = istr.get(); - if(istr.fail()) - { - // No data in stream, bail out but mention the character we - // grabbed. - return LLSDParser::PARSE_FAILURE; - } - - llssize rv = LLSDParser::PARSE_FAILURE; - switch(c) - { - case '\'': - case '"': - rv = deserialize_string_delim(istr, value, c); - break; - case 's': - // technically, less than max_bytes, but this is just meant to - // catch egregious protocol errors. parse errors will be - // caught in the case of incorrect counts. - rv = deserialize_string_raw(istr, value, max_bytes); - break; - default: - break; - } - if(LLSDParser::PARSE_FAILURE == rv) return rv; - return rv + 1; // account for the character grabbed at the top. -} - -llssize deserialize_string_delim( - std::istream& istr, - std::string& value, - char delim) -{ - std::ostringstream write_buffer; - bool found_escape = false; - bool found_hex = false; - bool found_digit = false; - U8 byte = 0; - llssize count = 0; - - while (true) - { - int next_byte = istr.get(); - ++count; - - if(istr.fail()) - { - // If our stream is empty, break out - value = write_buffer.str(); - return LLSDParser::PARSE_FAILURE; - } - - char next_char = (char)next_byte; // Now that we know it's not EOF - - if(found_escape) - { - // next character(s) is a special sequence. - if(found_hex) - { - if(found_digit) - { - found_digit = false; - found_hex = false; - found_escape = false; - byte = byte << 4; - byte |= hex_as_nybble(next_char); - write_buffer << byte; - byte = 0; - } - else - { - // next character is the first nybble of - // - found_digit = true; - byte = hex_as_nybble(next_char); - } - } - else if(next_char == 'x') - { - found_hex = true; - } - else - { - switch(next_char) - { - case 'a': - write_buffer << '\a'; - break; - case 'b': - write_buffer << '\b'; - break; - case 'f': - write_buffer << '\f'; - break; - case 'n': - write_buffer << '\n'; - break; - case 'r': - write_buffer << '\r'; - break; - case 't': - write_buffer << '\t'; - break; - case 'v': - write_buffer << '\v'; - break; - default: - write_buffer << next_char; - break; - } - found_escape = false; - } - } - else if(next_char == '\\') - { - found_escape = true; - } - else if(next_char == delim) - { - break; - } - else - { - write_buffer << next_char; - } - } - - value = write_buffer.str(); - return count; -} - -llssize deserialize_string_raw( - std::istream& istr, - std::string& value, - llssize max_bytes) -{ - llssize count = 0; - const S32 BUF_LEN = 20; - char buf[BUF_LEN]; /* Flawfinder: ignore */ - istr.get(buf, BUF_LEN - 1, ')'); - count += istr.gcount(); - int c = istr.get(); - c = istr.get(); - count += 2; - if(((c == '"') || (c == '\'')) && (buf[0] == '(')) - { - // We probably have a valid raw string. determine - // the size, and read it. - // *FIX: This is memory inefficient. - auto len = strtol(buf + 1, NULL, 0); - if((max_bytes>0)&&(len>max_bytes)) return LLSDParser::PARSE_FAILURE; - std::vector<char> buf; - if(len) - { - buf.resize(len); - count += fullread(istr, (char *)&buf[0], len); - value.assign(buf.begin(), buf.end()); - } - c = istr.get(); - ++count; - if(!((c == '"') || (c == '\''))) - { - return LLSDParser::PARSE_FAILURE; - } - } - else - { - return LLSDParser::PARSE_FAILURE; - } - return count; -} - -static const char* NOTATION_STRING_CHARACTERS[256] = -{ - "\\x00", // 0 - "\\x01", // 1 - "\\x02", // 2 - "\\x03", // 3 - "\\x04", // 4 - "\\x05", // 5 - "\\x06", // 6 - "\\a", // 7 - "\\b", // 8 - "\\t", // 9 - "\\n", // 10 - "\\v", // 11 - "\\f", // 12 - "\\r", // 13 - "\\x0e", // 14 - "\\x0f", // 15 - "\\x10", // 16 - "\\x11", // 17 - "\\x12", // 18 - "\\x13", // 19 - "\\x14", // 20 - "\\x15", // 21 - "\\x16", // 22 - "\\x17", // 23 - "\\x18", // 24 - "\\x19", // 25 - "\\x1a", // 26 - "\\x1b", // 27 - "\\x1c", // 28 - "\\x1d", // 29 - "\\x1e", // 30 - "\\x1f", // 31 - " ", // 32 - "!", // 33 - "\"", // 34 - "#", // 35 - "$", // 36 - "%", // 37 - "&", // 38 - "\\'", // 39 - "(", // 40 - ")", // 41 - "*", // 42 - "+", // 43 - ",", // 44 - "-", // 45 - ".", // 46 - "/", // 47 - "0", // 48 - "1", // 49 - "2", // 50 - "3", // 51 - "4", // 52 - "5", // 53 - "6", // 54 - "7", // 55 - "8", // 56 - "9", // 57 - ":", // 58 - ";", // 59 - "<", // 60 - "=", // 61 - ">", // 62 - "?", // 63 - "@", // 64 - "A", // 65 - "B", // 66 - "C", // 67 - "D", // 68 - "E", // 69 - "F", // 70 - "G", // 71 - "H", // 72 - "I", // 73 - "J", // 74 - "K", // 75 - "L", // 76 - "M", // 77 - "N", // 78 - "O", // 79 - "P", // 80 - "Q", // 81 - "R", // 82 - "S", // 83 - "T", // 84 - "U", // 85 - "V", // 86 - "W", // 87 - "X", // 88 - "Y", // 89 - "Z", // 90 - "[", // 91 - "\\\\", // 92 - "]", // 93 - "^", // 94 - "_", // 95 - "`", // 96 - "a", // 97 - "b", // 98 - "c", // 99 - "d", // 100 - "e", // 101 - "f", // 102 - "g", // 103 - "h", // 104 - "i", // 105 - "j", // 106 - "k", // 107 - "l", // 108 - "m", // 109 - "n", // 110 - "o", // 111 - "p", // 112 - "q", // 113 - "r", // 114 - "s", // 115 - "t", // 116 - "u", // 117 - "v", // 118 - "w", // 119 - "x", // 120 - "y", // 121 - "z", // 122 - "{", // 123 - "|", // 124 - "}", // 125 - "~", // 126 - "\\x7f", // 127 - "\\x80", // 128 - "\\x81", // 129 - "\\x82", // 130 - "\\x83", // 131 - "\\x84", // 132 - "\\x85", // 133 - "\\x86", // 134 - "\\x87", // 135 - "\\x88", // 136 - "\\x89", // 137 - "\\x8a", // 138 - "\\x8b", // 139 - "\\x8c", // 140 - "\\x8d", // 141 - "\\x8e", // 142 - "\\x8f", // 143 - "\\x90", // 144 - "\\x91", // 145 - "\\x92", // 146 - "\\x93", // 147 - "\\x94", // 148 - "\\x95", // 149 - "\\x96", // 150 - "\\x97", // 151 - "\\x98", // 152 - "\\x99", // 153 - "\\x9a", // 154 - "\\x9b", // 155 - "\\x9c", // 156 - "\\x9d", // 157 - "\\x9e", // 158 - "\\x9f", // 159 - "\\xa0", // 160 - "\\xa1", // 161 - "\\xa2", // 162 - "\\xa3", // 163 - "\\xa4", // 164 - "\\xa5", // 165 - "\\xa6", // 166 - "\\xa7", // 167 - "\\xa8", // 168 - "\\xa9", // 169 - "\\xaa", // 170 - "\\xab", // 171 - "\\xac", // 172 - "\\xad", // 173 - "\\xae", // 174 - "\\xaf", // 175 - "\\xb0", // 176 - "\\xb1", // 177 - "\\xb2", // 178 - "\\xb3", // 179 - "\\xb4", // 180 - "\\xb5", // 181 - "\\xb6", // 182 - "\\xb7", // 183 - "\\xb8", // 184 - "\\xb9", // 185 - "\\xba", // 186 - "\\xbb", // 187 - "\\xbc", // 188 - "\\xbd", // 189 - "\\xbe", // 190 - "\\xbf", // 191 - "\\xc0", // 192 - "\\xc1", // 193 - "\\xc2", // 194 - "\\xc3", // 195 - "\\xc4", // 196 - "\\xc5", // 197 - "\\xc6", // 198 - "\\xc7", // 199 - "\\xc8", // 200 - "\\xc9", // 201 - "\\xca", // 202 - "\\xcb", // 203 - "\\xcc", // 204 - "\\xcd", // 205 - "\\xce", // 206 - "\\xcf", // 207 - "\\xd0", // 208 - "\\xd1", // 209 - "\\xd2", // 210 - "\\xd3", // 211 - "\\xd4", // 212 - "\\xd5", // 213 - "\\xd6", // 214 - "\\xd7", // 215 - "\\xd8", // 216 - "\\xd9", // 217 - "\\xda", // 218 - "\\xdb", // 219 - "\\xdc", // 220 - "\\xdd", // 221 - "\\xde", // 222 - "\\xdf", // 223 - "\\xe0", // 224 - "\\xe1", // 225 - "\\xe2", // 226 - "\\xe3", // 227 - "\\xe4", // 228 - "\\xe5", // 229 - "\\xe6", // 230 - "\\xe7", // 231 - "\\xe8", // 232 - "\\xe9", // 233 - "\\xea", // 234 - "\\xeb", // 235 - "\\xec", // 236 - "\\xed", // 237 - "\\xee", // 238 - "\\xef", // 239 - "\\xf0", // 240 - "\\xf1", // 241 - "\\xf2", // 242 - "\\xf3", // 243 - "\\xf4", // 244 - "\\xf5", // 245 - "\\xf6", // 246 - "\\xf7", // 247 - "\\xf8", // 248 - "\\xf9", // 249 - "\\xfa", // 250 - "\\xfb", // 251 - "\\xfc", // 252 - "\\xfd", // 253 - "\\xfe", // 254 - "\\xff" // 255 -}; - -void serialize_string(const std::string& value, std::ostream& str) -{ - std::string::const_iterator it = value.begin(); - std::string::const_iterator end = value.end(); - U8 c; - for(; it != end; ++it) - { - c = (U8)(*it); - str << NOTATION_STRING_CHARACTERS[c]; - } -} - -llssize deserialize_boolean( - std::istream& istr, - LLSD& data, - const std::string& compare, - bool value) -{ - // - // this method is a little goofy, because it gets the stream at - // the point where the t or f has already been - // consumed. Basically, parse for a patch to the string passed in - // starting at index 1. If it's a match: - // * assign data to value - // * return the number of bytes read - // otherwise: - // * set data to LLSD::null - // * return LLSDParser::PARSE_FAILURE (-1) - // - llssize bytes_read = 0; - std::string::size_type ii = 0; - char c = istr.peek(); - while((++ii < compare.size()) - && (tolower(c) == (int)compare[ii]) - && istr.good()) - { - istr.ignore(); - ++bytes_read; - c = istr.peek(); - } - if(compare.size() != ii) - { - data.clear(); - return LLSDParser::PARSE_FAILURE; - } - data = value; - return bytes_read; -} - -std::ostream& operator<<(std::ostream& s, const LLSD& llsd) -{ - s << LLSDNotationStreamer(llsd); - return s; -} - - -//dirty little zippers -- yell at davep if these are horrid - -//return a string containing gzipped bytes of binary serialized LLSD -// VERY inefficient -- creates several copies of LLSD block in memory -std::string zip_llsd(LLSD& data) -{ - std::stringstream llsd_strm; - - LLSDSerialize::toBinary(data, llsd_strm); - - const U32 CHUNK = 65536; - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - S32 ret = deflateInit(&strm, Z_BEST_COMPRESSION); - if (ret != Z_OK) - { - LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; - return std::string(); - } - - std::string source = llsd_strm.str(); - - U8 out[CHUNK]; - - strm.avail_in = narrow<size_t>(source.size()); - strm.next_in = (U8*) source.data(); - U8* output = NULL; - - U32 cur_size = 0; - - U32 have = 0; - - do - { - strm.avail_out = CHUNK; - strm.next_out = out; - - ret = deflate(&strm, Z_FINISH); - if (ret == Z_OK || ret == Z_STREAM_END) - { //copy result into output - if (strm.avail_out >= CHUNK) - { - deflateEnd(&strm); - if(output) - free(output); - LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; - return std::string(); - } - - have = CHUNK-strm.avail_out; - U8* new_output = (U8*) realloc(output, cur_size+have); - if (new_output == NULL) - { - LL_WARNS() << "Failed to compress LLSD block: can't reallocate memory, current size: " << cur_size << " bytes; requested " << cur_size + have << " bytes." << LL_ENDL; - deflateEnd(&strm); - if (output) - { - free(output); - } - return std::string(); - } - output = new_output; - memcpy(output+cur_size, out, have); - cur_size += have; - } - else - { - deflateEnd(&strm); - if(output) - free(output); - LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; - return std::string(); - } - } - while (ret == Z_OK); - - std::string::size_type size = cur_size; - - std::string result((char*) output, size); - deflateEnd(&strm); - if(output) - free(output); - - return result; -} - -//decompress a block of LLSD from provided istream -// not very efficient -- creats a copy of decompressed LLSD block in memory -// and deserializes from that copy using LLSDSerialize -LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, std::istream& is, S32 size) -{ - std::unique_ptr<U8[]> in = std::unique_ptr<U8[]>(new(std::nothrow) U8[size]); - if (!in) - { - return ZR_MEM_ERROR; - } - is.read((char*) in.get(), size); - - return unzip_llsd(data, in.get(), size); -} - -LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, const U8* in, S32 size) -{ - U8* result = NULL; - llssize cur_size = 0; - z_stream strm; - - constexpr U32 CHUNK = 1024 * 512; - - static thread_local std::unique_ptr<U8[]> out; - if (!out) - { - out = std::unique_ptr<U8[]>(new(std::nothrow) U8[CHUNK]); - } - - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = size; - strm.next_in = const_cast<U8*>(in); - - S32 ret = inflateInit(&strm); - - do - { - strm.avail_out = CHUNK; - strm.next_out = out.get(); - ret = inflate(&strm, Z_NO_FLUSH); - switch (ret) - { - case Z_NEED_DICT: - case Z_DATA_ERROR: - { - inflateEnd(&strm); - free(result); - return ZR_DATA_ERROR; - } - case Z_STREAM_ERROR: - case Z_BUF_ERROR: - { - inflateEnd(&strm); - free(result); - return ZR_BUFFER_ERROR; - } - - case Z_MEM_ERROR: - { - inflateEnd(&strm); - free(result); - return ZR_MEM_ERROR; - } - } - - U32 have = CHUNK-strm.avail_out; - - U8* new_result = (U8*)realloc(result, cur_size + have); - if (new_result == NULL) - { - inflateEnd(&strm); - if (result) - { - free(result); - } - return ZR_MEM_ERROR; - } - result = new_result; - memcpy(result+cur_size, out.get(), have); - cur_size += have; - - } while (ret == Z_OK && ret != Z_STREAM_END); - - inflateEnd(&strm); - - if (ret != Z_STREAM_END) - { - free(result); - return ZR_DATA_ERROR; - } - - //result now points to the decompressed LLSD block - { - char* result_ptr = strip_deprecated_header((char*)result, cur_size); - - boost::iostreams::stream<boost::iostreams::array_source> istrm(result_ptr, cur_size); - - if (!LLSDSerialize::fromBinary(data, istrm, cur_size, UNZIP_LLSD_MAX_DEPTH)) - { - free(result); - return ZR_PARSE_ERROR; - } - } - - free(result); - return ZR_OK; -} -//This unzip function will only work with a gzip header and trailer - while the contents -//of the actual compressed data is the same for either format (gzip vs zlib ), the headers -//and trailers are different for the formats. -U8* unzip_llsdNavMesh( bool& valid, size_t& outsize, std::istream& is, S32 size ) -{ - if (size == 0) - { - LL_WARNS() << "No data to unzip." << LL_ENDL; - return NULL; - } - - U8* result = NULL; - U32 cur_size = 0; - z_stream strm; - - const U32 CHUNK = 0x4000; - - U8 *in = new(std::nothrow) U8[size]; - if (in == NULL) - { - LL_WARNS() << "Memory allocation failure." << LL_ENDL; - return NULL; - } - is.read((char*) in, size); - - U8 out[CHUNK]; - - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = size; - strm.next_in = in; - - - S32 ret = inflateInit2(&strm, windowBits | ENABLE_ZLIB_GZIP ); - do - { - strm.avail_out = CHUNK; - strm.next_out = out; - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR) - { - inflateEnd(&strm); - free(result); - delete [] in; - valid = false; - } - - switch (ret) - { - case Z_NEED_DICT: - ret = Z_DATA_ERROR; - case Z_DATA_ERROR: - case Z_MEM_ERROR: - inflateEnd(&strm); - free(result); - delete [] in; - valid = false; - break; - } - - U32 have = CHUNK-strm.avail_out; - - U8* new_result = (U8*) realloc(result, cur_size + have); - if (new_result == NULL) - { - LL_WARNS() << "Failed to unzip LLSD NavMesh block: can't reallocate memory, current size: " << cur_size - << " bytes; requested " << cur_size + have - << " bytes; total syze: ." << size << " bytes." - << LL_ENDL; - inflateEnd(&strm); - if (result) - { - free(result); - } - delete[] in; - valid = false; - return NULL; - } - result = new_result; - memcpy(result+cur_size, out, have); - cur_size += have; - - } while (ret == Z_OK); - - inflateEnd(&strm); - delete [] in; - - if (ret != Z_STREAM_END) - { - free(result); - valid = false; - return NULL; - } - - //result now points to the decompressed LLSD block - { - outsize= cur_size; - valid = true; - } - - return result; -} - -char* strip_deprecated_header(char* in, llssize& cur_size, llssize* header_size) -{ - const char* deprecated_header = "<? LLSD/Binary ?>"; - constexpr size_t deprecated_header_size = 17; - - if (cur_size > deprecated_header_size - && memcmp(in, deprecated_header, deprecated_header_size) == 0) - { - in = in + deprecated_header_size; - cur_size = cur_size - deprecated_header_size; - if (header_size) - { - *header_size = deprecated_header_size + 1; - } - } - - return in; -} - +/**
+ * @file llsdserialize.cpp
+ * @author Phoenix
+ * @date 2006-03-05
+ * @brief Implementation of LLSD parsers and formatters
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+#include "llsdserialize.h"
+#include "llpointer.h"
+#include "llstreamtools.h" // for fullread
+
+#include <iostream>
+#include "apr_base64.h"
+
+#include <boost/iostreams/device/array.hpp>
+#include <boost/iostreams/stream.hpp>
+
+#ifdef LL_USESYSTEMLIBS
+# include <zlib.h>
+#else
+# include "zlib-ng/zlib.h" // for davep's dirty little zip functions
+#endif
+
+#if !LL_WINDOWS
+#include <netinet/in.h> // htonl & ntohl
+#endif
+
+#include "lldate.h"
+#include "llmemorystream.h"
+#include "llsd.h"
+#include "llstring.h"
+#include "lluri.h"
+
+// File constants
+static const size_t MAX_HDR_LEN = 20;
+static const S32 UNZIP_LLSD_MAX_DEPTH = 96;
+static const char LEGACY_NON_HEADER[] = "<llsd>";
+const std::string LLSD_BINARY_HEADER("LLSD/Binary");
+const std::string LLSD_XML_HEADER("LLSD/XML");
+const std::string LLSD_NOTATION_HEADER("llsd/notation");
+
+//used to deflate a gzipped asset (currently used for navmeshes)
+#define windowBits 15
+#define ENABLE_ZLIB_GZIP 32
+
+// If we published this in llsdserialize.h, we could use it in the
+// implementation of LLSDOStreamer's operator<<().
+template <class Formatter>
+void format_using(const LLSD& data, std::ostream& ostr,
+ LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY)
+{
+ LLPointer<Formatter> f{ new Formatter };
+ f->format(data, ostr, options);
+}
+
+template <class Parser>
+S32 parse_using(std::istream& istr, LLSD& data, size_t max_bytes, S32 max_depth=-1)
+{
+ LLPointer<Parser> p{ new Parser };
+ return p->parse(istr, data, max_bytes, max_depth);
+}
+
+/**
+ * LLSDSerialize
+ */
+
+// static
+void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type,
+ LLSDFormatter::EFormatterOptions options)
+{
+ LLPointer<LLSDFormatter> f = NULL;
+
+ switch (type)
+ {
+ case LLSD_BINARY:
+ str << "<? " << LLSD_BINARY_HEADER << " ?>\n";
+ f = new LLSDBinaryFormatter;
+ break;
+
+ case LLSD_XML:
+ str << "<? " << LLSD_XML_HEADER << " ?>\n";
+ f = new LLSDXMLFormatter;
+ break;
+
+ case LLSD_NOTATION:
+ str << "<? " << LLSD_NOTATION_HEADER << " ?>\n";
+ f = new LLSDNotationFormatter;
+ break;
+
+ default:
+ LL_WARNS() << "serialize request for unknown ELLSD_Serialize" << LL_ENDL;
+ }
+
+ if (f.notNull())
+ {
+ f->format(sd, str, options);
+ }
+}
+
+// static
+bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str, llssize max_bytes)
+{
+ char hdr_buf[MAX_HDR_LEN + 1] = ""; /* Flawfinder: ignore */
+ bool fail_if_not_legacy = false;
+
+ /*
+ * Get the first line before anything. Don't read more than max_bytes:
+ * this get() overload reads no more than (count-1) bytes into the
+ * specified buffer. In the usual case when max_bytes exceeds
+ * sizeof(hdr_buf), get() will read no more than sizeof(hdr_buf)-2.
+ */
+ llssize max_hdr_read = MAX_HDR_LEN;
+ if (max_bytes != LLSDSerialize::SIZE_UNLIMITED)
+ {
+ max_hdr_read = llmin(max_bytes + 1, max_hdr_read);
+ }
+ str.get(hdr_buf, max_hdr_read, '\n');
+ auto inbuf = str.gcount();
+
+ // https://en.cppreference.com/w/cpp/io/basic_istream/get
+ // When the get() above sees the specified delimiter '\n', it stops there
+ // without pulling it from the stream. If it turns out that the stream
+ // does NOT contain a header, and the content includes meaningful '\n',
+ // it's important to pull that into hdr_buf too.
+ if (inbuf < max_bytes && str.get(hdr_buf[inbuf]))
+ {
+ // got the delimiting '\n'
+ ++inbuf;
+ // None of the following requires that hdr_buf contain a final '\0'
+ // byte. We could store one if needed, since even the incremented
+ // inbuf won't exceed sizeof(hdr_buf)-1, but there's no need.
+ }
+ std::string header{ hdr_buf, static_cast<std::string::size_type>(inbuf) };
+ if (str.fail())
+ {
+ str.clear();
+ fail_if_not_legacy = true;
+ }
+
+ if (!strncasecmp(LEGACY_NON_HEADER, hdr_buf, strlen(LEGACY_NON_HEADER))) /* Flawfinder: ignore */
+ { // Create a LLSD XML parser, and parse the first chunk read above.
+ LLSDXMLParser x;
+ x.parsePart(hdr_buf, inbuf); // Parse the first part that was already read
+ auto parsed = x.parse(str, sd, max_bytes - inbuf); // Parse the rest of it
+ // Formally we should probably check (parsed != PARSE_FAILURE &&
+ // parsed > 0), but since PARSE_FAILURE is -1, this suffices.
+ return (parsed > 0);
+ }
+
+ if (fail_if_not_legacy)
+ {
+ LL_WARNS() << "deserialize LLSD parse failure" << LL_ENDL;
+ return false;
+ }
+
+ /*
+ * Remove the newline chars
+ */
+ std::string::size_type lastchar = header.find_last_not_of("\r\n");
+ if (lastchar != std::string::npos)
+ {
+ // It's important that find_last_not_of() returns size_type, which is
+ // why lastchar explicitly declares the type above. erase(size_type)
+ // erases from that offset to the end of the string, whereas
+ // erase(iterator) erases only a single character.
+ header.erase(lastchar+1);
+ }
+
+ // trim off the <? ... ?> header syntax
+ auto start = header.find_first_not_of("<? ");
+ if (start != std::string::npos)
+ {
+ auto end = header.find_first_of(" ?", start);
+ if (end != std::string::npos)
+ {
+ header = header.substr(start, end - start);
+ ws(str);
+ }
+ }
+ /*
+ * Create the parser as appropriate
+ */
+ if (0 == LLStringUtil::compareInsensitive(header, LLSD_BINARY_HEADER))
+ {
+ return (parse_using<LLSDBinaryParser>(str, sd, max_bytes-inbuf) > 0);
+ }
+ else if (0 == LLStringUtil::compareInsensitive(header, LLSD_XML_HEADER))
+ {
+ return (parse_using<LLSDXMLParser>(str, sd, max_bytes-inbuf) > 0);
+ }
+ else if (0 == LLStringUtil::compareInsensitive(header, LLSD_NOTATION_HEADER))
+ {
+ return (parse_using<LLSDNotationParser>(str, sd, max_bytes-inbuf) > 0);
+ }
+ else // no header we recognize
+ {
+ LLPointer<LLSDParser> p;
+ if (inbuf && hdr_buf[0] == '<')
+ {
+ // looks like XML
+ LL_DEBUGS() << "deserialize request with no header, assuming XML" << LL_ENDL;
+ p = new LLSDXMLParser;
+ }
+ else
+ {
+ // assume notation
+ LL_DEBUGS() << "deserialize request with no header, assuming notation" << LL_ENDL;
+ p = new LLSDNotationParser;
+ }
+ // Since we've already read 'inbuf' bytes into 'hdr_buf', prepend that
+ // data to whatever remains in 'str'.
+ LLMemoryStreamBuf already(reinterpret_cast<const U8*>(hdr_buf), inbuf);
+ cat_streambuf prebuff(&already, str.rdbuf());
+ std::istream prepend(&prebuff);
+#if 1
+ return (p->parse(prepend, sd, max_bytes) > 0);
+#else
+ // debugging the reconstituted 'prepend' stream
+ // allocate a buffer that we hope is big enough for the whole thing
+ std::vector<char> wholemsg((max_bytes == size_t(SIZE_UNLIMITED))? 1024 : max_bytes);
+ prepend.read(wholemsg.data(), std::min(max_bytes, wholemsg.size()));
+ LLMemoryStream replay(reinterpret_cast<const U8*>(wholemsg.data()), prepend.gcount());
+ auto success{ p->parse(replay, sd, prepend.gcount()) > 0 };
+ {
+ LL_DEBUGS() << (success? "parsed: $$" : "failed: '")
+ << std::string(wholemsg.data(), llmin(prepend.gcount(), 100)) << "$$"
+ << LL_ENDL;
+ }
+ return success;
+#endif
+ }
+}
+
+/**
+ * Endian handlers
+ */
+#if LL_BIG_ENDIAN
+U64 ll_htonll(U64 hostlonglong) { return hostlonglong; }
+U64 ll_ntohll(U64 netlonglong) { return netlonglong; }
+F64 ll_htond(F64 hostlonglong) { return hostlonglong; }
+F64 ll_ntohd(F64 netlonglong) { return netlonglong; }
+#else
+// I read some comments one a indicating that doing an integer add
+// here would be faster than a bitwise or. For now, the or has
+// programmer clarity, since the intended outcome matches the
+// operation.
+U64 ll_htonll(U64 hostlonglong)
+{
+ return ((U64)(htonl((U32)((hostlonglong >> 32) & 0xFFFFFFFF))) |
+ ((U64)(htonl((U32)(hostlonglong & 0xFFFFFFFF))) << 32));
+}
+U64 ll_ntohll(U64 netlonglong)
+{
+ return ((U64)(ntohl((U32)((netlonglong >> 32) & 0xFFFFFFFF))) |
+ ((U64)(ntohl((U32)(netlonglong & 0xFFFFFFFF))) << 32));
+}
+union LLEndianSwapper
+{
+ F64 d;
+ U64 i;
+};
+F64 ll_htond(F64 hostdouble)
+{
+ LLEndianSwapper tmp;
+ tmp.d = hostdouble;
+ tmp.i = ll_htonll(tmp.i);
+ return tmp.d;
+}
+F64 ll_ntohd(F64 netdouble)
+{
+ LLEndianSwapper tmp;
+ tmp.d = netdouble;
+ tmp.i = ll_ntohll(tmp.i);
+ return tmp.d;
+}
+#endif
+
+/**
+ * Local functions.
+ */
+/**
+ * @brief Figure out what kind of string it is (raw or delimited) and handoff.
+ *
+ * @param istr The stream to read from.
+ * @param value [out] The string which was found.
+ * @param max_bytes The maximum possible length of the string. Passing in
+ * a negative value will skip this check.
+ * @return Returns number of bytes read off of the stream. Returns
+ * PARSE_FAILURE (-1) on failure.
+ */
+llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes);
+
+/**
+ * @brief Parse a delimited string.
+ *
+ * @param istr The stream to read from, with the delimiter already popped.
+ * @param value [out] The string which was found.
+ * @param d The delimiter to use.
+ * @return Returns number of bytes read off of the stream. Returns
+ * PARSE_FAILURE (-1) on failure.
+ */
+llssize deserialize_string_delim(std::istream& istr, std::string& value, char d);
+
+/**
+ * @brief Read a raw string off the stream.
+ *
+ * @param istr The stream to read from, with the (len) parameter
+ * leading the stream.
+ * @param value [out] The string which was found.
+ * @param d The delimiter to use.
+ * @param max_bytes The maximum possible length of the string. Passing in
+ * a negative value will skip this check.
+ * @return Returns number of bytes read off of the stream. Returns
+ * PARSE_FAILURE (-1) on failure.
+ */
+llssize deserialize_string_raw(
+ std::istream& istr,
+ std::string& value,
+ llssize max_bytes);
+
+/**
+ * @brief helper method for dealing with the different notation boolean format.
+ *
+ * @param istr The stream to read from with the leading character stripped.
+ * @param data [out] the result of the parse.
+ * @param compare The string to compare the boolean against
+ * @param vale The value to assign to data if the parse succeeds.
+ * @return Returns number of bytes read off of the stream. Returns
+ * PARSE_FAILURE (-1) on failure.
+ */
+llssize deserialize_boolean(
+ std::istream& istr,
+ LLSD& data,
+ const std::string& compare,
+ bool value);
+
+/**
+ * @brief Do notation escaping of a string to an ostream.
+ *
+ * @param value The string to escape and serialize
+ * @param str The stream to serialize to.
+ */
+void serialize_string(const std::string& value, std::ostream& str);
+
+
+/**
+ * Local constants.
+ */
+static const std::string NOTATION_TRUE_SERIAL("true");
+static const std::string NOTATION_FALSE_SERIAL("false");
+
+static const char BINARY_TRUE_SERIAL = '1';
+static const char BINARY_FALSE_SERIAL = '0';
+
+
+/**
+ * LLSDParser
+ */
+LLSDParser::LLSDParser()
+ : mCheckLimits(true), mMaxBytesLeft(0), mParseLines(false)
+{
+}
+
+// virtual
+LLSDParser::~LLSDParser()
+{ }
+
+S32 LLSDParser::parse(std::istream& istr, LLSD& data, llssize max_bytes, S32 max_depth)
+{
+ mCheckLimits = LLSDSerialize::SIZE_UNLIMITED != max_bytes;
+ mMaxBytesLeft = max_bytes;
+ return doParse(istr, data, max_depth);
+}
+
+
+// Parse using routine to get() lines, faster than parse()
+S32 LLSDParser::parseLines(std::istream& istr, LLSD& data)
+{
+ mCheckLimits = false;
+ mParseLines = true;
+ return doParse(istr, data);
+}
+
+
+int LLSDParser::get(std::istream& istr) const
+{
+ if(mCheckLimits) --mMaxBytesLeft;
+ return istr.get();
+}
+
+std::istream& LLSDParser::get(
+ std::istream& istr,
+ char* s,
+ std::streamsize n,
+ char delim) const
+{
+ istr.get(s, n, delim);
+ if(mCheckLimits) mMaxBytesLeft -= istr.gcount();
+ return istr;
+}
+
+std::istream& LLSDParser::get(
+ std::istream& istr,
+ std::streambuf& sb,
+ char delim) const
+{
+ istr.get(sb, delim);
+ if(mCheckLimits) mMaxBytesLeft -= istr.gcount();
+ return istr;
+}
+
+std::istream& LLSDParser::ignore(std::istream& istr) const
+{
+ istr.ignore();
+ if(mCheckLimits) --mMaxBytesLeft;
+ return istr;
+}
+
+std::istream& LLSDParser::putback(std::istream& istr, char c) const
+{
+ istr.putback(c);
+ if(mCheckLimits) ++mMaxBytesLeft;
+ return istr;
+}
+
+std::istream& LLSDParser::read(
+ std::istream& istr,
+ char* s,
+ std::streamsize n) const
+{
+ istr.read(s, n);
+ if(mCheckLimits) mMaxBytesLeft -= istr.gcount();
+ return istr;
+}
+
+void LLSDParser::account(llssize bytes) const
+{
+ if(mCheckLimits) mMaxBytesLeft -= bytes;
+}
+
+
+/**
+ * LLSDNotationParser
+ */
+LLSDNotationParser::LLSDNotationParser()
+{
+}
+
+// virtual
+LLSDNotationParser::~LLSDNotationParser()
+{ }
+
+// virtual
+S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
+ // map: { string:object, string:object }
+ // array: [ object, object, object ]
+ // undef: !
+ // boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE
+ // integer: i####
+ // real: r####
+ // uuid: u####
+ // string: "g'day" | 'have a "nice" day' | s(size)"raw data"
+ // uri: l"escaped"
+ // date: d"YYYY-MM-DDTHH:MM:SS.FFZ"
+ // binary: b##"ff3120ab1" | b(size)"raw data"
+ char c;
+ c = istr.peek();
+ if (max_depth == 0)
+ {
+ return PARSE_FAILURE;
+ }
+ while(isspace(c))
+ {
+ // pop the whitespace.
+ c = get(istr);
+ c = istr.peek();
+ continue;
+ }
+ if(!istr.good())
+ {
+ return 0;
+ }
+ S32 parse_count = 1;
+ switch(c)
+ {
+ case '{':
+ {
+ S32 child_count = parseMap(istr, data, max_depth - 1);
+ if((child_count == PARSE_FAILURE) || data.isUndefined())
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ else
+ {
+ parse_count += child_count;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading map." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case '[':
+ {
+ S32 child_count = parseArray(istr, data, max_depth - 1);
+ if((child_count == PARSE_FAILURE) || data.isUndefined())
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ else
+ {
+ parse_count += child_count;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading array." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case '!':
+ c = get(istr);
+ data.clear();
+ break;
+
+ case '0':
+ c = get(istr);
+ data = false;
+ break;
+
+ case 'F':
+ case 'f':
+ ignore(istr);
+ c = istr.peek();
+ if(isalpha(c))
+ {
+ auto cnt = deserialize_boolean(
+ istr,
+ data,
+ NOTATION_FALSE_SERIAL,
+ false);
+ if(PARSE_FAILURE == cnt) parse_count = cnt;
+ else account(cnt);
+ }
+ else
+ {
+ data = false;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+
+ case '1':
+ c = get(istr);
+ data = true;
+ break;
+
+ case 'T':
+ case 't':
+ ignore(istr);
+ c = istr.peek();
+ if(isalpha(c))
+ {
+ auto cnt = deserialize_boolean(istr,data,NOTATION_TRUE_SERIAL,true);
+ if(PARSE_FAILURE == cnt) parse_count = cnt;
+ else account(cnt);
+ }
+ else
+ {
+ data = true;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+
+ case 'i':
+ {
+ c = get(istr);
+ S32 integer = 0;
+ istr >> integer;
+ data = integer;
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading integer." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case 'r':
+ {
+ c = get(istr);
+ F64 real = 0.0;
+ istr >> real;
+ data = real;
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading real." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case 'u':
+ {
+ c = get(istr);
+ LLUUID id;
+ istr >> id;
+ data = id;
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading uuid." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case '\"':
+ case '\'':
+ case 's':
+ if(!parseString(istr, data))
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading string." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+
+ case 'l':
+ {
+ c = get(istr); // pop the 'l'
+ c = get(istr); // pop the delimiter
+ std::string str;
+ auto cnt = deserialize_string_delim(istr, str, c);
+ if(PARSE_FAILURE == cnt)
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ else
+ {
+ data = LLURI(str);
+ account(cnt);
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading link." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case 'd':
+ {
+ c = get(istr); // pop the 'd'
+ c = get(istr); // pop the delimiter
+ std::string str;
+ auto cnt = deserialize_string_delim(istr, str, c);
+ if(PARSE_FAILURE == cnt)
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ else
+ {
+ data = LLDate(str);
+ account(cnt);
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading date." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case 'b':
+ if(!parseBinary(istr, data))
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading data." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+
+ default:
+ parse_count = PARSE_FAILURE;
+ LL_INFOS() << "Unrecognized character while parsing: int(" << int(c)
+ << ")" << LL_ENDL;
+ break;
+ }
+ if(PARSE_FAILURE == parse_count)
+ {
+ data.clear();
+ }
+ return parse_count;
+}
+
+S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
+ // map: { string:object, string:object }
+ map = LLSD::emptyMap();
+ S32 parse_count = 0;
+ char c = get(istr);
+ if(c == '{')
+ {
+ // eat commas, white
+ bool found_name = false;
+ std::string name;
+ c = get(istr);
+ while(c != '}' && istr.good())
+ {
+ if(!found_name)
+ {
+ if((c == '\"') || (c == '\'') || (c == 's'))
+ {
+ putback(istr, c);
+ found_name = true;
+ auto count = deserialize_string(istr, name, mMaxBytesLeft);
+ if(PARSE_FAILURE == count) return PARSE_FAILURE;
+ account(count);
+ }
+ c = get(istr);
+ }
+ else
+ {
+ if(isspace(c) || (c == ':'))
+ {
+ c = get(istr);
+ continue;
+ }
+ putback(istr, c);
+ LLSD child;
+ S32 count = doParse(istr, child, max_depth);
+ if(count > 0)
+ {
+ // There must be a value for every key, thus
+ // child_count must be greater than 0.
+ parse_count += count;
+ map.insert(name, child);
+ }
+ else
+ {
+ return PARSE_FAILURE;
+ }
+ found_name = false;
+ c = get(istr);
+ }
+ }
+ if(c != '}')
+ {
+ map.clear();
+ return PARSE_FAILURE;
+ }
+ }
+ return parse_count;
+}
+
+S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
+ // array: [ object, object, object ]
+ array = LLSD::emptyArray();
+ S32 parse_count = 0;
+ char c = get(istr);
+ if(c == '[')
+ {
+ // eat commas, white
+ c = get(istr);
+ while((c != ']') && istr.good())
+ {
+ LLSD child;
+ if(isspace(c) || (c == ','))
+ {
+ c = get(istr);
+ continue;
+ }
+ putback(istr, c);
+ S32 count = doParse(istr, child, max_depth);
+ if(PARSE_FAILURE == count)
+ {
+ return PARSE_FAILURE;
+ }
+ else
+ {
+ parse_count += count;
+ array.append(child);
+ }
+ c = get(istr);
+ }
+ if(c != ']')
+ {
+ return PARSE_FAILURE;
+ }
+ }
+ return parse_count;
+}
+
+bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
+ std::string value;
+ auto count = deserialize_string(istr, value, mMaxBytesLeft);
+ if(PARSE_FAILURE == count) return false;
+ account(count);
+ data = value;
+ return true;
+}
+
+bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
+ // binary: b##"ff3120ab1"
+ // or: b(len)"..."
+
+ // I want to manually control those values here to make sure the
+ // parser doesn't break when someone changes a constant somewhere
+ // else.
+ const U32 BINARY_BUFFER_SIZE = 256;
+ const U32 STREAM_GET_COUNT = 255;
+
+ // need to read the base out.
+ char buf[BINARY_BUFFER_SIZE]; /* Flawfinder: ignore */
+ get(istr, buf, STREAM_GET_COUNT, '"');
+ char c = get(istr);
+ if(c != '"') return false;
+ if(0 == strncmp("b(", buf, 2))
+ {
+ // We probably have a valid raw binary stream. determine
+ // the size, and read it.
+ auto len = strtol(buf + 2, NULL, 0);
+ if(mCheckLimits && (len > mMaxBytesLeft)) return false;
+ std::vector<U8> value;
+ if(len)
+ {
+ value.resize(len);
+ account(fullread(istr, (char *)&value[0], len));
+ }
+ c = get(istr); // strip off the trailing double-quote
+ data = value;
+ }
+ else if(0 == strncmp("b64", buf, 3))
+ {
+ // *FIX: A bit inefficient, but works for now. To make the
+ // format better, I would need to add a hint into the
+ // serialization format that indicated how long it was.
+ std::stringstream coded_stream;
+ get(istr, *(coded_stream.rdbuf()), '\"');
+ c = get(istr);
+ std::string encoded(coded_stream.str());
+ S32 len = apr_base64_decode_len(encoded.c_str());
+ std::vector<U8> value;
+ if(len)
+ {
+ value.resize(len);
+ len = apr_base64_decode_binary(&value[0], encoded.c_str());
+ value.resize(len);
+ }
+ data = value;
+ }
+ else if(0 == strncmp("b16", buf, 3))
+ {
+ // yay, base 16. We pop the next character which is either a
+ // double quote or base 16 data. If it's a double quote, we're
+ // done parsing. If it's not, put the data back, and read the
+ // stream until the next double quote.
+ char* read; /*Flawfinder: ignore*/
+ U8 byte;
+ U8 byte_buffer[BINARY_BUFFER_SIZE];
+ U8* write;
+ std::vector<U8> value;
+ c = get(istr);
+ while(c != '"')
+ {
+ putback(istr, c);
+ read = buf;
+ write = byte_buffer;
+ get(istr, buf, STREAM_GET_COUNT, '"');
+ c = get(istr);
+ while(*read != '\0') /*Flawfinder: ignore*/
+ {
+ byte = hex_as_nybble(*read++);
+ byte = byte << 4;
+ byte |= hex_as_nybble(*read++);
+ *write++ = byte;
+ }
+ // copy the data out of the byte buffer
+ value.insert(value.end(), byte_buffer, write);
+ }
+ data = value;
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * LLSDBinaryParser
+ */
+LLSDBinaryParser::LLSDBinaryParser()
+{
+}
+
+// virtual
+LLSDBinaryParser::~LLSDBinaryParser()
+{
+}
+
+// virtual
+S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
+/**
+ * Undefined: '!'<br>
+ * Boolean: '1' for true '0' for false<br>
+ * Integer: 'i' + 4 bytes network byte order<br>
+ * Real: 'r' + 8 bytes IEEE double<br>
+ * UUID: 'u' + 16 byte unsigned integer<br>
+ * String: 's' + 4 byte integer size + string<br>
+ * strings also secretly support the notation format
+ * Date: 'd' + 8 byte IEEE double for seconds since epoch<br>
+ * URI: 'l' + 4 byte integer size + string uri<br>
+ * Binary: 'b' + 4 byte integer size + binary data<br>
+ * Array: '[' + 4 byte integer size + all values + ']'<br>
+ * Map: '{' + 4 byte integer size every(key + value) + '}'<br>
+ * map keys are serialized as s + 4 byte integer size + string or in the
+ * notation format.
+ */
+ char c;
+ c = get(istr);
+ if(!istr.good())
+ {
+ return 0;
+ }
+ if (max_depth == 0)
+ {
+ return PARSE_FAILURE;
+ }
+ S32 parse_count = 1;
+ switch(c)
+ {
+ case '{':
+ {
+ S32 child_count = parseMap(istr, data, max_depth - 1);
+ if((child_count == PARSE_FAILURE) || data.isUndefined())
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ else
+ {
+ parse_count += child_count;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary map." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case '[':
+ {
+ S32 child_count = parseArray(istr, data, max_depth - 1);
+ if((child_count == PARSE_FAILURE) || data.isUndefined())
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ else
+ {
+ parse_count += child_count;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary array." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case '!':
+ data.clear();
+ break;
+
+ case '0':
+ data = false;
+ break;
+
+ case '1':
+ data = true;
+ break;
+
+ case 'i':
+ {
+ U32 value_nbo = 0;
+ read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/
+ data = (S32)ntohl(value_nbo);
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary integer." << LL_ENDL;
+ }
+ break;
+ }
+
+ case 'r':
+ {
+ F64 real_nbo = 0.0;
+ read(istr, (char*)&real_nbo, sizeof(F64)); /*Flawfinder: ignore*/
+ data = ll_ntohd(real_nbo);
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary real." << LL_ENDL;
+ }
+ break;
+ }
+
+ case 'u':
+ {
+ LLUUID id;
+ read(istr, (char*)(&id.mData), UUID_BYTES); /*Flawfinder: ignore*/
+ data = id;
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary uuid." << LL_ENDL;
+ }
+ break;
+ }
+
+ case '\'':
+ case '"':
+ {
+ std::string value;
+ auto cnt = deserialize_string_delim(istr, value, c);
+ if(PARSE_FAILURE == cnt)
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ else
+ {
+ data = value;
+ account(cnt);
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary (notation-style) string."
+ << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case 's':
+ {
+ std::string value;
+ if(parseString(istr, value))
+ {
+ data = value;
+ }
+ else
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary string." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case 'l':
+ {
+ std::string value;
+ if(parseString(istr, value))
+ {
+ data = LLURI(value);
+ }
+ else
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary link." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case 'd':
+ {
+ F64 real = 0.0;
+ read(istr, (char*)&real, sizeof(F64)); /*Flawfinder: ignore*/
+ data = LLDate(real);
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary date." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ case 'b':
+ {
+ // We probably have a valid raw binary stream. determine
+ // the size, and read it.
+ U32 size_nbo = 0;
+ read(istr, (char*)&size_nbo, sizeof(U32)); /*Flawfinder: ignore*/
+ S32 size = (S32)ntohl(size_nbo);
+ if(mCheckLimits && (size > mMaxBytesLeft))
+ {
+ parse_count = PARSE_FAILURE;
+ }
+ else
+ {
+ std::vector<U8> value;
+ if(size > 0)
+ {
+ value.resize(size);
+ account(fullread(istr, (char*)&value[0], size));
+ }
+ data = value;
+ }
+ if(istr.fail())
+ {
+ LL_INFOS() << "STREAM FAILURE reading binary." << LL_ENDL;
+ parse_count = PARSE_FAILURE;
+ }
+ break;
+ }
+
+ default:
+ parse_count = PARSE_FAILURE;
+ LL_INFOS() << "Unrecognized character while parsing: int(" << int(c)
+ << ")" << LL_ENDL;
+ break;
+ }
+ if(PARSE_FAILURE == parse_count)
+ {
+ data.clear();
+ }
+ return parse_count;
+}
+
+S32 LLSDBinaryParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const
+{
+ map = LLSD::emptyMap();
+ U32 value_nbo = 0;
+ read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/
+ S32 size = (S32)ntohl(value_nbo);
+ S32 parse_count = 0;
+ S32 count = 0;
+ char c = get(istr);
+ while(c != '}' && (count < size) && istr.good())
+ {
+ std::string name;
+ switch(c)
+ {
+ case 'k':
+ if(!parseString(istr, name))
+ {
+ return PARSE_FAILURE;
+ }
+ break;
+ case '\'':
+ case '"':
+ {
+ auto cnt = deserialize_string_delim(istr, name, c);
+ if(PARSE_FAILURE == cnt) return PARSE_FAILURE;
+ account(cnt);
+ break;
+ }
+ }
+ LLSD child;
+ S32 child_count = doParse(istr, child, max_depth);
+ if(child_count > 0)
+ {
+ // There must be a value for every key, thus child_count
+ // must be greater than 0.
+ parse_count += child_count;
+ map.insert(name, child);
+ }
+ else
+ {
+ return PARSE_FAILURE;
+ }
+ ++count;
+ c = get(istr);
+ }
+ if((c != '}') || (count < size))
+ {
+ // Make sure it is correctly terminated and we parsed as many
+ // as were said to be there.
+ return PARSE_FAILURE;
+ }
+ return parse_count;
+}
+
+S32 LLSDBinaryParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const
+{
+ array = LLSD::emptyArray();
+ U32 value_nbo = 0;
+ read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/
+ S32 size = (S32)ntohl(value_nbo);
+
+ // *FIX: This would be a good place to reserve some space in the
+ // array...
+
+ S32 parse_count = 0;
+ S32 count = 0;
+ char c = istr.peek();
+ while((c != ']') && (count < size) && istr.good())
+ {
+ LLSD child;
+ S32 child_count = doParse(istr, child, max_depth);
+ if(PARSE_FAILURE == child_count)
+ {
+ return PARSE_FAILURE;
+ }
+ if(child_count)
+ {
+ parse_count += child_count;
+ array.append(child);
+ }
+ ++count;
+ c = istr.peek();
+ }
+ c = get(istr);
+ if((c != ']') || (count < size))
+ {
+ // Make sure it is correctly terminated and we parsed as many
+ // as were said to be there.
+ return PARSE_FAILURE;
+ }
+ return parse_count;
+}
+
+bool LLSDBinaryParser::parseString(
+ std::istream& istr,
+ std::string& value) const
+{
+ // *FIX: This is memory inefficient.
+ U32 value_nbo = 0;
+ read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/
+ S32 size = (S32)ntohl(value_nbo);
+ if(mCheckLimits && (size > mMaxBytesLeft)) return false;
+ if(size < 0) return false;
+ std::vector<char> buf;
+ if(size)
+ {
+ buf.resize(size);
+ account(fullread(istr, &buf[0], size));
+ value.assign(buf.begin(), buf.end());
+ }
+ return true;
+}
+
+
+/**
+ * LLSDFormatter
+ */
+LLSDFormatter::LLSDFormatter(bool boolAlpha, const std::string& realFmt, EFormatterOptions options):
+ mOptions(options)
+{
+ boolalpha(boolAlpha);
+ realFormat(realFmt);
+}
+
+// virtual
+LLSDFormatter::~LLSDFormatter()
+{ }
+
+void LLSDFormatter::boolalpha(bool alpha)
+{
+ mBoolAlpha = alpha;
+}
+
+void LLSDFormatter::realFormat(const std::string& format)
+{
+ mRealFormat = format;
+}
+
+S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr) const
+{
+ // pass options captured by constructor
+ return format(data, ostr, mOptions);
+}
+
+S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const
+{
+ return format_impl(data, ostr, options, 0);
+}
+
+void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const
+{
+ std::string buffer = llformat(mRealFormat.c_str(), real);
+ ostr << buffer;
+}
+
+/**
+ * LLSDNotationFormatter
+ */
+LLSDNotationFormatter::LLSDNotationFormatter(bool boolAlpha, const std::string& realFormat,
+ EFormatterOptions options):
+ LLSDFormatter(boolAlpha, realFormat, options)
+{
+}
+
+// virtual
+LLSDNotationFormatter::~LLSDNotationFormatter()
+{ }
+
+// static
+std::string LLSDNotationFormatter::escapeString(const std::string& in)
+{
+ std::ostringstream ostr;
+ serialize_string(in, ostr);
+ return ostr.str();
+}
+
+S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options, U32 level) const
+{
+ S32 format_count = 1;
+ std::string pre;
+ std::string post;
+
+ if (options & LLSDFormatter::OPTIONS_PRETTY)
+ {
+ for (U32 i = 0; i < level; i++)
+ {
+ pre += " ";
+ }
+ post = "\n";
+ }
+
+ switch(data.type())
+ {
+ case LLSD::TypeMap:
+ {
+ if (0 != level) ostr << post << pre;
+ ostr << "{";
+ std::string inner_pre;
+ if (options & LLSDFormatter::OPTIONS_PRETTY)
+ {
+ inner_pre = pre + " ";
+ }
+
+ bool need_comma = false;
+ LLSD::map_const_iterator iter = data.beginMap();
+ LLSD::map_const_iterator end = data.endMap();
+ for(; iter != end; ++iter)
+ {
+ if(need_comma) ostr << ",";
+ need_comma = true;
+ ostr << post << inner_pre << '\'';
+ serialize_string((*iter).first, ostr);
+ ostr << "':";
+ format_count += format_impl((*iter).second, ostr, options, level + 2);
+ }
+ ostr << post << pre << "}";
+ break;
+ }
+
+ case LLSD::TypeArray:
+ {
+ ostr << post << pre << "[";
+ bool need_comma = false;
+ LLSD::array_const_iterator iter = data.beginArray();
+ LLSD::array_const_iterator end = data.endArray();
+ for(; iter != end; ++iter)
+ {
+ if(need_comma) ostr << ",";
+ need_comma = true;
+ format_count += format_impl(*iter, ostr, options, level + 1);
+ }
+ ostr << "]";
+ break;
+ }
+
+ case LLSD::TypeUndefined:
+ ostr << "!";
+ break;
+
+ case LLSD::TypeBoolean:
+ if(mBoolAlpha ||
+#if( LL_WINDOWS || __GNUC__ > 2)
+ (ostr.flags() & std::ios::boolalpha)
+#else
+ (ostr.flags() & 0x0100)
+#endif
+ )
+ {
+ ostr << (data.asBoolean()
+ ? NOTATION_TRUE_SERIAL : NOTATION_FALSE_SERIAL);
+ }
+ else
+ {
+ ostr << (data.asBoolean() ? 1 : 0);
+ }
+ break;
+
+ case LLSD::TypeInteger:
+ ostr << "i" << data.asInteger();
+ break;
+
+ case LLSD::TypeReal:
+ ostr << "r";
+ if(mRealFormat.empty())
+ {
+ ostr << data.asReal();
+ }
+ else
+ {
+ formatReal(data.asReal(), ostr);
+ }
+ break;
+
+ case LLSD::TypeUUID:
+ ostr << "u" << data.asUUID();
+ break;
+
+ case LLSD::TypeString:
+ ostr << '\'';
+ serialize_string(data.asStringRef(), ostr);
+ ostr << '\'';
+ break;
+
+ case LLSD::TypeDate:
+ ostr << "d\"" << data.asDate() << "\"";
+ break;
+
+ case LLSD::TypeURI:
+ ostr << "l\"";
+ serialize_string(data.asString(), ostr);
+ ostr << "\"";
+ break;
+
+ case LLSD::TypeBinary:
+ {
+ // *FIX: memory inefficient.
+ const std::vector<U8>& buffer = data.asBinary();
+ if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY)
+ {
+ ostr << "b16\"";
+ if (! buffer.empty())
+ {
+ std::ios_base::fmtflags old_flags = ostr.flags();
+ ostr.setf( std::ios::hex, std::ios::basefield );
+ // It shouldn't strictly matter whether the emitted hex digits
+ // are uppercase; LLSDNotationParser handles either; but as of
+ // 2020-05-13, Python's llbase.llsd requires uppercase hex.
+ ostr << std::uppercase;
+ auto oldfill(ostr.fill('0'));
+ auto oldwidth(ostr.width());
+ for (size_t i = 0; i < buffer.size(); i++)
+ {
+ // have to restate setw() before every conversion
+ ostr << std::setw(2) << (int) buffer[i];
+ }
+ ostr.width(oldwidth);
+ ostr.fill(oldfill);
+ ostr.flags(old_flags);
+ }
+ }
+ else // ! OPTIONS_PRETTY_BINARY
+ {
+ ostr << "b(" << buffer.size() << ")\"";
+ if (! buffer.empty())
+ {
+ ostr.write((const char*)&buffer[0], buffer.size());
+ }
+ }
+ ostr << "\"";
+ break;
+ }
+
+ default:
+ // *NOTE: This should never happen.
+ ostr << "!";
+ break;
+ }
+ return format_count;
+}
+
+/**
+ * LLSDBinaryFormatter
+ */
+LLSDBinaryFormatter::LLSDBinaryFormatter(bool boolAlpha, const std::string& realFormat,
+ EFormatterOptions options):
+ LLSDFormatter(boolAlpha, realFormat, options)
+{
+}
+
+// virtual
+LLSDBinaryFormatter::~LLSDBinaryFormatter()
+{ }
+
+// virtual
+S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options, U32 level) const
+{
+ S32 format_count = 1;
+ switch(data.type())
+ {
+ case LLSD::TypeMap:
+ {
+ ostr.put('{');
+ U32 size_nbo = htonl(data.size());
+ ostr.write((const char*)(&size_nbo), sizeof(U32));
+ LLSD::map_const_iterator iter = data.beginMap();
+ LLSD::map_const_iterator end = data.endMap();
+ for(; iter != end; ++iter)
+ {
+ ostr.put('k');
+ formatString((*iter).first, ostr);
+ format_count += format_impl((*iter).second, ostr, options, level+1);
+ }
+ ostr.put('}');
+ break;
+ }
+
+ case LLSD::TypeArray:
+ {
+ ostr.put('[');
+ U32 size_nbo = htonl(data.size());
+ ostr.write((const char*)(&size_nbo), sizeof(U32));
+ LLSD::array_const_iterator iter = data.beginArray();
+ LLSD::array_const_iterator end = data.endArray();
+ for(; iter != end; ++iter)
+ {
+ format_count += format_impl(*iter, ostr, options, level+1);
+ }
+ ostr.put(']');
+ break;
+ }
+
+ case LLSD::TypeUndefined:
+ ostr.put('!');
+ break;
+
+ case LLSD::TypeBoolean:
+ if(data.asBoolean()) ostr.put(BINARY_TRUE_SERIAL);
+ else ostr.put(BINARY_FALSE_SERIAL);
+ break;
+
+ case LLSD::TypeInteger:
+ {
+ ostr.put('i');
+ U32 value_nbo = htonl(data.asInteger());
+ ostr.write((const char*)(&value_nbo), sizeof(U32));
+ break;
+ }
+
+ case LLSD::TypeReal:
+ {
+ ostr.put('r');
+ F64 value_nbo = ll_htond(data.asReal());
+ ostr.write((const char*)(&value_nbo), sizeof(F64));
+ break;
+ }
+
+ case LLSD::TypeUUID:
+ {
+ ostr.put('u');
+ LLUUID temp = data.asUUID();
+ ostr.write((const char*)(&(temp.mData)), UUID_BYTES);
+ break;
+ }
+
+ case LLSD::TypeString:
+ ostr.put('s');
+ formatString(data.asStringRef(), ostr);
+ break;
+
+ case LLSD::TypeDate:
+ {
+ ostr.put('d');
+ F64 value = data.asReal();
+ ostr.write((const char*)(&value), sizeof(F64));
+ break;
+ }
+
+ case LLSD::TypeURI:
+ ostr.put('l');
+ formatString(data.asString(), ostr);
+ break;
+
+ case LLSD::TypeBinary:
+ {
+ ostr.put('b');
+ const std::vector<U8>& buffer = data.asBinary();
+ U32 size_nbo = htonl(buffer.size());
+ ostr.write((const char*)(&size_nbo), sizeof(U32));
+ if(buffer.size()) ostr.write((const char*)&buffer[0], buffer.size());
+ break;
+ }
+
+ default:
+ // *NOTE: This should never happen.
+ ostr.put('!');
+ break;
+ }
+ return format_count;
+}
+
+void LLSDBinaryFormatter::formatString(
+ const std::string& string,
+ std::ostream& ostr) const
+{
+ U32 size_nbo = htonl(string.size());
+ ostr.write((const char*)(&size_nbo), sizeof(U32));
+ ostr.write(string.c_str(), string.size());
+}
+
+/**
+ * local functions
+ */
+llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes)
+{
+ int c = istr.get();
+ if(istr.fail())
+ {
+ // No data in stream, bail out but mention the character we
+ // grabbed.
+ return LLSDParser::PARSE_FAILURE;
+ }
+
+ llssize rv = LLSDParser::PARSE_FAILURE;
+ switch(c)
+ {
+ case '\'':
+ case '"':
+ rv = deserialize_string_delim(istr, value, c);
+ break;
+ case 's':
+ // technically, less than max_bytes, but this is just meant to
+ // catch egregious protocol errors. parse errors will be
+ // caught in the case of incorrect counts.
+ rv = deserialize_string_raw(istr, value, max_bytes);
+ break;
+ default:
+ break;
+ }
+ if(LLSDParser::PARSE_FAILURE == rv) return rv;
+ return rv + 1; // account for the character grabbed at the top.
+}
+
+llssize deserialize_string_delim(
+ std::istream& istr,
+ std::string& value,
+ char delim)
+{
+ std::ostringstream write_buffer;
+ bool found_escape = false;
+ bool found_hex = false;
+ bool found_digit = false;
+ U8 byte = 0;
+ llssize count = 0;
+
+ while (true)
+ {
+ int next_byte = istr.get();
+ ++count;
+
+ if(istr.fail())
+ {
+ // If our stream is empty, break out
+ value = write_buffer.str();
+ return LLSDParser::PARSE_FAILURE;
+ }
+
+ char next_char = (char)next_byte; // Now that we know it's not EOF
+
+ if(found_escape)
+ {
+ // next character(s) is a special sequence.
+ if(found_hex)
+ {
+ if(found_digit)
+ {
+ found_digit = false;
+ found_hex = false;
+ found_escape = false;
+ byte = byte << 4;
+ byte |= hex_as_nybble(next_char);
+ write_buffer << byte;
+ byte = 0;
+ }
+ else
+ {
+ // next character is the first nybble of
+ //
+ found_digit = true;
+ byte = hex_as_nybble(next_char);
+ }
+ }
+ else if(next_char == 'x')
+ {
+ found_hex = true;
+ }
+ else
+ {
+ switch(next_char)
+ {
+ case 'a':
+ write_buffer << '\a';
+ break;
+ case 'b':
+ write_buffer << '\b';
+ break;
+ case 'f':
+ write_buffer << '\f';
+ break;
+ case 'n':
+ write_buffer << '\n';
+ break;
+ case 'r':
+ write_buffer << '\r';
+ break;
+ case 't':
+ write_buffer << '\t';
+ break;
+ case 'v':
+ write_buffer << '\v';
+ break;
+ default:
+ write_buffer << next_char;
+ break;
+ }
+ found_escape = false;
+ }
+ }
+ else if(next_char == '\\')
+ {
+ found_escape = true;
+ }
+ else if(next_char == delim)
+ {
+ break;
+ }
+ else
+ {
+ write_buffer << next_char;
+ }
+ }
+
+ value = write_buffer.str();
+ return count;
+}
+
+llssize deserialize_string_raw(
+ std::istream& istr,
+ std::string& value,
+ llssize max_bytes)
+{
+ llssize count = 0;
+ const S32 BUF_LEN = 20;
+ char buf[BUF_LEN]; /* Flawfinder: ignore */
+ istr.get(buf, BUF_LEN - 1, ')');
+ count += istr.gcount();
+ int c = istr.get();
+ c = istr.get();
+ count += 2;
+ if(((c == '"') || (c == '\'')) && (buf[0] == '('))
+ {
+ // We probably have a valid raw string. determine
+ // the size, and read it.
+ // *FIX: This is memory inefficient.
+ auto len = strtol(buf + 1, NULL, 0);
+ if((max_bytes>0)&&(len>max_bytes)) return LLSDParser::PARSE_FAILURE;
+ std::vector<char> buf;
+ if(len)
+ {
+ buf.resize(len);
+ count += fullread(istr, (char *)&buf[0], len);
+ value.assign(buf.begin(), buf.end());
+ }
+ c = istr.get();
+ ++count;
+ if(!((c == '"') || (c == '\'')))
+ {
+ return LLSDParser::PARSE_FAILURE;
+ }
+ }
+ else
+ {
+ return LLSDParser::PARSE_FAILURE;
+ }
+ return count;
+}
+
+static const char* NOTATION_STRING_CHARACTERS[256] =
+{
+ "\\x00", // 0
+ "\\x01", // 1
+ "\\x02", // 2
+ "\\x03", // 3
+ "\\x04", // 4
+ "\\x05", // 5
+ "\\x06", // 6
+ "\\a", // 7
+ "\\b", // 8
+ "\\t", // 9
+ "\\n", // 10
+ "\\v", // 11
+ "\\f", // 12
+ "\\r", // 13
+ "\\x0e", // 14
+ "\\x0f", // 15
+ "\\x10", // 16
+ "\\x11", // 17
+ "\\x12", // 18
+ "\\x13", // 19
+ "\\x14", // 20
+ "\\x15", // 21
+ "\\x16", // 22
+ "\\x17", // 23
+ "\\x18", // 24
+ "\\x19", // 25
+ "\\x1a", // 26
+ "\\x1b", // 27
+ "\\x1c", // 28
+ "\\x1d", // 29
+ "\\x1e", // 30
+ "\\x1f", // 31
+ " ", // 32
+ "!", // 33
+ "\"", // 34
+ "#", // 35
+ "$", // 36
+ "%", // 37
+ "&", // 38
+ "\\'", // 39
+ "(", // 40
+ ")", // 41
+ "*", // 42
+ "+", // 43
+ ",", // 44
+ "-", // 45
+ ".", // 46
+ "/", // 47
+ "0", // 48
+ "1", // 49
+ "2", // 50
+ "3", // 51
+ "4", // 52
+ "5", // 53
+ "6", // 54
+ "7", // 55
+ "8", // 56
+ "9", // 57
+ ":", // 58
+ ";", // 59
+ "<", // 60
+ "=", // 61
+ ">", // 62
+ "?", // 63
+ "@", // 64
+ "A", // 65
+ "B", // 66
+ "C", // 67
+ "D", // 68
+ "E", // 69
+ "F", // 70
+ "G", // 71
+ "H", // 72
+ "I", // 73
+ "J", // 74
+ "K", // 75
+ "L", // 76
+ "M", // 77
+ "N", // 78
+ "O", // 79
+ "P", // 80
+ "Q", // 81
+ "R", // 82
+ "S", // 83
+ "T", // 84
+ "U", // 85
+ "V", // 86
+ "W", // 87
+ "X", // 88
+ "Y", // 89
+ "Z", // 90
+ "[", // 91
+ "\\\\", // 92
+ "]", // 93
+ "^", // 94
+ "_", // 95
+ "`", // 96
+ "a", // 97
+ "b", // 98
+ "c", // 99
+ "d", // 100
+ "e", // 101
+ "f", // 102
+ "g", // 103
+ "h", // 104
+ "i", // 105
+ "j", // 106
+ "k", // 107
+ "l", // 108
+ "m", // 109
+ "n", // 110
+ "o", // 111
+ "p", // 112
+ "q", // 113
+ "r", // 114
+ "s", // 115
+ "t", // 116
+ "u", // 117
+ "v", // 118
+ "w", // 119
+ "x", // 120
+ "y", // 121
+ "z", // 122
+ "{", // 123
+ "|", // 124
+ "}", // 125
+ "~", // 126
+ "\\x7f", // 127
+ "\\x80", // 128
+ "\\x81", // 129
+ "\\x82", // 130
+ "\\x83", // 131
+ "\\x84", // 132
+ "\\x85", // 133
+ "\\x86", // 134
+ "\\x87", // 135
+ "\\x88", // 136
+ "\\x89", // 137
+ "\\x8a", // 138
+ "\\x8b", // 139
+ "\\x8c", // 140
+ "\\x8d", // 141
+ "\\x8e", // 142
+ "\\x8f", // 143
+ "\\x90", // 144
+ "\\x91", // 145
+ "\\x92", // 146
+ "\\x93", // 147
+ "\\x94", // 148
+ "\\x95", // 149
+ "\\x96", // 150
+ "\\x97", // 151
+ "\\x98", // 152
+ "\\x99", // 153
+ "\\x9a", // 154
+ "\\x9b", // 155
+ "\\x9c", // 156
+ "\\x9d", // 157
+ "\\x9e", // 158
+ "\\x9f", // 159
+ "\\xa0", // 160
+ "\\xa1", // 161
+ "\\xa2", // 162
+ "\\xa3", // 163
+ "\\xa4", // 164
+ "\\xa5", // 165
+ "\\xa6", // 166
+ "\\xa7", // 167
+ "\\xa8", // 168
+ "\\xa9", // 169
+ "\\xaa", // 170
+ "\\xab", // 171
+ "\\xac", // 172
+ "\\xad", // 173
+ "\\xae", // 174
+ "\\xaf", // 175
+ "\\xb0", // 176
+ "\\xb1", // 177
+ "\\xb2", // 178
+ "\\xb3", // 179
+ "\\xb4", // 180
+ "\\xb5", // 181
+ "\\xb6", // 182
+ "\\xb7", // 183
+ "\\xb8", // 184
+ "\\xb9", // 185
+ "\\xba", // 186
+ "\\xbb", // 187
+ "\\xbc", // 188
+ "\\xbd", // 189
+ "\\xbe", // 190
+ "\\xbf", // 191
+ "\\xc0", // 192
+ "\\xc1", // 193
+ "\\xc2", // 194
+ "\\xc3", // 195
+ "\\xc4", // 196
+ "\\xc5", // 197
+ "\\xc6", // 198
+ "\\xc7", // 199
+ "\\xc8", // 200
+ "\\xc9", // 201
+ "\\xca", // 202
+ "\\xcb", // 203
+ "\\xcc", // 204
+ "\\xcd", // 205
+ "\\xce", // 206
+ "\\xcf", // 207
+ "\\xd0", // 208
+ "\\xd1", // 209
+ "\\xd2", // 210
+ "\\xd3", // 211
+ "\\xd4", // 212
+ "\\xd5", // 213
+ "\\xd6", // 214
+ "\\xd7", // 215
+ "\\xd8", // 216
+ "\\xd9", // 217
+ "\\xda", // 218
+ "\\xdb", // 219
+ "\\xdc", // 220
+ "\\xdd", // 221
+ "\\xde", // 222
+ "\\xdf", // 223
+ "\\xe0", // 224
+ "\\xe1", // 225
+ "\\xe2", // 226
+ "\\xe3", // 227
+ "\\xe4", // 228
+ "\\xe5", // 229
+ "\\xe6", // 230
+ "\\xe7", // 231
+ "\\xe8", // 232
+ "\\xe9", // 233
+ "\\xea", // 234
+ "\\xeb", // 235
+ "\\xec", // 236
+ "\\xed", // 237
+ "\\xee", // 238
+ "\\xef", // 239
+ "\\xf0", // 240
+ "\\xf1", // 241
+ "\\xf2", // 242
+ "\\xf3", // 243
+ "\\xf4", // 244
+ "\\xf5", // 245
+ "\\xf6", // 246
+ "\\xf7", // 247
+ "\\xf8", // 248
+ "\\xf9", // 249
+ "\\xfa", // 250
+ "\\xfb", // 251
+ "\\xfc", // 252
+ "\\xfd", // 253
+ "\\xfe", // 254
+ "\\xff" // 255
+};
+
+void serialize_string(const std::string& value, std::ostream& str)
+{
+ std::string::const_iterator it = value.begin();
+ std::string::const_iterator end = value.end();
+ U8 c;
+ for(; it != end; ++it)
+ {
+ c = (U8)(*it);
+ str << NOTATION_STRING_CHARACTERS[c];
+ }
+}
+
+llssize deserialize_boolean(
+ std::istream& istr,
+ LLSD& data,
+ const std::string& compare,
+ bool value)
+{
+ //
+ // this method is a little goofy, because it gets the stream at
+ // the point where the t or f has already been
+ // consumed. Basically, parse for a patch to the string passed in
+ // starting at index 1. If it's a match:
+ // * assign data to value
+ // * return the number of bytes read
+ // otherwise:
+ // * set data to LLSD::null
+ // * return LLSDParser::PARSE_FAILURE (-1)
+ //
+ llssize bytes_read = 0;
+ std::string::size_type ii = 0;
+ char c = istr.peek();
+ while((++ii < compare.size())
+ && (tolower(c) == (int)compare[ii])
+ && istr.good())
+ {
+ istr.ignore();
+ ++bytes_read;
+ c = istr.peek();
+ }
+ if(compare.size() != ii)
+ {
+ data.clear();
+ return LLSDParser::PARSE_FAILURE;
+ }
+ data = value;
+ return bytes_read;
+}
+
+std::ostream& operator<<(std::ostream& s, const LLSD& llsd)
+{
+ s << LLSDNotationStreamer(llsd);
+ return s;
+}
+
+
+//dirty little zippers -- yell at davep if these are horrid
+
+//return a string containing gzipped bytes of binary serialized LLSD
+// VERY inefficient -- creates several copies of LLSD block in memory
+std::string zip_llsd(LLSD& data)
+{
+ std::stringstream llsd_strm;
+
+ LLSDSerialize::toBinary(data, llsd_strm);
+
+ const U32 CHUNK = 65536;
+
+ z_stream strm;
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+
+ S32 ret = deflateInit(&strm, Z_BEST_COMPRESSION);
+ if (ret != Z_OK)
+ {
+ LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL;
+ return std::string();
+ }
+
+ std::string source = llsd_strm.str();
+
+ U8 out[CHUNK];
+
+ strm.avail_in = narrow<size_t>(source.size());
+ strm.next_in = (U8*) source.data();
+ U8* output = NULL;
+
+ U32 cur_size = 0;
+
+ U32 have = 0;
+
+ do
+ {
+ strm.avail_out = CHUNK;
+ strm.next_out = out;
+
+ ret = deflate(&strm, Z_FINISH);
+ if (ret == Z_OK || ret == Z_STREAM_END)
+ { //copy result into output
+ if (strm.avail_out >= CHUNK)
+ {
+ deflateEnd(&strm);
+ if(output)
+ free(output);
+ LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL;
+ return std::string();
+ }
+
+ have = CHUNK-strm.avail_out;
+ U8* new_output = (U8*) realloc(output, cur_size+have);
+ if (new_output == NULL)
+ {
+ LL_WARNS() << "Failed to compress LLSD block: can't reallocate memory, current size: " << cur_size << " bytes; requested " << cur_size + have << " bytes." << LL_ENDL;
+ deflateEnd(&strm);
+ if (output)
+ {
+ free(output);
+ }
+ return std::string();
+ }
+ output = new_output;
+ memcpy(output+cur_size, out, have);
+ cur_size += have;
+ }
+ else
+ {
+ deflateEnd(&strm);
+ if(output)
+ free(output);
+ LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL;
+ return std::string();
+ }
+ }
+ while (ret == Z_OK);
+
+ std::string::size_type size = cur_size;
+
+ std::string result((char*) output, size);
+ deflateEnd(&strm);
+ if(output)
+ free(output);
+
+ return result;
+}
+
+//decompress a block of LLSD from provided istream
+// not very efficient -- creats a copy of decompressed LLSD block in memory
+// and deserializes from that copy using LLSDSerialize
+LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, std::istream& is, S32 size)
+{
+ std::unique_ptr<U8[]> in = std::unique_ptr<U8[]>(new(std::nothrow) U8[size]);
+ if (!in)
+ {
+ return ZR_MEM_ERROR;
+ }
+ is.read((char*) in.get(), size);
+
+ return unzip_llsd(data, in.get(), size);
+}
+
+LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, const U8* in, S32 size)
+{
+ U8* result = NULL;
+ llssize cur_size = 0;
+ z_stream strm;
+
+ constexpr U32 CHUNK = 1024 * 512;
+
+ static thread_local std::unique_ptr<U8[]> out;
+ if (!out)
+ {
+ out = std::unique_ptr<U8[]>(new(std::nothrow) U8[CHUNK]);
+ }
+
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = size;
+ strm.next_in = const_cast<U8*>(in);
+
+ S32 ret = inflateInit(&strm);
+
+ do
+ {
+ strm.avail_out = CHUNK;
+ strm.next_out = out.get();
+ ret = inflate(&strm, Z_NO_FLUSH);
+ switch (ret)
+ {
+ case Z_NEED_DICT:
+ case Z_DATA_ERROR:
+ {
+ inflateEnd(&strm);
+ free(result);
+ return ZR_DATA_ERROR;
+ }
+ case Z_STREAM_ERROR:
+ case Z_BUF_ERROR:
+ {
+ inflateEnd(&strm);
+ free(result);
+ return ZR_BUFFER_ERROR;
+ }
+
+ case Z_MEM_ERROR:
+ {
+ inflateEnd(&strm);
+ free(result);
+ return ZR_MEM_ERROR;
+ }
+ }
+
+ U32 have = CHUNK-strm.avail_out;
+
+ U8* new_result = (U8*)realloc(result, cur_size + have);
+ if (new_result == NULL)
+ {
+ inflateEnd(&strm);
+ if (result)
+ {
+ free(result);
+ }
+ return ZR_MEM_ERROR;
+ }
+ result = new_result;
+ memcpy(result+cur_size, out.get(), have);
+ cur_size += have;
+
+ } while (ret == Z_OK && ret != Z_STREAM_END);
+
+ inflateEnd(&strm);
+
+ if (ret != Z_STREAM_END)
+ {
+ free(result);
+ return ZR_DATA_ERROR;
+ }
+
+ //result now points to the decompressed LLSD block
+ {
+ char* result_ptr = strip_deprecated_header((char*)result, cur_size);
+
+ boost::iostreams::stream<boost::iostreams::array_source> istrm(result_ptr, cur_size);
+
+ if (!LLSDSerialize::fromBinary(data, istrm, cur_size, UNZIP_LLSD_MAX_DEPTH))
+ {
+ free(result);
+ return ZR_PARSE_ERROR;
+ }
+ }
+
+ free(result);
+ return ZR_OK;
+}
+//This unzip function will only work with a gzip header and trailer - while the contents
+//of the actual compressed data is the same for either format (gzip vs zlib ), the headers
+//and trailers are different for the formats.
+U8* unzip_llsdNavMesh( bool& valid, size_t& outsize, std::istream& is, S32 size )
+{
+ if (size == 0)
+ {
+ LL_WARNS() << "No data to unzip." << LL_ENDL;
+ return NULL;
+ }
+
+ U8* result = NULL;
+ U32 cur_size = 0;
+ z_stream strm;
+
+ const U32 CHUNK = 0x4000;
+
+ U8 *in = new(std::nothrow) U8[size];
+ if (in == NULL)
+ {
+ LL_WARNS() << "Memory allocation failure." << LL_ENDL;
+ return NULL;
+ }
+ is.read((char*) in, size);
+
+ U8 out[CHUNK];
+
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = size;
+ strm.next_in = in;
+
+
+ S32 ret = inflateInit2(&strm, windowBits | ENABLE_ZLIB_GZIP );
+ do
+ {
+ strm.avail_out = CHUNK;
+ strm.next_out = out;
+ ret = inflate(&strm, Z_NO_FLUSH);
+ if (ret == Z_STREAM_ERROR)
+ {
+ inflateEnd(&strm);
+ free(result);
+ delete [] in;
+ valid = false;
+ }
+
+ switch (ret)
+ {
+ case Z_NEED_DICT:
+ ret = Z_DATA_ERROR;
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ inflateEnd(&strm);
+ free(result);
+ delete [] in;
+ valid = false;
+ break;
+ }
+
+ U32 have = CHUNK-strm.avail_out;
+
+ U8* new_result = (U8*) realloc(result, cur_size + have);
+ if (new_result == NULL)
+ {
+ LL_WARNS() << "Failed to unzip LLSD NavMesh block: can't reallocate memory, current size: " << cur_size
+ << " bytes; requested " << cur_size + have
+ << " bytes; total syze: ." << size << " bytes."
+ << LL_ENDL;
+ inflateEnd(&strm);
+ if (result)
+ {
+ free(result);
+ }
+ delete[] in;
+ valid = false;
+ return NULL;
+ }
+ result = new_result;
+ memcpy(result+cur_size, out, have);
+ cur_size += have;
+
+ } while (ret == Z_OK);
+
+ inflateEnd(&strm);
+ delete [] in;
+
+ if (ret != Z_STREAM_END)
+ {
+ free(result);
+ valid = false;
+ return NULL;
+ }
+
+ //result now points to the decompressed LLSD block
+ {
+ outsize= cur_size;
+ valid = true;
+ }
+
+ return result;
+}
+
+char* strip_deprecated_header(char* in, llssize& cur_size, llssize* header_size)
+{
+ const char* deprecated_header = "<? LLSD/Binary ?>";
+ constexpr size_t deprecated_header_size = 17;
+
+ if (cur_size > deprecated_header_size
+ && memcmp(in, deprecated_header, deprecated_header_size) == 0)
+ {
+ in = in + deprecated_header_size;
+ cur_size = cur_size - deprecated_header_size;
+ if (header_size)
+ {
+ *header_size = deprecated_header_size + 1;
+ }
+ }
+
+ return in;
+}
+
diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index efce458117..4dd47ad1ac 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -1,1083 +1,1083 @@ -/** - * @file llsdutil.cpp - * @author Phoenix - * @date 2006-05-24 - * @brief Implementation of classes, functions, etc, for using structured data. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llsdutil.h" -#include <sstream> - -#if LL_WINDOWS -# define WIN32_LEAN_AND_MEAN -# include <winsock2.h> // for htonl -#elif LL_LINUX -# include <netinet/in.h> -#elif LL_DARWIN -# include <arpa/inet.h> -#endif - -#include "llsdserialize.h" -#include "stringize.h" -#include "is_approx_equal_fraction.h" - -#include <map> -#include <set> -#include <boost/range.hpp> - -// U32 -LLSD ll_sd_from_U32(const U32 val) -{ - std::vector<U8> v; - U32 net_order = htonl(val); - - v.resize(4); - memcpy(&(v[0]), &net_order, 4); /* Flawfinder: ignore */ - - return LLSD(v); -} - -U32 ll_U32_from_sd(const LLSD& sd) -{ - U32 ret; - std::vector<U8> v = sd.asBinary(); - if (v.size() < 4) - { - return 0; - } - memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */ - ret = ntohl(ret); - return ret; -} - -//U64 -LLSD ll_sd_from_U64(const U64 val) -{ - std::vector<U8> v; - U32 high, low; - - high = (U32)(val >> 32); - low = (U32)val; - high = htonl(high); - low = htonl(low); - - v.resize(8); - memcpy(&(v[0]), &high, 4); /* Flawfinder: ignore */ - memcpy(&(v[4]), &low, 4); /* Flawfinder: ignore */ - - return LLSD(v); -} - -U64 ll_U64_from_sd(const LLSD& sd) -{ - U32 high, low; - std::vector<U8> v = sd.asBinary(); - - if (v.size() < 8) - { - return 0; - } - - memcpy(&high, &(v[0]), 4); /* Flawfinder: ignore */ - memcpy(&low, &(v[4]), 4); /* Flawfinder: ignore */ - high = ntohl(high); - low = ntohl(low); - - return ((U64)high) << 32 | low; -} - -// IP Address (stored in net order in a U32, so don't need swizzling) -LLSD ll_sd_from_ipaddr(const U32 val) -{ - std::vector<U8> v; - - v.resize(4); - memcpy(&(v[0]), &val, 4); /* Flawfinder: ignore */ - - return LLSD(v); -} - -U32 ll_ipaddr_from_sd(const LLSD& sd) -{ - U32 ret; - std::vector<U8> v = sd.asBinary(); - if (v.size() < 4) - { - return 0; - } - memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */ - return ret; -} - -// Converts an LLSD binary to an LLSD string -LLSD ll_string_from_binary(const LLSD& sd) -{ - std::vector<U8> value = sd.asBinary(); - std::string str; - str.resize(value.size()); - memcpy(&str[0], &value[0], value.size()); - return str; -} - -// Converts an LLSD string to an LLSD binary -LLSD ll_binary_from_string(const LLSD& sd) -{ - std::vector<U8> binary_value; - - std::string string_value = sd.asString(); - for (const U8 c : string_value) - { - binary_value.push_back(c); - } - - binary_value.push_back('\0'); - - return binary_value; -} - -char* ll_print_sd(const LLSD& sd) -{ - const U32 bufferSize = 10 * 1024; - static char buffer[bufferSize]; - std::ostringstream stream; - //stream.rdbuf()->pubsetbuf(buffer, bufferSize); - stream << LLSDOStreamer<LLSDXMLFormatter>(sd); - stream << std::ends; - strncpy(buffer, stream.str().c_str(), bufferSize); - buffer[bufferSize - 1] = '\0'; - return buffer; -} - -char* ll_pretty_print_sd_ptr(const LLSD* sd) -{ - if (sd) - { - return ll_pretty_print_sd(*sd); - } - return NULL; -} - -char* ll_pretty_print_sd(const LLSD& sd) -{ - const U32 bufferSize = 100 * 1024; - static char buffer[bufferSize]; - std::ostringstream stream; - //stream.rdbuf()->pubsetbuf(buffer, bufferSize); - stream << LLSDOStreamer<LLSDXMLFormatter>(sd, LLSDFormatter::OPTIONS_PRETTY); - stream << std::ends; - strncpy(buffer, stream.str().c_str(), bufferSize); - buffer[bufferSize - 1] = '\0'; - return buffer; -} - -std::string ll_stream_notation_sd(const LLSD& sd) -{ - std::ostringstream stream; - stream << LLSDOStreamer<LLSDNotationFormatter>(sd); - return stream.str(); -} - - -//compares the structure of an LLSD to a template LLSD and stores the -//"valid" values in a 3rd LLSD. Default values pulled from the template -//if the tested LLSD does not contain the key/value pair. -//Excess values in the test LLSD are ignored in the resultant_llsd. -//If the llsd to test has a specific key to a map and the values -//are not of the same type, false is returned or if the LLSDs are not -//of the same value. Ordering of arrays matters -//Otherwise, returns true -BOOL compare_llsd_with_template( - const LLSD& llsd_to_test, - const LLSD& template_llsd, - LLSD& resultant_llsd) -{ - LL_PROFILE_ZONE_SCOPED - - if ( - llsd_to_test.isUndefined() && - template_llsd.isDefined() ) - { - resultant_llsd = template_llsd; - return TRUE; - } - else if ( llsd_to_test.type() != template_llsd.type() ) - { - resultant_llsd = LLSD(); - return FALSE; - } - - if ( llsd_to_test.isArray() ) - { - //they are both arrays - //we loop over all the items in the template - //verifying that the to_test has a subset (in the same order) - //any shortcoming in the testing_llsd are just taken - //to be the rest of the template - LLSD data; - LLSD::array_const_iterator test_iter; - LLSD::array_const_iterator template_iter; - - resultant_llsd = LLSD::emptyArray(); - test_iter = llsd_to_test.beginArray(); - - for ( - template_iter = template_llsd.beginArray(); - (template_iter != template_llsd.endArray() && - test_iter != llsd_to_test.endArray()); - ++template_iter) - { - if ( !compare_llsd_with_template( - *test_iter, - *template_iter, - data) ) - { - resultant_llsd = LLSD(); - return FALSE; - } - else - { - resultant_llsd.append(data); - } - - ++test_iter; - } - - //so either the test or the template ended - //we do another loop now to the end of the template - //grabbing the default values - for (; - template_iter != template_llsd.endArray(); - ++template_iter) - { - resultant_llsd.append(*template_iter); - } - } - else if ( llsd_to_test.isMap() ) - { - //now we loop over the keys of the two maps - //any excess is taken from the template - //excess is ignored in the test - LLSD value; - LLSD::map_const_iterator template_iter; - - resultant_llsd = LLSD::emptyMap(); - for ( - template_iter = template_llsd.beginMap(); - template_iter != template_llsd.endMap(); - ++template_iter) - { - if ( llsd_to_test.has(template_iter->first) ) - { - //the test LLSD has the same key - if ( !compare_llsd_with_template( - llsd_to_test[template_iter->first], - template_iter->second, - value) ) - { - resultant_llsd = LLSD(); - return FALSE; - } - else - { - resultant_llsd[template_iter->first] = value; - } - } - else - { - //test llsd doesn't have it...take the - //template as default value - resultant_llsd[template_iter->first] = - template_iter->second; - } - } - } - else - { - //of same type...take the test llsd's value - resultant_llsd = llsd_to_test; - } - - - return TRUE; -} - -// filter_llsd_with_template() is a direct clone (copy-n-paste) of -// compare_llsd_with_template with the following differences: -// (1) bool vs BOOL return types -// (2) A map with the key value "*" is a special value and maps any key in the -// test llsd that doesn't have an explicitly matching key in the template. -// (3) The element of an array with exactly one element is taken as a template -// for *all* the elements of the test array. If the template array is of -// different size, compare_llsd_with_template() semantics apply. -bool filter_llsd_with_template( - const LLSD & llsd_to_test, - const LLSD & template_llsd, - LLSD & resultant_llsd) -{ - LL_PROFILE_ZONE_SCOPED - - if (llsd_to_test.isUndefined() && template_llsd.isDefined()) - { - resultant_llsd = template_llsd; - return true; - } - else if (llsd_to_test.type() != template_llsd.type()) - { - resultant_llsd = LLSD(); - return false; - } - - if (llsd_to_test.isArray()) - { - //they are both arrays - //we loop over all the items in the template - //verifying that the to_test has a subset (in the same order) - //any shortcoming in the testing_llsd are just taken - //to be the rest of the template - LLSD data; - LLSD::array_const_iterator test_iter; - LLSD::array_const_iterator template_iter; - - resultant_llsd = LLSD::emptyArray(); - test_iter = llsd_to_test.beginArray(); - - if (1 == template_llsd.size()) - { - // If the template has a single item, treat it as - // the template for *all* items in the test LLSD. - template_iter = template_llsd.beginArray(); - - for (; test_iter != llsd_to_test.endArray(); ++test_iter) - { - if (! filter_llsd_with_template(*test_iter, *template_iter, data)) - { - resultant_llsd = LLSD(); - return false; - } - else - { - resultant_llsd.append(data); - } - } - } - else - { - // Traditional compare_llsd_with_template matching - - for (template_iter = template_llsd.beginArray(); - template_iter != template_llsd.endArray() && - test_iter != llsd_to_test.endArray(); - ++template_iter, ++test_iter) - { - if (! filter_llsd_with_template(*test_iter, *template_iter, data)) - { - resultant_llsd = LLSD(); - return false; - } - else - { - resultant_llsd.append(data); - } - } - - //so either the test or the template ended - //we do another loop now to the end of the template - //grabbing the default values - for (; - template_iter != template_llsd.endArray(); - ++template_iter) - { - resultant_llsd.append(*template_iter); - } - } - } - else if (llsd_to_test.isMap()) - { - resultant_llsd = LLSD::emptyMap(); - - //now we loop over the keys of the two maps - //any excess is taken from the template - //excess is ignored in the test - - // Special tag for wildcarded LLSD map key templates - const LLSD::String wildcard_tag("*"); - - const bool template_has_wildcard = template_llsd.has(wildcard_tag); - LLSD wildcard_value; - LLSD value; - - const LLSD::map_const_iterator template_iter_end(template_llsd.endMap()); - for (LLSD::map_const_iterator template_iter(template_llsd.beginMap()); - template_iter_end != template_iter; - ++template_iter) - { - if (wildcard_tag == template_iter->first) - { - wildcard_value = template_iter->second; - } - else if (llsd_to_test.has(template_iter->first)) - { - //the test LLSD has the same key - if (! filter_llsd_with_template(llsd_to_test[template_iter->first], - template_iter->second, - value)) - { - resultant_llsd = LLSD(); - return false; - } - else - { - resultant_llsd[template_iter->first] = value; - } - } - else if (! template_has_wildcard) - { - // test llsd doesn't have it...take the - // template as default value - resultant_llsd[template_iter->first] = template_iter->second; - } - } - if (template_has_wildcard) - { - LLSD sub_value; - LLSD::map_const_iterator test_iter; - - for (test_iter = llsd_to_test.beginMap(); - test_iter != llsd_to_test.endMap(); - ++test_iter) - { - if (resultant_llsd.has(test_iter->first)) - { - // Final value has test key, assume more specific - // template matched and we shouldn't modify it again. - continue; - } - else if (! filter_llsd_with_template(test_iter->second, - wildcard_value, - sub_value)) - { - // Test value doesn't match wildcarded template - resultant_llsd = LLSD(); - return false; - } - else - { - // Test value matches template, add the actuals. - resultant_llsd[test_iter->first] = sub_value; - } - } - } - } - else - { - //of same type...take the test llsd's value - resultant_llsd = llsd_to_test; - } - - return true; -} - -/***************************************************************************** -* Helpers for llsd_matches() -*****************************************************************************/ -// raw data used for LLSD::Type lookup -struct Data -{ - LLSD::Type type; - const char* name; -} typedata[] = -{ -#define def(type) { LLSD::type, &#type[4] } - def(TypeUndefined), - def(TypeBoolean), - def(TypeInteger), - def(TypeReal), - def(TypeString), - def(TypeUUID), - def(TypeDate), - def(TypeURI), - def(TypeBinary), - def(TypeMap), - def(TypeArray) -#undef def -}; - -// LLSD::Type lookup class into which we load the above static data -class TypeLookup -{ - typedef std::map<LLSD::Type, std::string> MapType; - -public: - TypeLookup() - { - LL_PROFILE_ZONE_SCOPED - - for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) - { - mMap[di->type] = di->name; - } - } - - std::string lookup(LLSD::Type type) const - { - LL_PROFILE_ZONE_SCOPED - - MapType::const_iterator found = mMap.find(type); - if (found != mMap.end()) - { - return found->second; - } - return STRINGIZE("<unknown LLSD type " << type << ">"); - } - -private: - MapType mMap; -}; - -// static instance of the lookup class -static const TypeLookup sTypes; - -// describe a mismatch; phrasing may want tweaking -const std::string op(" required instead of "); - -// llsd_matches() wants to identify specifically where in a complex prototype -// structure the mismatch occurred. This entails passing a prefix string, -// empty for the top-level call. If the prototype contains an array of maps, -// and the mismatch occurs in the second map in a key 'foo', we want to -// decorate the returned string with: "[1]['foo']: etc." On the other hand, we -// want to omit the entire prefix -- including colon -- if the mismatch is at -// top level. This helper accepts the (possibly empty) recursively-accumulated -// prefix string, returning either empty or the original string with colon -// appended. -static std::string colon(const std::string& pfx) -{ - if (pfx.empty()) - return pfx; - return pfx + ": "; -} - -// param type for match_types -typedef std::vector<LLSD::Type> TypeVector; - -// The scalar cases in llsd_matches() use this helper. In most cases, we can -// accept not only the exact type specified in the prototype, but also other -// types convertible to the expected type. That implies looping over an array -// of such types. If the actual type doesn't match any of them, we want to -// provide a list of acceptable conversions as well as the exact type, e.g.: -// "Integer (or Boolean, Real, String) required instead of UUID". Both the -// implementation and the calling logic are simplified by separating out the -// expected type from the convertible types. -static std::string match_types(LLSD::Type expect, // prototype.type() - const TypeVector& accept, // types convertible to that type - LLSD::Type actual, // type we're checking - const std::string& pfx) // as for llsd_matches -{ - LL_PROFILE_ZONE_SCOPED - - // Trivial case: if the actual type is exactly what we expect, we're good. - if (actual == expect) - return ""; - - // For the rest of the logic, build up a suitable error string as we go so - // we only have to make a single pass over the list of acceptable types. - // If we detect success along the way, we'll simply discard the partial - // error string. - std::ostringstream out; - out << colon(pfx) << sTypes.lookup(expect); - - // If there are any convertible types, append that list. - if (! accept.empty()) - { - out << " ("; - const char* sep = "or "; - for (TypeVector::const_iterator ai(accept.begin()), aend(accept.end()); - ai != aend; ++ai, sep = ", ") - { - // Don't forget to return success if we match any of those types... - if (actual == *ai) - return ""; - out << sep << sTypes.lookup(*ai); - } - out << ')'; - } - // If we got this far, it's because 'actual' was not one of the acceptable - // types, so we must return an error. 'out' already contains colon(pfx) - // and the formatted list of acceptable types, so just append the mismatch - // phrase and the actual type. - out << op << sTypes.lookup(actual); - return out.str(); -} - -// see docstring in .h file -std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx) -{ - LL_PROFILE_ZONE_SCOPED - - // An undefined prototype means that any data is valid. - // An undefined slot in an array or map prototype means that any data - // may fill that slot. - if (prototype.isUndefined()) - return ""; - // A prototype array must match a data array with at least as many - // entries. Moreover, every prototype entry must match the - // corresponding data entry. - if (prototype.isArray()) - { - if (! data.isArray()) - { - return STRINGIZE(colon(pfx) << "Array" << op << sTypes.lookup(data.type())); - } - if (data.size() < prototype.size()) - { - return STRINGIZE(colon(pfx) << "Array size " << prototype.size() << op - << "Array size " << data.size()); - } - for (LLSD::Integer i = 0; i < prototype.size(); ++i) - { - std::string match(llsd_matches(prototype[i], data[i], STRINGIZE('[' << i << ']'))); - if (! match.empty()) - { - return match; - } - } - return ""; - } - // A prototype map must match a data map. Every key in the prototype - // must have a corresponding key in the data map; every value in the - // prototype must match the corresponding key's value in the data. - if (prototype.isMap()) - { - if (! data.isMap()) - { - return STRINGIZE(colon(pfx) << "Map" << op << sTypes.lookup(data.type())); - } - // If there are a number of keys missing from the data, it would be - // frustrating to a coder to discover them one at a time, with a big - // build each time. Enumerate all missing keys. - std::ostringstream out; - out << colon(pfx); - const char* init = "Map missing keys: "; - const char* sep = init; - for (LLSD::map_const_iterator mi = prototype.beginMap(); mi != prototype.endMap(); ++mi) - { - if (! data.has(mi->first)) - { - out << sep << mi->first; - sep = ", "; - } - } - // So... are we missing any keys? - if (sep != init) - { - return out.str(); - } - // Good, the data block contains all the keys required by the - // prototype. Now match the prototype entries. - for (LLSD::map_const_iterator mi2 = prototype.beginMap(); mi2 != prototype.endMap(); ++mi2) - { - std::string match(llsd_matches(mi2->second, data[mi2->first], - STRINGIZE("['" << mi2->first << "']"))); - if (! match.empty()) - { - return match; - } - } - return ""; - } - // A String prototype can match String, Boolean, Integer, Real, UUID, - // Date and URI, because any of these can be converted to String. - if (prototype.isString()) - { - static LLSD::Type accept[] = - { - LLSD::TypeBoolean, - LLSD::TypeInteger, - LLSD::TypeReal, - LLSD::TypeUUID, - LLSD::TypeDate, - LLSD::TypeURI - }; - return match_types(prototype.type(), - TypeVector(boost::begin(accept), boost::end(accept)), - data.type(), - pfx); - } - // Boolean, Integer, Real match each other or String. TBD: ensure that - // a String value is numeric. - if (prototype.isBoolean() || prototype.isInteger() || prototype.isReal()) - { - static LLSD::Type all[] = - { - LLSD::TypeBoolean, - LLSD::TypeInteger, - LLSD::TypeReal, - LLSD::TypeString - }; - // Funny business: shuffle the set of acceptable types to include all - // but the prototype's type. Get the acceptable types in a set. - std::set<LLSD::Type> rest(boost::begin(all), boost::end(all)); - // Remove the prototype's type because we pass that separately. - rest.erase(prototype.type()); - return match_types(prototype.type(), - TypeVector(rest.begin(), rest.end()), - data.type(), - pfx); - } - // UUID, Date and URI match themselves or String. - if (prototype.isUUID() || prototype.isDate() || prototype.isURI()) - { - static LLSD::Type accept[] = - { - LLSD::TypeString - }; - return match_types(prototype.type(), - TypeVector(boost::begin(accept), boost::end(accept)), - data.type(), - pfx); - } - // We don't yet know the conversion semantics associated with any new LLSD - // data type that might be added, so until we've been extended to handle - // them, assume it's strict: the new type matches only itself. (This is - // true of Binary, which is why we don't handle that case separately.) Too - // bad LLSD doesn't define isConvertible(Type to, Type from). - return match_types(prototype.type(), TypeVector(), data.type(), pfx); -} - -bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits) -{ - LL_PROFILE_ZONE_SCOPED - - // We're comparing strict equality of LLSD representation rather than - // performing any conversions. So if the types aren't equal, the LLSD - // values aren't equal. - if (lhs.type() != rhs.type()) - { - return false; - } - - // Here we know both types are equal. Now compare values. - switch (lhs.type()) - { - case LLSD::TypeUndefined: - // Both are TypeUndefined. There's nothing more to know. - return true; - - case LLSD::TypeReal: - // This is where the 'bits' argument comes in handy. If passed - // explicitly, it means to use is_approx_equal_fraction() to compare. - if (bits >= 0) - { - return is_approx_equal_fraction(lhs.asReal(), rhs.asReal(), bits); - } - // Otherwise we compare bit representations, and the usual caveats - // about comparing floating-point numbers apply. Omitting 'bits' when - // comparing Real values is only useful when we expect identical bit - // representation for a given Real value, e.g. for integer-valued - // Reals. - return (lhs.asReal() == rhs.asReal()); - -#define COMPARE_SCALAR(type) \ - case LLSD::Type##type: \ - /* LLSD::URI has operator!=() but not operator==() */ \ - /* rely on the optimizer for all others */ \ - return (! (lhs.as##type() != rhs.as##type())) - - COMPARE_SCALAR(Boolean); - COMPARE_SCALAR(Integer); - COMPARE_SCALAR(String); - COMPARE_SCALAR(UUID); - COMPARE_SCALAR(Date); - COMPARE_SCALAR(URI); - COMPARE_SCALAR(Binary); - -#undef COMPARE_SCALAR - - case LLSD::TypeArray: - { - LLSD::array_const_iterator - lai(lhs.beginArray()), laend(lhs.endArray()), - rai(rhs.beginArray()), raend(rhs.endArray()); - // Compare array elements, walking the two arrays in parallel. - for ( ; lai != laend && rai != raend; ++lai, ++rai) - { - // If any one array element is unequal, the arrays are unequal. - if (! llsd_equals(*lai, *rai, bits)) - return false; - } - // Here we've reached the end of one or the other array. They're equal - // only if they're BOTH at end: that is, if they have equal length too. - return (lai == laend && rai == raend); - } - - case LLSD::TypeMap: - { - // Build a set of all rhs keys. - std::set<LLSD::String> rhskeys; - for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap()); - rmi != rmend; ++rmi) - { - rhskeys.insert(rmi->first); - } - // Now walk all the lhs keys. - for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap()); - lmi != lmend; ++lmi) - { - // Try to erase this lhs key from the set of rhs keys. If rhs has - // no such key, the maps are unequal. erase(key) returns count of - // items erased. - if (rhskeys.erase(lmi->first) != 1) - return false; - // Both maps have the current key. Compare values. - if (! llsd_equals(lmi->second, rhs[lmi->first], bits)) - return false; - } - // We've now established that all the lhs keys have equal values in - // both maps. The maps are equal unless rhs contains a superset of - // those keys. - return rhskeys.empty(); - } - - default: - // We expect that every possible type() value is specifically handled - // above. Failing to extend this switch to support a new LLSD type is - // an error that must be brought to the coder's attention. - LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << ", " << bits << "): " - "unknown type " << lhs.type() << LL_ENDL; - return false; // pacify the compiler - } -} - -/***************************************************************************** -* llsd::drill() -*****************************************************************************/ -namespace llsd -{ - -LLSD& drill_ref(LLSD& blob, const LLSD& rawPath) -{ - LL_PROFILE_ZONE_SCOPED - - // Treat rawPath uniformly as an array. If it's not already an array, - // store it as the only entry in one. (But let's say Undefined means an - // empty array.) - LLSD path; - if (rawPath.isArray() || rawPath.isUndefined()) - { - path = rawPath; - } - else - { - path.append(rawPath); - } - - // Need to indicate a current destination -- but that current destination - // must change as we step through the path array. Where normally we'd use - // an LLSD& to capture a subscripted LLSD lvalue, this time we must - // instead use a pointer -- since it must be reassigned. - // Start by pointing to the input blob exactly as is. - LLSD* located{&blob}; - - // Extract the element of interest by walking path. Use an explicit index - // so that, in case of a bogus type in path, we can identify the specific - // path entry that's bad. - for (LLSD::Integer i = 0; i < path.size(); ++i) - { - LL_PROFILE_ZONE_NUM( i ) - - const LLSD& key{path[i]}; - if (key.isString()) - { - // a string path element is a map key - located = &((*located)[key.asString()]); - } - else if (key.isInteger()) - { - // an integer path element is an array index - located = &((*located)[key.asInteger()]); - } - else - { - // What do we do with Real or Array or Map or ...? - // As it's a coder error -- not a user error -- rub the coder's - // face in it so it gets fixed. - LL_ERRS("llsdutil") << "drill(" << blob << ", " << rawPath - << "): path[" << i << "] bad type " - << sTypes.lookup(key.type()) << LL_ENDL; - } - } - - // dereference the pointer to return a reference to the element we found - return *located; -} - -LLSD drill(const LLSD& blob, const LLSD& path) -{ - LL_PROFILE_ZONE_SCOPED - - // drill_ref() does exactly what we want. Temporarily cast away - // const-ness and use that. - return drill_ref(const_cast<LLSD&>(blob), path); -} - -} // namespace llsd - -// Construct a deep partial clone of of an LLSD object. primitive types share -// references, however maps, arrays and binary objects are duplicated. An optional -// filter may be include to exclude/include keys in a map. -LLSD llsd_clone(LLSD value, LLSD filter) -{ - LL_PROFILE_ZONE_SCOPED - - LLSD clone; - bool has_filter(filter.isMap()); - - switch (value.type()) - { - case LLSD::TypeMap: - clone = LLSD::emptyMap(); - for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm) - { - if (has_filter) - { - if (filter.has((*itm).first)) - { - if (!filter[(*itm).first].asBoolean()) - continue; - } - else if (filter.has("*")) - { - if (!filter["*"].asBoolean()) - continue; - } - else - { - continue; - } - } - clone[(*itm).first] = llsd_clone((*itm).second, filter); - } - break; - case LLSD::TypeArray: - clone = LLSD::emptyArray(); - for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita) - { - clone.append(llsd_clone(*ita, filter)); - } - break; - - case LLSD::TypeBinary: - { - LLSD::Binary bin(value.asBinary().begin(), value.asBinary().end()); - clone = LLSD::Binary(bin); - break; - } - default: - clone = value; - } - - return clone; -} - -LLSD llsd_shallow(LLSD value, LLSD filter) -{ - LLSD shallow; - bool has_filter(filter.isMap()); - - if (value.isMap()) - { - shallow = LLSD::emptyMap(); - for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm) - { - if (has_filter) - { - if (filter.has((*itm).first)) - { - if (!filter[(*itm).first].asBoolean()) - continue; - } - else if (filter.has("*")) - { - if (!filter["*"].asBoolean()) - continue; - } - else - { - continue; - } - } - shallow[(*itm).first] = (*itm).second; - } - } - else if (value.isArray()) - { - shallow = LLSD::emptyArray(); - for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita) - { - shallow.append(*ita); - } - } - else - { - return value; - } - - return shallow; -} - -LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args) -{ - // LLSD supports a number of types, two of which are aggregates: Map and - // Array. We don't try to support Map: supporting Map would seem to - // promise that we could somehow match the string key to 'func's parameter - // names. Uh sorry, maybe in some future version of C++ with reflection. - if (args.isMap()) - { - LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported")); - } - // We expect an LLSD array, but what the heck, treat isUndefined() as a - // zero-length array for calling a nullary 'func'. - if (args.isUndefined() || args.isArray()) - { - // this works because LLSD().size() == 0 - if (args.size() != arity) - { - LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ", - args.size(), "-entry LLSD array)"))); - } - return args; - } - - // args is one of the scalar types - // scalar_LLSD.size() == 0, so don't test that here. - // You can pass a scalar LLSD only to a unary 'func'. - if (arity != 1) - { - LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), " - "LLSD ", LLSD::typeString(args.type()), ")"))); - } - // make an array of it - return llsd::array(args); -} +/**
+ * @file llsdutil.cpp
+ * @author Phoenix
+ * @date 2006-05-24
+ * @brief Implementation of classes, functions, etc, for using structured data.
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llsdutil.h"
+#include <sstream>
+
+#if LL_WINDOWS
+# define WIN32_LEAN_AND_MEAN
+# include <winsock2.h> // for htonl
+#elif LL_LINUX
+# include <netinet/in.h>
+#elif LL_DARWIN
+# include <arpa/inet.h>
+#endif
+
+#include "llsdserialize.h"
+#include "stringize.h"
+#include "is_approx_equal_fraction.h"
+
+#include <map>
+#include <set>
+#include <boost/range.hpp>
+
+// U32
+LLSD ll_sd_from_U32(const U32 val)
+{
+ std::vector<U8> v;
+ U32 net_order = htonl(val);
+
+ v.resize(4);
+ memcpy(&(v[0]), &net_order, 4); /* Flawfinder: ignore */
+
+ return LLSD(v);
+}
+
+U32 ll_U32_from_sd(const LLSD& sd)
+{
+ U32 ret;
+ std::vector<U8> v = sd.asBinary();
+ if (v.size() < 4)
+ {
+ return 0;
+ }
+ memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */
+ ret = ntohl(ret);
+ return ret;
+}
+
+//U64
+LLSD ll_sd_from_U64(const U64 val)
+{
+ std::vector<U8> v;
+ U32 high, low;
+
+ high = (U32)(val >> 32);
+ low = (U32)val;
+ high = htonl(high);
+ low = htonl(low);
+
+ v.resize(8);
+ memcpy(&(v[0]), &high, 4); /* Flawfinder: ignore */
+ memcpy(&(v[4]), &low, 4); /* Flawfinder: ignore */
+
+ return LLSD(v);
+}
+
+U64 ll_U64_from_sd(const LLSD& sd)
+{
+ U32 high, low;
+ std::vector<U8> v = sd.asBinary();
+
+ if (v.size() < 8)
+ {
+ return 0;
+ }
+
+ memcpy(&high, &(v[0]), 4); /* Flawfinder: ignore */
+ memcpy(&low, &(v[4]), 4); /* Flawfinder: ignore */
+ high = ntohl(high);
+ low = ntohl(low);
+
+ return ((U64)high) << 32 | low;
+}
+
+// IP Address (stored in net order in a U32, so don't need swizzling)
+LLSD ll_sd_from_ipaddr(const U32 val)
+{
+ std::vector<U8> v;
+
+ v.resize(4);
+ memcpy(&(v[0]), &val, 4); /* Flawfinder: ignore */
+
+ return LLSD(v);
+}
+
+U32 ll_ipaddr_from_sd(const LLSD& sd)
+{
+ U32 ret;
+ std::vector<U8> v = sd.asBinary();
+ if (v.size() < 4)
+ {
+ return 0;
+ }
+ memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */
+ return ret;
+}
+
+// Converts an LLSD binary to an LLSD string
+LLSD ll_string_from_binary(const LLSD& sd)
+{
+ std::vector<U8> value = sd.asBinary();
+ std::string str;
+ str.resize(value.size());
+ memcpy(&str[0], &value[0], value.size());
+ return str;
+}
+
+// Converts an LLSD string to an LLSD binary
+LLSD ll_binary_from_string(const LLSD& sd)
+{
+ std::vector<U8> binary_value;
+
+ std::string string_value = sd.asString();
+ for (const U8 c : string_value)
+ {
+ binary_value.push_back(c);
+ }
+
+ binary_value.push_back('\0');
+
+ return binary_value;
+}
+
+char* ll_print_sd(const LLSD& sd)
+{
+ const U32 bufferSize = 10 * 1024;
+ static char buffer[bufferSize];
+ std::ostringstream stream;
+ //stream.rdbuf()->pubsetbuf(buffer, bufferSize);
+ stream << LLSDOStreamer<LLSDXMLFormatter>(sd);
+ stream << std::ends;
+ strncpy(buffer, stream.str().c_str(), bufferSize);
+ buffer[bufferSize - 1] = '\0';
+ return buffer;
+}
+
+char* ll_pretty_print_sd_ptr(const LLSD* sd)
+{
+ if (sd)
+ {
+ return ll_pretty_print_sd(*sd);
+ }
+ return NULL;
+}
+
+char* ll_pretty_print_sd(const LLSD& sd)
+{
+ const U32 bufferSize = 100 * 1024;
+ static char buffer[bufferSize];
+ std::ostringstream stream;
+ //stream.rdbuf()->pubsetbuf(buffer, bufferSize);
+ stream << LLSDOStreamer<LLSDXMLFormatter>(sd, LLSDFormatter::OPTIONS_PRETTY);
+ stream << std::ends;
+ strncpy(buffer, stream.str().c_str(), bufferSize);
+ buffer[bufferSize - 1] = '\0';
+ return buffer;
+}
+
+std::string ll_stream_notation_sd(const LLSD& sd)
+{
+ std::ostringstream stream;
+ stream << LLSDOStreamer<LLSDNotationFormatter>(sd);
+ return stream.str();
+}
+
+
+//compares the structure of an LLSD to a template LLSD and stores the
+//"valid" values in a 3rd LLSD. Default values pulled from the template
+//if the tested LLSD does not contain the key/value pair.
+//Excess values in the test LLSD are ignored in the resultant_llsd.
+//If the llsd to test has a specific key to a map and the values
+//are not of the same type, false is returned or if the LLSDs are not
+//of the same value. Ordering of arrays matters
+//Otherwise, returns true
+bool compare_llsd_with_template(
+ const LLSD& llsd_to_test,
+ const LLSD& template_llsd,
+ LLSD& resultant_llsd)
+{
+ LL_PROFILE_ZONE_SCOPED
+
+ if (
+ llsd_to_test.isUndefined() &&
+ template_llsd.isDefined() )
+ {
+ resultant_llsd = template_llsd;
+ return true;
+ }
+ else if ( llsd_to_test.type() != template_llsd.type() )
+ {
+ resultant_llsd = LLSD();
+ return false;
+ }
+
+ if ( llsd_to_test.isArray() )
+ {
+ //they are both arrays
+ //we loop over all the items in the template
+ //verifying that the to_test has a subset (in the same order)
+ //any shortcoming in the testing_llsd are just taken
+ //to be the rest of the template
+ LLSD data;
+ LLSD::array_const_iterator test_iter;
+ LLSD::array_const_iterator template_iter;
+
+ resultant_llsd = LLSD::emptyArray();
+ test_iter = llsd_to_test.beginArray();
+
+ for (
+ template_iter = template_llsd.beginArray();
+ (template_iter != template_llsd.endArray() &&
+ test_iter != llsd_to_test.endArray());
+ ++template_iter)
+ {
+ if ( !compare_llsd_with_template(
+ *test_iter,
+ *template_iter,
+ data) )
+ {
+ resultant_llsd = LLSD();
+ return false;
+ }
+ else
+ {
+ resultant_llsd.append(data);
+ }
+
+ ++test_iter;
+ }
+
+ //so either the test or the template ended
+ //we do another loop now to the end of the template
+ //grabbing the default values
+ for (;
+ template_iter != template_llsd.endArray();
+ ++template_iter)
+ {
+ resultant_llsd.append(*template_iter);
+ }
+ }
+ else if ( llsd_to_test.isMap() )
+ {
+ //now we loop over the keys of the two maps
+ //any excess is taken from the template
+ //excess is ignored in the test
+ LLSD value;
+ LLSD::map_const_iterator template_iter;
+
+ resultant_llsd = LLSD::emptyMap();
+ for (
+ template_iter = template_llsd.beginMap();
+ template_iter != template_llsd.endMap();
+ ++template_iter)
+ {
+ if ( llsd_to_test.has(template_iter->first) )
+ {
+ //the test LLSD has the same key
+ if ( !compare_llsd_with_template(
+ llsd_to_test[template_iter->first],
+ template_iter->second,
+ value) )
+ {
+ resultant_llsd = LLSD();
+ return false;
+ }
+ else
+ {
+ resultant_llsd[template_iter->first] = value;
+ }
+ }
+ else
+ {
+ //test llsd doesn't have it...take the
+ //template as default value
+ resultant_llsd[template_iter->first] =
+ template_iter->second;
+ }
+ }
+ }
+ else
+ {
+ //of same type...take the test llsd's value
+ resultant_llsd = llsd_to_test;
+ }
+
+
+ return true;
+}
+
+// filter_llsd_with_template() is a direct clone (copy-n-paste) of
+// compare_llsd_with_template with the following differences:
+// (1) bool vs BOOL return types
+// (2) A map with the key value "*" is a special value and maps any key in the
+// test llsd that doesn't have an explicitly matching key in the template.
+// (3) The element of an array with exactly one element is taken as a template
+// for *all* the elements of the test array. If the template array is of
+// different size, compare_llsd_with_template() semantics apply.
+bool filter_llsd_with_template(
+ const LLSD & llsd_to_test,
+ const LLSD & template_llsd,
+ LLSD & resultant_llsd)
+{
+ LL_PROFILE_ZONE_SCOPED
+
+ if (llsd_to_test.isUndefined() && template_llsd.isDefined())
+ {
+ resultant_llsd = template_llsd;
+ return true;
+ }
+ else if (llsd_to_test.type() != template_llsd.type())
+ {
+ resultant_llsd = LLSD();
+ return false;
+ }
+
+ if (llsd_to_test.isArray())
+ {
+ //they are both arrays
+ //we loop over all the items in the template
+ //verifying that the to_test has a subset (in the same order)
+ //any shortcoming in the testing_llsd are just taken
+ //to be the rest of the template
+ LLSD data;
+ LLSD::array_const_iterator test_iter;
+ LLSD::array_const_iterator template_iter;
+
+ resultant_llsd = LLSD::emptyArray();
+ test_iter = llsd_to_test.beginArray();
+
+ if (1 == template_llsd.size())
+ {
+ // If the template has a single item, treat it as
+ // the template for *all* items in the test LLSD.
+ template_iter = template_llsd.beginArray();
+
+ for (; test_iter != llsd_to_test.endArray(); ++test_iter)
+ {
+ if (! filter_llsd_with_template(*test_iter, *template_iter, data))
+ {
+ resultant_llsd = LLSD();
+ return false;
+ }
+ else
+ {
+ resultant_llsd.append(data);
+ }
+ }
+ }
+ else
+ {
+ // Traditional compare_llsd_with_template matching
+
+ for (template_iter = template_llsd.beginArray();
+ template_iter != template_llsd.endArray() &&
+ test_iter != llsd_to_test.endArray();
+ ++template_iter, ++test_iter)
+ {
+ if (! filter_llsd_with_template(*test_iter, *template_iter, data))
+ {
+ resultant_llsd = LLSD();
+ return false;
+ }
+ else
+ {
+ resultant_llsd.append(data);
+ }
+ }
+
+ //so either the test or the template ended
+ //we do another loop now to the end of the template
+ //grabbing the default values
+ for (;
+ template_iter != template_llsd.endArray();
+ ++template_iter)
+ {
+ resultant_llsd.append(*template_iter);
+ }
+ }
+ }
+ else if (llsd_to_test.isMap())
+ {
+ resultant_llsd = LLSD::emptyMap();
+
+ //now we loop over the keys of the two maps
+ //any excess is taken from the template
+ //excess is ignored in the test
+
+ // Special tag for wildcarded LLSD map key templates
+ const LLSD::String wildcard_tag("*");
+
+ const bool template_has_wildcard = template_llsd.has(wildcard_tag);
+ LLSD wildcard_value;
+ LLSD value;
+
+ const LLSD::map_const_iterator template_iter_end(template_llsd.endMap());
+ for (LLSD::map_const_iterator template_iter(template_llsd.beginMap());
+ template_iter_end != template_iter;
+ ++template_iter)
+ {
+ if (wildcard_tag == template_iter->first)
+ {
+ wildcard_value = template_iter->second;
+ }
+ else if (llsd_to_test.has(template_iter->first))
+ {
+ //the test LLSD has the same key
+ if (! filter_llsd_with_template(llsd_to_test[template_iter->first],
+ template_iter->second,
+ value))
+ {
+ resultant_llsd = LLSD();
+ return false;
+ }
+ else
+ {
+ resultant_llsd[template_iter->first] = value;
+ }
+ }
+ else if (! template_has_wildcard)
+ {
+ // test llsd doesn't have it...take the
+ // template as default value
+ resultant_llsd[template_iter->first] = template_iter->second;
+ }
+ }
+ if (template_has_wildcard)
+ {
+ LLSD sub_value;
+ LLSD::map_const_iterator test_iter;
+
+ for (test_iter = llsd_to_test.beginMap();
+ test_iter != llsd_to_test.endMap();
+ ++test_iter)
+ {
+ if (resultant_llsd.has(test_iter->first))
+ {
+ // Final value has test key, assume more specific
+ // template matched and we shouldn't modify it again.
+ continue;
+ }
+ else if (! filter_llsd_with_template(test_iter->second,
+ wildcard_value,
+ sub_value))
+ {
+ // Test value doesn't match wildcarded template
+ resultant_llsd = LLSD();
+ return false;
+ }
+ else
+ {
+ // Test value matches template, add the actuals.
+ resultant_llsd[test_iter->first] = sub_value;
+ }
+ }
+ }
+ }
+ else
+ {
+ //of same type...take the test llsd's value
+ resultant_llsd = llsd_to_test;
+ }
+
+ return true;
+}
+
+/*****************************************************************************
+* Helpers for llsd_matches()
+*****************************************************************************/
+// raw data used for LLSD::Type lookup
+struct Data
+{
+ LLSD::Type type;
+ const char* name;
+} typedata[] =
+{
+#define def(type) { LLSD::type, &#type[4] }
+ def(TypeUndefined),
+ def(TypeBoolean),
+ def(TypeInteger),
+ def(TypeReal),
+ def(TypeString),
+ def(TypeUUID),
+ def(TypeDate),
+ def(TypeURI),
+ def(TypeBinary),
+ def(TypeMap),
+ def(TypeArray)
+#undef def
+};
+
+// LLSD::Type lookup class into which we load the above static data
+class TypeLookup
+{
+ typedef std::map<LLSD::Type, std::string> MapType;
+
+public:
+ TypeLookup()
+ {
+ LL_PROFILE_ZONE_SCOPED
+
+ for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di)
+ {
+ mMap[di->type] = di->name;
+ }
+ }
+
+ std::string lookup(LLSD::Type type) const
+ {
+ LL_PROFILE_ZONE_SCOPED
+
+ MapType::const_iterator found = mMap.find(type);
+ if (found != mMap.end())
+ {
+ return found->second;
+ }
+ return STRINGIZE("<unknown LLSD type " << type << ">");
+ }
+
+private:
+ MapType mMap;
+};
+
+// static instance of the lookup class
+static const TypeLookup sTypes;
+
+// describe a mismatch; phrasing may want tweaking
+const std::string op(" required instead of ");
+
+// llsd_matches() wants to identify specifically where in a complex prototype
+// structure the mismatch occurred. This entails passing a prefix string,
+// empty for the top-level call. If the prototype contains an array of maps,
+// and the mismatch occurs in the second map in a key 'foo', we want to
+// decorate the returned string with: "[1]['foo']: etc." On the other hand, we
+// want to omit the entire prefix -- including colon -- if the mismatch is at
+// top level. This helper accepts the (possibly empty) recursively-accumulated
+// prefix string, returning either empty or the original string with colon
+// appended.
+static std::string colon(const std::string& pfx)
+{
+ if (pfx.empty())
+ return pfx;
+ return pfx + ": ";
+}
+
+// param type for match_types
+typedef std::vector<LLSD::Type> TypeVector;
+
+// The scalar cases in llsd_matches() use this helper. In most cases, we can
+// accept not only the exact type specified in the prototype, but also other
+// types convertible to the expected type. That implies looping over an array
+// of such types. If the actual type doesn't match any of them, we want to
+// provide a list of acceptable conversions as well as the exact type, e.g.:
+// "Integer (or Boolean, Real, String) required instead of UUID". Both the
+// implementation and the calling logic are simplified by separating out the
+// expected type from the convertible types.
+static std::string match_types(LLSD::Type expect, // prototype.type()
+ const TypeVector& accept, // types convertible to that type
+ LLSD::Type actual, // type we're checking
+ const std::string& pfx) // as for llsd_matches
+{
+ LL_PROFILE_ZONE_SCOPED
+
+ // Trivial case: if the actual type is exactly what we expect, we're good.
+ if (actual == expect)
+ return "";
+
+ // For the rest of the logic, build up a suitable error string as we go so
+ // we only have to make a single pass over the list of acceptable types.
+ // If we detect success along the way, we'll simply discard the partial
+ // error string.
+ std::ostringstream out;
+ out << colon(pfx) << sTypes.lookup(expect);
+
+ // If there are any convertible types, append that list.
+ if (! accept.empty())
+ {
+ out << " (";
+ const char* sep = "or ";
+ for (TypeVector::const_iterator ai(accept.begin()), aend(accept.end());
+ ai != aend; ++ai, sep = ", ")
+ {
+ // Don't forget to return success if we match any of those types...
+ if (actual == *ai)
+ return "";
+ out << sep << sTypes.lookup(*ai);
+ }
+ out << ')';
+ }
+ // If we got this far, it's because 'actual' was not one of the acceptable
+ // types, so we must return an error. 'out' already contains colon(pfx)
+ // and the formatted list of acceptable types, so just append the mismatch
+ // phrase and the actual type.
+ out << op << sTypes.lookup(actual);
+ return out.str();
+}
+
+// see docstring in .h file
+std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx)
+{
+ LL_PROFILE_ZONE_SCOPED
+
+ // An undefined prototype means that any data is valid.
+ // An undefined slot in an array or map prototype means that any data
+ // may fill that slot.
+ if (prototype.isUndefined())
+ return "";
+ // A prototype array must match a data array with at least as many
+ // entries. Moreover, every prototype entry must match the
+ // corresponding data entry.
+ if (prototype.isArray())
+ {
+ if (! data.isArray())
+ {
+ return STRINGIZE(colon(pfx) << "Array" << op << sTypes.lookup(data.type()));
+ }
+ if (data.size() < prototype.size())
+ {
+ return STRINGIZE(colon(pfx) << "Array size " << prototype.size() << op
+ << "Array size " << data.size());
+ }
+ for (LLSD::Integer i = 0; i < prototype.size(); ++i)
+ {
+ std::string match(llsd_matches(prototype[i], data[i], STRINGIZE('[' << i << ']')));
+ if (! match.empty())
+ {
+ return match;
+ }
+ }
+ return "";
+ }
+ // A prototype map must match a data map. Every key in the prototype
+ // must have a corresponding key in the data map; every value in the
+ // prototype must match the corresponding key's value in the data.
+ if (prototype.isMap())
+ {
+ if (! data.isMap())
+ {
+ return STRINGIZE(colon(pfx) << "Map" << op << sTypes.lookup(data.type()));
+ }
+ // If there are a number of keys missing from the data, it would be
+ // frustrating to a coder to discover them one at a time, with a big
+ // build each time. Enumerate all missing keys.
+ std::ostringstream out;
+ out << colon(pfx);
+ const char* init = "Map missing keys: ";
+ const char* sep = init;
+ for (LLSD::map_const_iterator mi = prototype.beginMap(); mi != prototype.endMap(); ++mi)
+ {
+ if (! data.has(mi->first))
+ {
+ out << sep << mi->first;
+ sep = ", ";
+ }
+ }
+ // So... are we missing any keys?
+ if (sep != init)
+ {
+ return out.str();
+ }
+ // Good, the data block contains all the keys required by the
+ // prototype. Now match the prototype entries.
+ for (LLSD::map_const_iterator mi2 = prototype.beginMap(); mi2 != prototype.endMap(); ++mi2)
+ {
+ std::string match(llsd_matches(mi2->second, data[mi2->first],
+ STRINGIZE("['" << mi2->first << "']")));
+ if (! match.empty())
+ {
+ return match;
+ }
+ }
+ return "";
+ }
+ // A String prototype can match String, Boolean, Integer, Real, UUID,
+ // Date and URI, because any of these can be converted to String.
+ if (prototype.isString())
+ {
+ static LLSD::Type accept[] =
+ {
+ LLSD::TypeBoolean,
+ LLSD::TypeInteger,
+ LLSD::TypeReal,
+ LLSD::TypeUUID,
+ LLSD::TypeDate,
+ LLSD::TypeURI
+ };
+ return match_types(prototype.type(),
+ TypeVector(boost::begin(accept), boost::end(accept)),
+ data.type(),
+ pfx);
+ }
+ // Boolean, Integer, Real match each other or String. TBD: ensure that
+ // a String value is numeric.
+ if (prototype.isBoolean() || prototype.isInteger() || prototype.isReal())
+ {
+ static LLSD::Type all[] =
+ {
+ LLSD::TypeBoolean,
+ LLSD::TypeInteger,
+ LLSD::TypeReal,
+ LLSD::TypeString
+ };
+ // Funny business: shuffle the set of acceptable types to include all
+ // but the prototype's type. Get the acceptable types in a set.
+ std::set<LLSD::Type> rest(boost::begin(all), boost::end(all));
+ // Remove the prototype's type because we pass that separately.
+ rest.erase(prototype.type());
+ return match_types(prototype.type(),
+ TypeVector(rest.begin(), rest.end()),
+ data.type(),
+ pfx);
+ }
+ // UUID, Date and URI match themselves or String.
+ if (prototype.isUUID() || prototype.isDate() || prototype.isURI())
+ {
+ static LLSD::Type accept[] =
+ {
+ LLSD::TypeString
+ };
+ return match_types(prototype.type(),
+ TypeVector(boost::begin(accept), boost::end(accept)),
+ data.type(),
+ pfx);
+ }
+ // We don't yet know the conversion semantics associated with any new LLSD
+ // data type that might be added, so until we've been extended to handle
+ // them, assume it's strict: the new type matches only itself. (This is
+ // true of Binary, which is why we don't handle that case separately.) Too
+ // bad LLSD doesn't define isConvertible(Type to, Type from).
+ return match_types(prototype.type(), TypeVector(), data.type(), pfx);
+}
+
+bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits)
+{
+ LL_PROFILE_ZONE_SCOPED
+
+ // We're comparing strict equality of LLSD representation rather than
+ // performing any conversions. So if the types aren't equal, the LLSD
+ // values aren't equal.
+ if (lhs.type() != rhs.type())
+ {
+ return false;
+ }
+
+ // Here we know both types are equal. Now compare values.
+ switch (lhs.type())
+ {
+ case LLSD::TypeUndefined:
+ // Both are TypeUndefined. There's nothing more to know.
+ return true;
+
+ case LLSD::TypeReal:
+ // This is where the 'bits' argument comes in handy. If passed
+ // explicitly, it means to use is_approx_equal_fraction() to compare.
+ if (bits >= 0)
+ {
+ return is_approx_equal_fraction(lhs.asReal(), rhs.asReal(), bits);
+ }
+ // Otherwise we compare bit representations, and the usual caveats
+ // about comparing floating-point numbers apply. Omitting 'bits' when
+ // comparing Real values is only useful when we expect identical bit
+ // representation for a given Real value, e.g. for integer-valued
+ // Reals.
+ return (lhs.asReal() == rhs.asReal());
+
+#define COMPARE_SCALAR(type) \
+ case LLSD::Type##type: \
+ /* LLSD::URI has operator!=() but not operator==() */ \
+ /* rely on the optimizer for all others */ \
+ return (! (lhs.as##type() != rhs.as##type()))
+
+ COMPARE_SCALAR(Boolean);
+ COMPARE_SCALAR(Integer);
+ COMPARE_SCALAR(String);
+ COMPARE_SCALAR(UUID);
+ COMPARE_SCALAR(Date);
+ COMPARE_SCALAR(URI);
+ COMPARE_SCALAR(Binary);
+
+#undef COMPARE_SCALAR
+
+ case LLSD::TypeArray:
+ {
+ LLSD::array_const_iterator
+ lai(lhs.beginArray()), laend(lhs.endArray()),
+ rai(rhs.beginArray()), raend(rhs.endArray());
+ // Compare array elements, walking the two arrays in parallel.
+ for ( ; lai != laend && rai != raend; ++lai, ++rai)
+ {
+ // If any one array element is unequal, the arrays are unequal.
+ if (! llsd_equals(*lai, *rai, bits))
+ return false;
+ }
+ // Here we've reached the end of one or the other array. They're equal
+ // only if they're BOTH at end: that is, if they have equal length too.
+ return (lai == laend && rai == raend);
+ }
+
+ case LLSD::TypeMap:
+ {
+ // Build a set of all rhs keys.
+ std::set<LLSD::String> rhskeys;
+ for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap());
+ rmi != rmend; ++rmi)
+ {
+ rhskeys.insert(rmi->first);
+ }
+ // Now walk all the lhs keys.
+ for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap());
+ lmi != lmend; ++lmi)
+ {
+ // Try to erase this lhs key from the set of rhs keys. If rhs has
+ // no such key, the maps are unequal. erase(key) returns count of
+ // items erased.
+ if (rhskeys.erase(lmi->first) != 1)
+ return false;
+ // Both maps have the current key. Compare values.
+ if (! llsd_equals(lmi->second, rhs[lmi->first], bits))
+ return false;
+ }
+ // We've now established that all the lhs keys have equal values in
+ // both maps. The maps are equal unless rhs contains a superset of
+ // those keys.
+ return rhskeys.empty();
+ }
+
+ default:
+ // We expect that every possible type() value is specifically handled
+ // above. Failing to extend this switch to support a new LLSD type is
+ // an error that must be brought to the coder's attention.
+ LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << ", " << bits << "): "
+ "unknown type " << lhs.type() << LL_ENDL;
+ return false; // pacify the compiler
+ }
+}
+
+/*****************************************************************************
+* llsd::drill()
+*****************************************************************************/
+namespace llsd
+{
+
+LLSD& drill_ref(LLSD& blob, const LLSD& rawPath)
+{
+ LL_PROFILE_ZONE_SCOPED
+
+ // Treat rawPath uniformly as an array. If it's not already an array,
+ // store it as the only entry in one. (But let's say Undefined means an
+ // empty array.)
+ LLSD path;
+ if (rawPath.isArray() || rawPath.isUndefined())
+ {
+ path = rawPath;
+ }
+ else
+ {
+ path.append(rawPath);
+ }
+
+ // Need to indicate a current destination -- but that current destination
+ // must change as we step through the path array. Where normally we'd use
+ // an LLSD& to capture a subscripted LLSD lvalue, this time we must
+ // instead use a pointer -- since it must be reassigned.
+ // Start by pointing to the input blob exactly as is.
+ LLSD* located{&blob};
+
+ // Extract the element of interest by walking path. Use an explicit index
+ // so that, in case of a bogus type in path, we can identify the specific
+ // path entry that's bad.
+ for (LLSD::Integer i = 0; i < path.size(); ++i)
+ {
+ LL_PROFILE_ZONE_NUM( i )
+
+ const LLSD& key{path[i]};
+ if (key.isString())
+ {
+ // a string path element is a map key
+ located = &((*located)[key.asString()]);
+ }
+ else if (key.isInteger())
+ {
+ // an integer path element is an array index
+ located = &((*located)[key.asInteger()]);
+ }
+ else
+ {
+ // What do we do with Real or Array or Map or ...?
+ // As it's a coder error -- not a user error -- rub the coder's
+ // face in it so it gets fixed.
+ LL_ERRS("llsdutil") << "drill(" << blob << ", " << rawPath
+ << "): path[" << i << "] bad type "
+ << sTypes.lookup(key.type()) << LL_ENDL;
+ }
+ }
+
+ // dereference the pointer to return a reference to the element we found
+ return *located;
+}
+
+LLSD drill(const LLSD& blob, const LLSD& path)
+{
+ LL_PROFILE_ZONE_SCOPED
+
+ // drill_ref() does exactly what we want. Temporarily cast away
+ // const-ness and use that.
+ return drill_ref(const_cast<LLSD&>(blob), path);
+}
+
+} // namespace llsd
+
+// Construct a deep partial clone of of an LLSD object. primitive types share
+// references, however maps, arrays and binary objects are duplicated. An optional
+// filter may be include to exclude/include keys in a map.
+LLSD llsd_clone(LLSD value, LLSD filter)
+{
+ LL_PROFILE_ZONE_SCOPED
+
+ LLSD clone;
+ bool has_filter(filter.isMap());
+
+ switch (value.type())
+ {
+ case LLSD::TypeMap:
+ clone = LLSD::emptyMap();
+ for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm)
+ {
+ if (has_filter)
+ {
+ if (filter.has((*itm).first))
+ {
+ if (!filter[(*itm).first].asBoolean())
+ continue;
+ }
+ else if (filter.has("*"))
+ {
+ if (!filter["*"].asBoolean())
+ continue;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ clone[(*itm).first] = llsd_clone((*itm).second, filter);
+ }
+ break;
+ case LLSD::TypeArray:
+ clone = LLSD::emptyArray();
+ for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita)
+ {
+ clone.append(llsd_clone(*ita, filter));
+ }
+ break;
+
+ case LLSD::TypeBinary:
+ {
+ LLSD::Binary bin(value.asBinary().begin(), value.asBinary().end());
+ clone = LLSD::Binary(bin);
+ break;
+ }
+ default:
+ clone = value;
+ }
+
+ return clone;
+}
+
+LLSD llsd_shallow(LLSD value, LLSD filter)
+{
+ LLSD shallow;
+ bool has_filter(filter.isMap());
+
+ if (value.isMap())
+ {
+ shallow = LLSD::emptyMap();
+ for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm)
+ {
+ if (has_filter)
+ {
+ if (filter.has((*itm).first))
+ {
+ if (!filter[(*itm).first].asBoolean())
+ continue;
+ }
+ else if (filter.has("*"))
+ {
+ if (!filter["*"].asBoolean())
+ continue;
+ }
+ else
+ {
+ continue;
+ }
+ }
+ shallow[(*itm).first] = (*itm).second;
+ }
+ }
+ else if (value.isArray())
+ {
+ shallow = LLSD::emptyArray();
+ for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita)
+ {
+ shallow.append(*ita);
+ }
+ }
+ else
+ {
+ return value;
+ }
+
+ return shallow;
+}
+
+LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args)
+{
+ // LLSD supports a number of types, two of which are aggregates: Map and
+ // Array. We don't try to support Map: supporting Map would seem to
+ // promise that we could somehow match the string key to 'func's parameter
+ // names. Uh sorry, maybe in some future version of C++ with reflection.
+ if (args.isMap())
+ {
+ LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported"));
+ }
+ // We expect an LLSD array, but what the heck, treat isUndefined() as a
+ // zero-length array for calling a nullary 'func'.
+ if (args.isUndefined() || args.isArray())
+ {
+ // this works because LLSD().size() == 0
+ if (args.size() != arity)
+ {
+ LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ",
+ args.size(), "-entry LLSD array)")));
+ }
+ return args;
+ }
+
+ // args is one of the scalar types
+ // scalar_LLSD.size() == 0, so don't test that here.
+ // You can pass a scalar LLSD only to a unary 'func'.
+ if (arity != 1)
+ {
+ LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), "
+ "LLSD ", LLSD::typeString(args.type()), ")")));
+ }
+ // make an array of it
+ return llsd::array(args);
+}
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index aa497c53c7..aa234e2f62 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -1,674 +1,674 @@ -/** - * @file llsdutil.h - * @author Phoenix - * @date 2006-05-24 - * @brief Utility classes, functions, etc, for using structured data. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLSDUTIL_H -#define LL_LLSDUTIL_H - -#include "apply.h" // LL::invoke() -#include "function_types.h" // LL::function_arity -#include "llsd.h" -#include <boost/functional/hash.hpp> -#include <cassert> -#include <memory> // std::shared_ptr -#include <type_traits> -#include <vector> - -// U32 -LL_COMMON_API LLSD ll_sd_from_U32(const U32); -LL_COMMON_API U32 ll_U32_from_sd(const LLSD& sd); - -// U64 -LL_COMMON_API LLSD ll_sd_from_U64(const U64); -LL_COMMON_API U64 ll_U64_from_sd(const LLSD& sd); - -// IP Address -LL_COMMON_API LLSD ll_sd_from_ipaddr(const U32); -LL_COMMON_API U32 ll_ipaddr_from_sd(const LLSD& sd); - -// Binary to string -LL_COMMON_API LLSD ll_string_from_binary(const LLSD& sd); - -//String to binary -LL_COMMON_API LLSD ll_binary_from_string(const LLSD& sd); - -// Serializes sd to static buffer and returns pointer, useful for gdb debugging. -LL_COMMON_API char* ll_print_sd(const LLSD& sd); - -// Serializes sd to static buffer and returns pointer, using "pretty printing" mode. -LL_COMMON_API char* ll_pretty_print_sd_ptr(const LLSD* sd); -LL_COMMON_API char* ll_pretty_print_sd(const LLSD& sd); - -LL_COMMON_API std::string ll_stream_notation_sd(const LLSD& sd); - -//compares the structure of an LLSD to a template LLSD and stores the -//"valid" values in a 3rd LLSD. Default values -//are pulled from the template. Extra keys/values in the test -//are ignored in the resultant LLSD. Ordering of arrays matters -//Returns false if the test is of same type but values differ in type -//Otherwise, returns true - -LL_COMMON_API BOOL compare_llsd_with_template( - const LLSD& llsd_to_test, - const LLSD& template_llsd, - LLSD& resultant_llsd); - -// filter_llsd_with_template() is a direct clone (copy-n-paste) of -// compare_llsd_with_template with the following differences: -// (1) bool vs BOOL return types -// (2) A map with the key value "*" is a special value and maps any key in the -// test llsd that doesn't have an explicitly matching key in the template. -// (3) The element of an array with exactly one element is taken as a template -// for *all* the elements of the test array. If the template array is of -// different size, compare_llsd_with_template() semantics apply. -bool filter_llsd_with_template( - const LLSD & llsd_to_test, - const LLSD & template_llsd, - LLSD & resultant_llsd); - -/** - * Recursively determine whether a given LLSD data block "matches" another - * LLSD prototype. The returned string is empty() on success, non-empty() on - * mismatch. - * - * This function tests structure (types) rather than data values. It is - * intended for when a consumer expects an LLSD block with a particular - * structure, and must succinctly detect whether the arriving block is - * well-formed. For instance, a test of the form: - * @code - * if (! (data.has("request") && data.has("target") && data.has("modifier") ...)) - * @endcode - * could instead be expressed by initializing a prototype LLSD map with the - * required keys and writing: - * @code - * if (! llsd_matches(prototype, data).empty()) - * @endcode - * - * A non-empty return value is an error-message fragment intended to indicate - * to (English-speaking) developers where in the prototype structure the - * mismatch occurred. - * - * * If a slot in the prototype isUndefined(), then anything is valid at that - * place in the real object. (Passing prototype == LLSD() matches anything - * at all.) - * * An array in the prototype must match a data array at least that large. - * (Additional entries in the data array are ignored.) Every isDefined() - * entry in the prototype array must match the corresponding entry in the - * data array. - * * A map in the prototype must match a map in the data. Every key in the - * prototype map must match a corresponding key in the data map. (Additional - * keys in the data map are ignored.) Every isDefined() value in the - * prototype map must match the corresponding key's value in the data map. - * * Scalar values in the prototype are tested for @em type rather than value. - * For instance, a String in the prototype matches any String at all. In - * effect, storing an Integer at a particular place in the prototype asserts - * that the caller intends to apply asInteger() to the corresponding slot in - * the data. - * * A String in the prototype matches String, Boolean, Integer, Real, UUID, - * Date and URI, because asString() applied to any of these produces a - * meaningful result. - * * Similarly, a Boolean, Integer or Real in the prototype can match any of - * Boolean, Integer or Real in the data -- or even String. - * * UUID matches UUID or String. - * * Date matches Date or String. - * * URI matches URI or String. - * * Binary in the prototype matches only Binary in the data. - * - * @TODO: when a Boolean, Integer or Real in the prototype matches a String in - * the data, we should examine the String @em value to ensure it can be - * meaningfully converted to the requested type. The same goes for UUID, Date - * and URI. - */ -LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); - -/// Deep equality. If you want to compare LLSD::Real values for approximate -/// equality rather than bitwise equality, pass @a bits as for -/// is_approx_equal_fraction(). -LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits=-1); -/// If you don't care about LLSD::Real equality -inline bool operator==(const LLSD& lhs, const LLSD& rhs) -{ - return llsd_equals(lhs, rhs); -} -inline bool operator!=(const LLSD& lhs, const LLSD& rhs) -{ - // operator!=() should always be the negation of operator==() - return ! (lhs == rhs); -} - -// Simple function to copy data out of input & output iterators if -// there is no need for casting. -template<typename Input> LLSD llsd_copy_array(Input iter, Input end) -{ - LLSD dest; - for (; iter != end; ++iter) - { - dest.append(*iter); - } - return dest; -} - -namespace llsd -{ - -/** - * Drill down to locate an element in 'blob' according to 'path', where 'path' - * is one of the following: - * - * - LLSD::String: 'blob' is an LLSD::Map. Find the entry with key 'path'. - * - LLSD::Integer: 'blob' is an LLSD::Array. Find the entry with index 'path'. - * - Any other 'path' type will be interpreted as LLSD::Array, and 'blob' is a - * nested structure. For each element of 'path': - * - If it's an LLSD::Integer, select the entry with that index from an - * LLSD::Array at that level. - * - If it's an LLSD::String, select the entry with that key from an - * LLSD::Map at that level. - * - Anything else is an error. - * - * By implication, if path.isUndefined() or otherwise equivalent to an empty - * LLSD::Array, drill[_ref]() returns 'blob' as is. - */ -LLSD drill(const LLSD& blob, const LLSD& path); -LLSD& drill_ref( LLSD& blob, const LLSD& path); - -} - -namespace llsd -{ - -/** - * Construct an LLSD::Array inline, using modern C++ variadic arguments. - */ - -// recursion tail -inline -void array_(LLSD&) {} - -// recursive call -template <typename T0, typename... Ts> -void array_(LLSD& data, T0&& v0, Ts&&... vs) -{ - data.append(std::forward<T0>(v0)); - array_(data, std::forward<Ts>(vs)...); -} - -// public interface -template <typename... Ts> -LLSD array(Ts&&... vs) -{ - LLSD data; - array_(data, std::forward<Ts>(vs)...); - return data; -} - -} // namespace llsd - -/***************************************************************************** -* LLSDMap -*****************************************************************************/ -/** - * Construct an LLSD::Map inline, with implicit conversion to LLSD. Usage: - * - * @code - * void somefunc(const LLSD&); - * ... - * somefunc(LLSDMap("alpha", "abc")("number", 17)("pi", 3.14)); - * @endcode - * - * For completeness, LLSDMap() with no args constructs an empty map, so - * <tt>LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14)</tt> produces a map - * equivalent to the above. But for most purposes, LLSD() is already - * equivalent to an empty map, and if you explicitly want an empty isMap(), - * there's LLSD::emptyMap(). However, supporting a no-args LLSDMap() - * constructor follows the principle of least astonishment. - */ -class LLSDMap -{ -public: - LLSDMap(): - _data(LLSD::emptyMap()) - {} - LLSDMap(const LLSD::String& key, const LLSD& value): - _data(LLSD::emptyMap()) - { - _data[key] = value; - } - - LLSDMap& operator()(const LLSD::String& key, const LLSD& value) - { - _data[key] = value; - return *this; - } - - operator LLSD() const { return _data; } - LLSD get() const { return _data; } - -private: - LLSD _data; -}; - -namespace llsd -{ - -/** - * Construct an LLSD::Map inline, using modern C++ variadic arguments. - */ - -// recursion tail -inline -void map_(LLSD&) {} - -// recursive call -template <typename T0, typename... Ts> -void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs) -{ - data[k0] = v0; - map_(data, std::forward<Ts>(vs)...); -} - -// public interface -template <typename... Ts> -LLSD map(Ts&&... vs) -{ - LLSD data; - map_(data, std::forward<Ts>(vs)...); - return data; -} - -} // namespace llsd - -/***************************************************************************** -* LLSDParam -*****************************************************************************/ -struct LLSDParamBase -{ - virtual ~LLSDParamBase() {} -}; - -/** - * LLSDParam is a customization point for passing LLSD values to function - * parameters of more or less arbitrary type. LLSD provides a small set of - * native conversions; but if a generic algorithm explicitly constructs an - * LLSDParam object in the function's argument list, a consumer can provide - * LLSDParam specializations to support more different parameter types than - * LLSD's native conversions. - * - * Usage: - * - * @code - * void somefunc(const paramtype&); - * ... - * somefunc(..., LLSDParam<paramtype>(someLLSD), ...); - * @endcode - */ -template <typename T> -class LLSDParam: public LLSDParamBase -{ -public: - /** - * Default implementation converts to T on construction, saves converted - * value for later retrieval - */ - LLSDParam(const LLSD& value): - value_(value) - {} - - operator T() const { return value_; } - -private: - T value_; -}; - -/** - * LLSDParam<LLSD> is for when you don't already have the target parameter - * type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the - * templated conversion operator will try to select a more specific LLSDParam - * specialization. - */ -template <> -class LLSDParam<LLSD>: public LLSDParamBase -{ -private: - LLSD value_; - // LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on - // demand. Returning that engages LLSDParam<T>::operator T(), producing - // the desired result. But LLSDParam<const char*> owns a std::string whose - // c_str() is returned by its operator const char*(). If we return a temp - // LLSDParam<const char*>, the compiler can destroy it right away, as soon - // as we've called operator const char*(). That's a problem! That - // invalidates the const char* we've just passed to the subject function. - // This LLSDParam<LLSD> is presumably guaranteed to survive until the - // subject function has returned, so we must ensure that any constructed - // LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting - // each LLSDParam<T> on the heap and capturing a smart pointer in a vector - // works. We would have liked to use std::unique_ptr, but vector entries - // must be copyable. - // (Alternatively we could assume that every instance of LLSDParam<LLSD> - // will be asked for at most ONE conversion. We could store a scalar - // std::unique_ptr and, when constructing an new LLSDParam<T>, assert that - // the unique_ptr is empty. But some future change in usage patterns, and - // consequent failure of that assertion, would be very mysterious. Instead - // of explaining how to fix it, just fix it now.) - mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_; - -public: - LLSDParam(const LLSD& value): value_(value) {} - - /// if we're literally being asked for an LLSD parameter, avoid infinite - /// recursion - operator LLSD() const { return value_; } - - /// otherwise, instantiate a more specific LLSDParam<T> to convert; that - /// preserves the existing customization mechanism - template <typename T> - operator T() const - { - // capture 'ptr' with the specific subclass type because converters_ - // only stores LLSDParamBase pointers - auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) }; - // keep the new converter alive until we ourselves are destroyed - converters_.push_back(ptr); - return *ptr; - } -}; - -/** - * Turns out that several target types could accept an LLSD param using any of - * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or - * std::string. Therefore, the compiler can't decide which LLSD conversion - * operator to choose, even though to us it seems obvious. But that's okay, we - * can specialize LLSDParam for such target types, explicitly specifying the - * desired conversion -- that's part of what LLSDParam is all about. Turns out - * we have to do that enough to make it worthwhile generalizing. Use a macro - * because I need to specify one of the asReal, etc., explicit conversion - * methods as well as a type. If I'm overlooking a clever way to implement - * that using a template instead, feel free to reimplement. - */ -#define LLSDParam_for(T, AS) \ -template <> \ -class LLSDParam<T>: public LLSDParamBase \ -{ \ -public: \ - LLSDParam(const LLSD& value): \ - value_((T)value.AS()) \ - {} \ - \ - operator T() const { return value_; } \ - \ -private: \ - T value_; \ -} - -LLSDParam_for(float, asReal); -LLSDParam_for(LLUUID, asUUID); -LLSDParam_for(LLDate, asDate); -LLSDParam_for(LLURI, asURI); -LLSDParam_for(LLSD::Binary, asBinary); - -/** - * LLSDParam<const char*> is an example of the kind of conversion you can - * support with LLSDParam beyond native LLSD conversions. Normally you can't - * pass an LLSD object to a function accepting const char* -- but you can - * safely pass an LLSDParam<const char*>(yourLLSD). - */ -template <> -class LLSDParam<const char*>: public LLSDParamBase -{ -private: - // The difference here is that we store a std::string rather than a const - // char*. It's important that the LLSDParam object own the std::string. - std::string value_; - // We don't bother storing the incoming LLSD object, but we do have to - // distinguish whether value_ is an empty string because the LLSD object - // contains an empty string or because it's isUndefined(). - bool undefined_; - -public: - LLSDParam(const LLSD& value): - value_(value), - undefined_(value.isUndefined()) - {} - - // The const char* we retrieve is for storage owned by our value_ member. - // That's how we guarantee that the const char* is valid for the lifetime - // of this LLSDParam object. Constructing your LLSDParam in the argument - // list should ensure that the LLSDParam object will persist for the - // duration of the function call. - operator const char*() const - { - if (undefined_) - { - // By default, an isUndefined() LLSD object's asString() method - // will produce an empty string. But for a function accepting - // const char*, it's often important to be able to pass NULL, and - // isUndefined() seems like the best way. If you want to pass an - // empty string, you can still pass LLSD(""). Without this special - // case, though, no LLSD value could pass NULL. - return NULL; - } - return value_.c_str(); - } -}; - -namespace llsd -{ - -/***************************************************************************** -* range-based for-loop helpers for LLSD -*****************************************************************************/ -/// Usage: for (LLSD item : inArray(someLLSDarray)) { ... } -class inArray -{ -public: - inArray(const LLSD& array): - _array(array) - {} - - typedef LLSD::array_const_iterator const_iterator; - typedef LLSD::array_iterator iterator; - - iterator begin() { return _array.beginArray(); } - iterator end() { return _array.endArray(); } - const_iterator begin() const { return _array.beginArray(); } - const_iterator end() const { return _array.endArray(); } - -private: - LLSD _array; -}; - -/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator. -typedef std::map<LLSD::String, LLSD>::value_type MapEntry; - -/// Usage: for([const] MapEntry& e : inMap(someLLSDmap)) { ... } -class inMap -{ -public: - inMap(const LLSD& map): - _map(map) - {} - - typedef LLSD::map_const_iterator const_iterator; - typedef LLSD::map_iterator iterator; - - iterator begin() { return _map.beginMap(); } - iterator end() { return _map.endMap(); } - const_iterator begin() const { return _map.beginMap(); } - const_iterator end() const { return _map.endMap(); } - -private: - LLSD _map; -}; - -} // namespace llsd - - -// Creates a deep clone of an LLSD object. Maps, Arrays and binary objects -// are duplicated, atomic primitives (Boolean, Integer, Real, etc) simply -// use a shared reference. -// Optionally a filter may be specified to control what is duplicated. The -// map takes the form "keyname/boolean". -// If the value is true the value will be duplicated otherwise it will be skipped -// when encountered in a map. A key name of "*" can be specified as a wild card -// and will specify the default behavior. If no wild card is given and the clone -// encounters a name not in the filter, that value will be skipped. -LLSD llsd_clone(LLSD value, LLSD filter = LLSD()); - -// Creates a shallow copy of a map or array. If passed any other type of LLSD -// object it simply returns that value. See llsd_clone for a description of -// the filter parameter. -LLSD llsd_shallow(LLSD value, LLSD filter = LLSD()); - -namespace llsd -{ - -// llsd namespace aliases -inline -LLSD clone (LLSD value, LLSD filter=LLSD()) { return llsd_clone (value, filter); } -inline -LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter); } - -} // namespace llsd - -// Specialization for generating a hash value from an LLSD block. -namespace boost -{ -template <> -struct hash<LLSD> -{ - typedef LLSD argument_type; - typedef std::size_t result_type; - result_type operator()(argument_type const& s) const - { - result_type seed(0); - - LLSD::Type stype = s.type(); - boost::hash_combine(seed, (S32)stype); - - switch (stype) - { - case LLSD::TypeBoolean: - boost::hash_combine(seed, s.asBoolean()); - break; - case LLSD::TypeInteger: - boost::hash_combine(seed, s.asInteger()); - break; - case LLSD::TypeReal: - boost::hash_combine(seed, s.asReal()); - break; - case LLSD::TypeURI: - case LLSD::TypeString: - boost::hash_combine(seed, s.asString()); - break; - case LLSD::TypeUUID: - boost::hash_combine(seed, s.asUUID()); - break; - case LLSD::TypeDate: - boost::hash_combine(seed, s.asDate().secondsSinceEpoch()); - break; - case LLSD::TypeBinary: - { - const LLSD::Binary &b(s.asBinary()); - boost::hash_range(seed, b.begin(), b.end()); - break; - } - case LLSD::TypeMap: - { - for (LLSD::map_const_iterator itm = s.beginMap(); itm != s.endMap(); ++itm) - { - boost::hash_combine(seed, (*itm).first); - boost::hash_combine(seed, (*itm).second); - } - break; - } - case LLSD::TypeArray: - for (LLSD::array_const_iterator ita = s.beginArray(); ita != s.endArray(); ++ita) - { - boost::hash_combine(seed, (*ita)); - } - break; - case LLSD::TypeUndefined: - default: - break; - } - - return seed; - } -}; -} - -namespace LL -{ - -/***************************************************************************** -* apply(function, LLSD array) -*****************************************************************************/ -// validate incoming LLSD blob, and return an LLSD array suitable to pass to -// the function of interest -LLSD apply_llsd_fix(size_t arity, const LLSD& args); - -// Derived from https://stackoverflow.com/a/20441189 -// and https://en.cppreference.com/w/cpp/utility/apply . -// We can't simply make a tuple from the LLSD array and then apply() that -// tuple to the function -- how would make_tuple() deduce the correct -// parameter type for each entry? We must go directly to the target function. -template <typename CALLABLE, std::size_t... I> -auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>) -{ - // call func(unpacked args), using generic LLSDParam<LLSD> to convert each - // entry in 'array' to the target parameter type - return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...); -} - -// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic -// function with (that many) items from the passed LLSD array -template <size_t ARITY, typename CALLABLE> -auto apply_n(CALLABLE&& func, const LLSD& args) -{ - return apply_impl(std::forward<CALLABLE>(func), - apply_llsd_fix(ARITY, args), - std::make_index_sequence<ARITY>()); -} - -/** - * apply(function, LLSD) goes beyond C++17 std::apply(). For this case - * @a function @emph cannot be variadic: the compiler must know at compile - * time how many arguments to pass. This isn't Python. (But see apply_n() to - * pass a specific number of args to a variadic function.) - */ -template <typename CALLABLE> -auto apply(CALLABLE&& func, const LLSD& args) -{ - // infer arity from the definition of func - constexpr auto arity = function_arity< - typename std::remove_reference<CALLABLE>::type>::value; - // now that we have a compile-time arity, apply_n() works - return apply_n<arity>(std::forward<CALLABLE>(func), args); -} - -} // namespace LL - -#endif // LL_LLSDUTIL_H +/**
+ * @file llsdutil.h
+ * @author Phoenix
+ * @date 2006-05-24
+ * @brief Utility classes, functions, etc, for using structured data.
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLSDUTIL_H
+#define LL_LLSDUTIL_H
+
+#include "apply.h" // LL::invoke()
+#include "function_types.h" // LL::function_arity
+#include "llsd.h"
+#include <boost/functional/hash.hpp>
+#include <cassert>
+#include <memory> // std::shared_ptr
+#include <type_traits>
+#include <vector>
+
+// U32
+LL_COMMON_API LLSD ll_sd_from_U32(const U32);
+LL_COMMON_API U32 ll_U32_from_sd(const LLSD& sd);
+
+// U64
+LL_COMMON_API LLSD ll_sd_from_U64(const U64);
+LL_COMMON_API U64 ll_U64_from_sd(const LLSD& sd);
+
+// IP Address
+LL_COMMON_API LLSD ll_sd_from_ipaddr(const U32);
+LL_COMMON_API U32 ll_ipaddr_from_sd(const LLSD& sd);
+
+// Binary to string
+LL_COMMON_API LLSD ll_string_from_binary(const LLSD& sd);
+
+//String to binary
+LL_COMMON_API LLSD ll_binary_from_string(const LLSD& sd);
+
+// Serializes sd to static buffer and returns pointer, useful for gdb debugging.
+LL_COMMON_API char* ll_print_sd(const LLSD& sd);
+
+// Serializes sd to static buffer and returns pointer, using "pretty printing" mode.
+LL_COMMON_API char* ll_pretty_print_sd_ptr(const LLSD* sd);
+LL_COMMON_API char* ll_pretty_print_sd(const LLSD& sd);
+
+LL_COMMON_API std::string ll_stream_notation_sd(const LLSD& sd);
+
+//compares the structure of an LLSD to a template LLSD and stores the
+//"valid" values in a 3rd LLSD. Default values
+//are pulled from the template. Extra keys/values in the test
+//are ignored in the resultant LLSD. Ordering of arrays matters
+//Returns false if the test is of same type but values differ in type
+//Otherwise, returns true
+
+LL_COMMON_API bool compare_llsd_with_template(
+ const LLSD& llsd_to_test,
+ const LLSD& template_llsd,
+ LLSD& resultant_llsd);
+
+// filter_llsd_with_template() is a direct clone (copy-n-paste) of
+// compare_llsd_with_template with the following differences:
+// (1) bool vs BOOL return types
+// (2) A map with the key value "*" is a special value and maps any key in the
+// test llsd that doesn't have an explicitly matching key in the template.
+// (3) The element of an array with exactly one element is taken as a template
+// for *all* the elements of the test array. If the template array is of
+// different size, compare_llsd_with_template() semantics apply.
+bool filter_llsd_with_template(
+ const LLSD & llsd_to_test,
+ const LLSD & template_llsd,
+ LLSD & resultant_llsd);
+
+/**
+ * Recursively determine whether a given LLSD data block "matches" another
+ * LLSD prototype. The returned string is empty() on success, non-empty() on
+ * mismatch.
+ *
+ * This function tests structure (types) rather than data values. It is
+ * intended for when a consumer expects an LLSD block with a particular
+ * structure, and must succinctly detect whether the arriving block is
+ * well-formed. For instance, a test of the form:
+ * @code
+ * if (! (data.has("request") && data.has("target") && data.has("modifier") ...))
+ * @endcode
+ * could instead be expressed by initializing a prototype LLSD map with the
+ * required keys and writing:
+ * @code
+ * if (! llsd_matches(prototype, data).empty())
+ * @endcode
+ *
+ * A non-empty return value is an error-message fragment intended to indicate
+ * to (English-speaking) developers where in the prototype structure the
+ * mismatch occurred.
+ *
+ * * If a slot in the prototype isUndefined(), then anything is valid at that
+ * place in the real object. (Passing prototype == LLSD() matches anything
+ * at all.)
+ * * An array in the prototype must match a data array at least that large.
+ * (Additional entries in the data array are ignored.) Every isDefined()
+ * entry in the prototype array must match the corresponding entry in the
+ * data array.
+ * * A map in the prototype must match a map in the data. Every key in the
+ * prototype map must match a corresponding key in the data map. (Additional
+ * keys in the data map are ignored.) Every isDefined() value in the
+ * prototype map must match the corresponding key's value in the data map.
+ * * Scalar values in the prototype are tested for @em type rather than value.
+ * For instance, a String in the prototype matches any String at all. In
+ * effect, storing an Integer at a particular place in the prototype asserts
+ * that the caller intends to apply asInteger() to the corresponding slot in
+ * the data.
+ * * A String in the prototype matches String, Boolean, Integer, Real, UUID,
+ * Date and URI, because asString() applied to any of these produces a
+ * meaningful result.
+ * * Similarly, a Boolean, Integer or Real in the prototype can match any of
+ * Boolean, Integer or Real in the data -- or even String.
+ * * UUID matches UUID or String.
+ * * Date matches Date or String.
+ * * URI matches URI or String.
+ * * Binary in the prototype matches only Binary in the data.
+ *
+ * @TODO: when a Boolean, Integer or Real in the prototype matches a String in
+ * the data, we should examine the String @em value to ensure it can be
+ * meaningfully converted to the requested type. The same goes for UUID, Date
+ * and URI.
+ */
+LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx="");
+
+/// Deep equality. If you want to compare LLSD::Real values for approximate
+/// equality rather than bitwise equality, pass @a bits as for
+/// is_approx_equal_fraction().
+LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits=-1);
+/// If you don't care about LLSD::Real equality
+inline bool operator==(const LLSD& lhs, const LLSD& rhs)
+{
+ return llsd_equals(lhs, rhs);
+}
+inline bool operator!=(const LLSD& lhs, const LLSD& rhs)
+{
+ // operator!=() should always be the negation of operator==()
+ return ! (lhs == rhs);
+}
+
+// Simple function to copy data out of input & output iterators if
+// there is no need for casting.
+template<typename Input> LLSD llsd_copy_array(Input iter, Input end)
+{
+ LLSD dest;
+ for (; iter != end; ++iter)
+ {
+ dest.append(*iter);
+ }
+ return dest;
+}
+
+namespace llsd
+{
+
+/**
+ * Drill down to locate an element in 'blob' according to 'path', where 'path'
+ * is one of the following:
+ *
+ * - LLSD::String: 'blob' is an LLSD::Map. Find the entry with key 'path'.
+ * - LLSD::Integer: 'blob' is an LLSD::Array. Find the entry with index 'path'.
+ * - Any other 'path' type will be interpreted as LLSD::Array, and 'blob' is a
+ * nested structure. For each element of 'path':
+ * - If it's an LLSD::Integer, select the entry with that index from an
+ * LLSD::Array at that level.
+ * - If it's an LLSD::String, select the entry with that key from an
+ * LLSD::Map at that level.
+ * - Anything else is an error.
+ *
+ * By implication, if path.isUndefined() or otherwise equivalent to an empty
+ * LLSD::Array, drill[_ref]() returns 'blob' as is.
+ */
+LLSD drill(const LLSD& blob, const LLSD& path);
+LLSD& drill_ref( LLSD& blob, const LLSD& path);
+
+}
+
+namespace llsd
+{
+
+/**
+ * Construct an LLSD::Array inline, using modern C++ variadic arguments.
+ */
+
+// recursion tail
+inline
+void array_(LLSD&) {}
+
+// recursive call
+template <typename T0, typename... Ts>
+void array_(LLSD& data, T0&& v0, Ts&&... vs)
+{
+ data.append(std::forward<T0>(v0));
+ array_(data, std::forward<Ts>(vs)...);
+}
+
+// public interface
+template <typename... Ts>
+LLSD array(Ts&&... vs)
+{
+ LLSD data;
+ array_(data, std::forward<Ts>(vs)...);
+ return data;
+}
+
+} // namespace llsd
+
+/*****************************************************************************
+* LLSDMap
+*****************************************************************************/
+/**
+ * Construct an LLSD::Map inline, with implicit conversion to LLSD. Usage:
+ *
+ * @code
+ * void somefunc(const LLSD&);
+ * ...
+ * somefunc(LLSDMap("alpha", "abc")("number", 17)("pi", 3.14));
+ * @endcode
+ *
+ * For completeness, LLSDMap() with no args constructs an empty map, so
+ * <tt>LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14)</tt> produces a map
+ * equivalent to the above. But for most purposes, LLSD() is already
+ * equivalent to an empty map, and if you explicitly want an empty isMap(),
+ * there's LLSD::emptyMap(). However, supporting a no-args LLSDMap()
+ * constructor follows the principle of least astonishment.
+ */
+class LLSDMap
+{
+public:
+ LLSDMap():
+ _data(LLSD::emptyMap())
+ {}
+ LLSDMap(const LLSD::String& key, const LLSD& value):
+ _data(LLSD::emptyMap())
+ {
+ _data[key] = value;
+ }
+
+ LLSDMap& operator()(const LLSD::String& key, const LLSD& value)
+ {
+ _data[key] = value;
+ return *this;
+ }
+
+ operator LLSD() const { return _data; }
+ LLSD get() const { return _data; }
+
+private:
+ LLSD _data;
+};
+
+namespace llsd
+{
+
+/**
+ * Construct an LLSD::Map inline, using modern C++ variadic arguments.
+ */
+
+// recursion tail
+inline
+void map_(LLSD&) {}
+
+// recursive call
+template <typename T0, typename... Ts>
+void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs)
+{
+ data[k0] = v0;
+ map_(data, std::forward<Ts>(vs)...);
+}
+
+// public interface
+template <typename... Ts>
+LLSD map(Ts&&... vs)
+{
+ LLSD data;
+ map_(data, std::forward<Ts>(vs)...);
+ return data;
+}
+
+} // namespace llsd
+
+/*****************************************************************************
+* LLSDParam
+*****************************************************************************/
+struct LLSDParamBase
+{
+ virtual ~LLSDParamBase() {}
+};
+
+/**
+ * LLSDParam is a customization point for passing LLSD values to function
+ * parameters of more or less arbitrary type. LLSD provides a small set of
+ * native conversions; but if a generic algorithm explicitly constructs an
+ * LLSDParam object in the function's argument list, a consumer can provide
+ * LLSDParam specializations to support more different parameter types than
+ * LLSD's native conversions.
+ *
+ * Usage:
+ *
+ * @code
+ * void somefunc(const paramtype&);
+ * ...
+ * somefunc(..., LLSDParam<paramtype>(someLLSD), ...);
+ * @endcode
+ */
+template <typename T>
+class LLSDParam: public LLSDParamBase
+{
+public:
+ /**
+ * Default implementation converts to T on construction, saves converted
+ * value for later retrieval
+ */
+ LLSDParam(const LLSD& value):
+ value_(value)
+ {}
+
+ operator T() const { return value_; }
+
+private:
+ T value_;
+};
+
+/**
+ * LLSDParam<LLSD> is for when you don't already have the target parameter
+ * type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the
+ * templated conversion operator will try to select a more specific LLSDParam
+ * specialization.
+ */
+template <>
+class LLSDParam<LLSD>: public LLSDParamBase
+{
+private:
+ LLSD value_;
+ // LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on
+ // demand. Returning that engages LLSDParam<T>::operator T(), producing
+ // the desired result. But LLSDParam<const char*> owns a std::string whose
+ // c_str() is returned by its operator const char*(). If we return a temp
+ // LLSDParam<const char*>, the compiler can destroy it right away, as soon
+ // as we've called operator const char*(). That's a problem! That
+ // invalidates the const char* we've just passed to the subject function.
+ // This LLSDParam<LLSD> is presumably guaranteed to survive until the
+ // subject function has returned, so we must ensure that any constructed
+ // LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting
+ // each LLSDParam<T> on the heap and capturing a smart pointer in a vector
+ // works. We would have liked to use std::unique_ptr, but vector entries
+ // must be copyable.
+ // (Alternatively we could assume that every instance of LLSDParam<LLSD>
+ // will be asked for at most ONE conversion. We could store a scalar
+ // std::unique_ptr and, when constructing an new LLSDParam<T>, assert that
+ // the unique_ptr is empty. But some future change in usage patterns, and
+ // consequent failure of that assertion, would be very mysterious. Instead
+ // of explaining how to fix it, just fix it now.)
+ mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_;
+
+public:
+ LLSDParam(const LLSD& value): value_(value) {}
+
+ /// if we're literally being asked for an LLSD parameter, avoid infinite
+ /// recursion
+ operator LLSD() const { return value_; }
+
+ /// otherwise, instantiate a more specific LLSDParam<T> to convert; that
+ /// preserves the existing customization mechanism
+ template <typename T>
+ operator T() const
+ {
+ // capture 'ptr' with the specific subclass type because converters_
+ // only stores LLSDParamBase pointers
+ auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) };
+ // keep the new converter alive until we ourselves are destroyed
+ converters_.push_back(ptr);
+ return *ptr;
+ }
+};
+
+/**
+ * Turns out that several target types could accept an LLSD param using any of
+ * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or
+ * std::string. Therefore, the compiler can't decide which LLSD conversion
+ * operator to choose, even though to us it seems obvious. But that's okay, we
+ * can specialize LLSDParam for such target types, explicitly specifying the
+ * desired conversion -- that's part of what LLSDParam is all about. Turns out
+ * we have to do that enough to make it worthwhile generalizing. Use a macro
+ * because I need to specify one of the asReal, etc., explicit conversion
+ * methods as well as a type. If I'm overlooking a clever way to implement
+ * that using a template instead, feel free to reimplement.
+ */
+#define LLSDParam_for(T, AS) \
+template <> \
+class LLSDParam<T>: public LLSDParamBase \
+{ \
+public: \
+ LLSDParam(const LLSD& value): \
+ value_((T)value.AS()) \
+ {} \
+ \
+ operator T() const { return value_; } \
+ \
+private: \
+ T value_; \
+}
+
+LLSDParam_for(float, asReal);
+LLSDParam_for(LLUUID, asUUID);
+LLSDParam_for(LLDate, asDate);
+LLSDParam_for(LLURI, asURI);
+LLSDParam_for(LLSD::Binary, asBinary);
+
+/**
+ * LLSDParam<const char*> is an example of the kind of conversion you can
+ * support with LLSDParam beyond native LLSD conversions. Normally you can't
+ * pass an LLSD object to a function accepting const char* -- but you can
+ * safely pass an LLSDParam<const char*>(yourLLSD).
+ */
+template <>
+class LLSDParam<const char*>: public LLSDParamBase
+{
+private:
+ // The difference here is that we store a std::string rather than a const
+ // char*. It's important that the LLSDParam object own the std::string.
+ std::string value_;
+ // We don't bother storing the incoming LLSD object, but we do have to
+ // distinguish whether value_ is an empty string because the LLSD object
+ // contains an empty string or because it's isUndefined().
+ bool undefined_;
+
+public:
+ LLSDParam(const LLSD& value):
+ value_(value),
+ undefined_(value.isUndefined())
+ {}
+
+ // The const char* we retrieve is for storage owned by our value_ member.
+ // That's how we guarantee that the const char* is valid for the lifetime
+ // of this LLSDParam object. Constructing your LLSDParam in the argument
+ // list should ensure that the LLSDParam object will persist for the
+ // duration of the function call.
+ operator const char*() const
+ {
+ if (undefined_)
+ {
+ // By default, an isUndefined() LLSD object's asString() method
+ // will produce an empty string. But for a function accepting
+ // const char*, it's often important to be able to pass NULL, and
+ // isUndefined() seems like the best way. If you want to pass an
+ // empty string, you can still pass LLSD(""). Without this special
+ // case, though, no LLSD value could pass NULL.
+ return NULL;
+ }
+ return value_.c_str();
+ }
+};
+
+namespace llsd
+{
+
+/*****************************************************************************
+* range-based for-loop helpers for LLSD
+*****************************************************************************/
+/// Usage: for (LLSD item : inArray(someLLSDarray)) { ... }
+class inArray
+{
+public:
+ inArray(const LLSD& array):
+ _array(array)
+ {}
+
+ typedef LLSD::array_const_iterator const_iterator;
+ typedef LLSD::array_iterator iterator;
+
+ iterator begin() { return _array.beginArray(); }
+ iterator end() { return _array.endArray(); }
+ const_iterator begin() const { return _array.beginArray(); }
+ const_iterator end() const { return _array.endArray(); }
+
+private:
+ LLSD _array;
+};
+
+/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator.
+typedef std::map<LLSD::String, LLSD>::value_type MapEntry;
+
+/// Usage: for([const] MapEntry& e : inMap(someLLSDmap)) { ... }
+class inMap
+{
+public:
+ inMap(const LLSD& map):
+ _map(map)
+ {}
+
+ typedef LLSD::map_const_iterator const_iterator;
+ typedef LLSD::map_iterator iterator;
+
+ iterator begin() { return _map.beginMap(); }
+ iterator end() { return _map.endMap(); }
+ const_iterator begin() const { return _map.beginMap(); }
+ const_iterator end() const { return _map.endMap(); }
+
+private:
+ LLSD _map;
+};
+
+} // namespace llsd
+
+
+// Creates a deep clone of an LLSD object. Maps, Arrays and binary objects
+// are duplicated, atomic primitives (Boolean, Integer, Real, etc) simply
+// use a shared reference.
+// Optionally a filter may be specified to control what is duplicated. The
+// map takes the form "keyname/boolean".
+// If the value is true the value will be duplicated otherwise it will be skipped
+// when encountered in a map. A key name of "*" can be specified as a wild card
+// and will specify the default behavior. If no wild card is given and the clone
+// encounters a name not in the filter, that value will be skipped.
+LLSD llsd_clone(LLSD value, LLSD filter = LLSD());
+
+// Creates a shallow copy of a map or array. If passed any other type of LLSD
+// object it simply returns that value. See llsd_clone for a description of
+// the filter parameter.
+LLSD llsd_shallow(LLSD value, LLSD filter = LLSD());
+
+namespace llsd
+{
+
+// llsd namespace aliases
+inline
+LLSD clone (LLSD value, LLSD filter=LLSD()) { return llsd_clone (value, filter); }
+inline
+LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter); }
+
+} // namespace llsd
+
+// Specialization for generating a hash value from an LLSD block.
+namespace boost
+{
+template <>
+struct hash<LLSD>
+{
+ typedef LLSD argument_type;
+ typedef std::size_t result_type;
+ result_type operator()(argument_type const& s) const
+ {
+ result_type seed(0);
+
+ LLSD::Type stype = s.type();
+ boost::hash_combine(seed, (S32)stype);
+
+ switch (stype)
+ {
+ case LLSD::TypeBoolean:
+ boost::hash_combine(seed, s.asBoolean());
+ break;
+ case LLSD::TypeInteger:
+ boost::hash_combine(seed, s.asInteger());
+ break;
+ case LLSD::TypeReal:
+ boost::hash_combine(seed, s.asReal());
+ break;
+ case LLSD::TypeURI:
+ case LLSD::TypeString:
+ boost::hash_combine(seed, s.asString());
+ break;
+ case LLSD::TypeUUID:
+ boost::hash_combine(seed, s.asUUID());
+ break;
+ case LLSD::TypeDate:
+ boost::hash_combine(seed, s.asDate().secondsSinceEpoch());
+ break;
+ case LLSD::TypeBinary:
+ {
+ const LLSD::Binary &b(s.asBinary());
+ boost::hash_range(seed, b.begin(), b.end());
+ break;
+ }
+ case LLSD::TypeMap:
+ {
+ for (LLSD::map_const_iterator itm = s.beginMap(); itm != s.endMap(); ++itm)
+ {
+ boost::hash_combine(seed, (*itm).first);
+ boost::hash_combine(seed, (*itm).second);
+ }
+ break;
+ }
+ case LLSD::TypeArray:
+ for (LLSD::array_const_iterator ita = s.beginArray(); ita != s.endArray(); ++ita)
+ {
+ boost::hash_combine(seed, (*ita));
+ }
+ break;
+ case LLSD::TypeUndefined:
+ default:
+ break;
+ }
+
+ return seed;
+ }
+};
+}
+
+namespace LL
+{
+
+/*****************************************************************************
+* apply(function, LLSD array)
+*****************************************************************************/
+// validate incoming LLSD blob, and return an LLSD array suitable to pass to
+// the function of interest
+LLSD apply_llsd_fix(size_t arity, const LLSD& args);
+
+// Derived from https://stackoverflow.com/a/20441189
+// and https://en.cppreference.com/w/cpp/utility/apply .
+// We can't simply make a tuple from the LLSD array and then apply() that
+// tuple to the function -- how would make_tuple() deduce the correct
+// parameter type for each entry? We must go directly to the target function.
+template <typename CALLABLE, std::size_t... I>
+auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
+{
+ // call func(unpacked args), using generic LLSDParam<LLSD> to convert each
+ // entry in 'array' to the target parameter type
+ return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
+}
+
+// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic
+// function with (that many) items from the passed LLSD array
+template <size_t ARITY, typename CALLABLE>
+auto apply_n(CALLABLE&& func, const LLSD& args)
+{
+ return apply_impl(std::forward<CALLABLE>(func),
+ apply_llsd_fix(ARITY, args),
+ std::make_index_sequence<ARITY>());
+}
+
+/**
+ * apply(function, LLSD) goes beyond C++17 std::apply(). For this case
+ * @a function @emph cannot be variadic: the compiler must know at compile
+ * time how many arguments to pass. This isn't Python. (But see apply_n() to
+ * pass a specific number of args to a variadic function.)
+ */
+template <typename CALLABLE>
+auto apply(CALLABLE&& func, const LLSD& args)
+{
+ // infer arity from the definition of func
+ constexpr auto arity = function_arity<
+ typename std::remove_reference<CALLABLE>::type>::value;
+ // now that we have a compile-time arity, apply_n() works
+ return apply_n<arity>(std::forward<CALLABLE>(func), args);
+}
+
+} // namespace LL
+
+#endif // LL_LLSDUTIL_H
diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index 91c05bd5ed..61f82a4c03 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -1,24 +1,24 @@ -/** +/** * @file llsingleton.h * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, 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$ */ @@ -807,17 +807,6 @@ private: \ DERIVED_CLASS(__VA_ARGS__) /** - * A slight variance from the above, but includes the "override" keyword - */ -#define LLSINGLETON_C11(DERIVED_CLASS) \ -private: \ - /* implement LLSingleton pure virtual method whose sole purpose */ \ - /* is to remind people to use this macro */ \ - virtual void you_must_use_LLSINGLETON_macro() override {} \ - friend class LLSingleton<DERIVED_CLASS>; \ - DERIVED_CLASS() - -/** * Use LLSINGLETON_EMPTY_CTOR(Foo); at the start of an LLSingleton<Foo> * subclass body when the constructor is trivial: * @@ -835,13 +824,9 @@ private: \ /* LLSINGLETON() is carefully implemented to permit exactly this */ \ LLSINGLETON(DERIVED_CLASS) {} -#define LLSINGLETON_EMPTY_CTOR_C11(DERIVED_CLASS) \ - /* LLSINGLETON() is carefully implemented to permit exactly this */ \ - LLSINGLETON_C11(DERIVED_CLASS) {} - // Relatively unsafe singleton implementation that is much faster // and simpler than LLSingleton, but has no dependency tracking -// or inherent thread safety and requires manual invocation of +// or inherent thread safety and requires manual invocation of // createInstance before first use. template<class T> class LLSimpleton diff --git a/indra/llcommon/llstacktrace.cpp b/indra/llcommon/llstacktrace.cpp index 1fe7f0f25f..05e71b8203 100644 --- a/indra/llcommon/llstacktrace.cpp +++ b/indra/llcommon/llstacktrace.cpp @@ -1,168 +1,168 @@ -/** - * @file llstacktrace.cpp - * @brief stack tracing functionality - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" -#include "llstacktrace.h" - -#ifdef LL_WINDOWS - -#include <iostream> -#include <sstream> - -#include "llwin32headerslean.h" -#pragma warning (push) -#pragma warning (disable:4091) // a microsoft header has warnings. Very nice. -#include <dbghelp.h> -#pragma warning (pop) - -typedef USHORT NTAPI RtlCaptureStackBackTrace_Function( - IN ULONG frames_to_skip, - IN ULONG frames_to_capture, - OUT PVOID *backtrace, - OUT PULONG backtrace_hash); - -static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn = - (RtlCaptureStackBackTrace_Function*) - GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCaptureStackBackTrace"); - -bool ll_get_stack_trace(std::vector<std::string>& lines) -{ - const S32 MAX_STACK_DEPTH = 32; - const S32 STRING_NAME_LENGTH = 200; - const S32 FRAME_SKIP = 2; - static BOOL symbolsLoaded = false; - static BOOL firstCall = true; - - HANDLE hProc = GetCurrentProcess(); - - // load the symbols if they're not loaded - if(!symbolsLoaded && firstCall) - { - symbolsLoaded = SymInitialize(hProc, NULL, true); - firstCall = false; - } - - // if loaded, get the call stack - if(symbolsLoaded) - { - // create the frames to hold the addresses - void* frames[MAX_STACK_DEPTH]; - memset(frames, 0, sizeof(void*)*MAX_STACK_DEPTH); - S32 depth = 0; - - // get the addresses - depth = RtlCaptureStackBackTrace_fn(FRAME_SKIP, MAX_STACK_DEPTH, frames, NULL); - - IMAGEHLP_LINE64 line; - memset(&line, 0, sizeof(IMAGEHLP_LINE64)); - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); - - // create something to hold address info - PIMAGEHLP_SYMBOL64 pSym; - pSym = (PIMAGEHLP_SYMBOL64)malloc(sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH); - memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH); - pSym->MaxNameLength = STRING_NAME_LENGTH; - pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); - - // get address info for each address frame - // and store - for(S32 i=0; i < depth; i++) - { - std::stringstream stack_line; - BOOL ret; - - DWORD64 addr = (DWORD64)frames[i]; - ret = SymGetSymFromAddr64(hProc, addr, 0, pSym); - if(ret) - { - stack_line << pSym->Name << " "; - } - - DWORD dummy; - ret = SymGetLineFromAddr64(hProc, addr, &dummy, &line); - if(ret) - { - std::string file_name = line.FileName; - std::string::size_type index = file_name.rfind("\\"); - stack_line << file_name.substr(index + 1, file_name.size()) << ":" << line.LineNumber; - } - - lines.push_back(stack_line.str()); - } - - free(pSym); - - // TODO: figure out a way to cleanup symbol loading - // Not hugely necessary, however. - //SymCleanup(hProc); - return true; - } - else - { - lines.push_back("Stack Trace Failed. PDB symbol info not loaded"); - } - - return false; -} - -void ll_get_stack_trace_internal(std::vector<std::string>& lines) -{ - const S32 MAX_STACK_DEPTH = 100; - const S32 STRING_NAME_LENGTH = 256; - - HANDLE process = GetCurrentProcess(); - SymInitialize( process, NULL, TRUE ); - - void *stack[MAX_STACK_DEPTH]; - - unsigned short frames = RtlCaptureStackBackTrace_fn( 0, MAX_STACK_DEPTH, stack, NULL ); - SYMBOL_INFO *symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + STRING_NAME_LENGTH * sizeof(char), 1); - symbol->MaxNameLen = STRING_NAME_LENGTH-1; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - - for(unsigned int i = 0; i < frames; i++) - { - SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol); - lines.push_back(symbol->Name); - } - - free( symbol ); -} - -#else - -bool ll_get_stack_trace(std::vector<std::string>& lines) -{ - return false; -} - -void ll_get_stack_trace_internal(std::vector<std::string>& lines) -{ - -} - -#endif - +/**
+ * @file llstacktrace.cpp
+ * @brief stack tracing functionality
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+#include "llstacktrace.h"
+
+#ifdef LL_WINDOWS
+
+#include <iostream>
+#include <sstream>
+
+#include "llwin32headerslean.h"
+#pragma warning (push)
+#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
+#include <dbghelp.h>
+#pragma warning (pop)
+
+typedef USHORT NTAPI RtlCaptureStackBackTrace_Function(
+ IN ULONG frames_to_skip,
+ IN ULONG frames_to_capture,
+ OUT PVOID *backtrace,
+ OUT PULONG backtrace_hash);
+
+static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn =
+ (RtlCaptureStackBackTrace_Function*)
+ GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCaptureStackBackTrace");
+
+bool ll_get_stack_trace(std::vector<std::string>& lines)
+{
+ const S32 MAX_STACK_DEPTH = 32;
+ const S32 STRING_NAME_LENGTH = 200;
+ const S32 FRAME_SKIP = 2;
+ static bool symbolsLoaded = false;
+ static bool firstCall = true;
+
+ HANDLE hProc = GetCurrentProcess();
+
+ // load the symbols if they're not loaded
+ if(!symbolsLoaded && firstCall)
+ {
+ symbolsLoaded = SymInitialize(hProc, NULL, true);
+ firstCall = false;
+ }
+
+ // if loaded, get the call stack
+ if(symbolsLoaded)
+ {
+ // create the frames to hold the addresses
+ void* frames[MAX_STACK_DEPTH];
+ memset(frames, 0, sizeof(void*)*MAX_STACK_DEPTH);
+ S32 depth = 0;
+
+ // get the addresses
+ depth = RtlCaptureStackBackTrace_fn(FRAME_SKIP, MAX_STACK_DEPTH, frames, NULL);
+
+ IMAGEHLP_LINE64 line;
+ memset(&line, 0, sizeof(IMAGEHLP_LINE64));
+ line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+
+ // create something to hold address info
+ PIMAGEHLP_SYMBOL64 pSym;
+ pSym = (PIMAGEHLP_SYMBOL64)malloc(sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH);
+ memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH);
+ pSym->MaxNameLength = STRING_NAME_LENGTH;
+ pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
+
+ // get address info for each address frame
+ // and store
+ for(S32 i=0; i < depth; i++)
+ {
+ std::stringstream stack_line;
+ bool ret;
+
+ DWORD64 addr = (DWORD64)frames[i];
+ ret = SymGetSymFromAddr64(hProc, addr, 0, pSym);
+ if(ret)
+ {
+ stack_line << pSym->Name << " ";
+ }
+
+ DWORD dummy;
+ ret = SymGetLineFromAddr64(hProc, addr, &dummy, &line);
+ if(ret)
+ {
+ std::string file_name = line.FileName;
+ std::string::size_type index = file_name.rfind("\\");
+ stack_line << file_name.substr(index + 1, file_name.size()) << ":" << line.LineNumber;
+ }
+
+ lines.push_back(stack_line.str());
+ }
+
+ free(pSym);
+
+ // TODO: figure out a way to cleanup symbol loading
+ // Not hugely necessary, however.
+ //SymCleanup(hProc);
+ return true;
+ }
+ else
+ {
+ lines.push_back("Stack Trace Failed. PDB symbol info not loaded");
+ }
+
+ return false;
+}
+
+void ll_get_stack_trace_internal(std::vector<std::string>& lines)
+{
+ const S32 MAX_STACK_DEPTH = 100;
+ const S32 STRING_NAME_LENGTH = 256;
+
+ HANDLE process = GetCurrentProcess();
+ SymInitialize( process, NULL, true );
+
+ void *stack[MAX_STACK_DEPTH];
+
+ unsigned short frames = RtlCaptureStackBackTrace_fn( 0, MAX_STACK_DEPTH, stack, NULL );
+ SYMBOL_INFO *symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + STRING_NAME_LENGTH * sizeof(char), 1);
+ symbol->MaxNameLen = STRING_NAME_LENGTH-1;
+ symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
+
+ for(unsigned int i = 0; i < frames; i++)
+ {
+ SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
+ lines.push_back(symbol->Name);
+ }
+
+ free( symbol );
+}
+
+#else
+
+bool ll_get_stack_trace(std::vector<std::string>& lines)
+{
+ return false;
+}
+
+void ll_get_stack_trace_internal(std::vector<std::string>& lines)
+{
+
+}
+
+#endif
+
diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index 3a6efd7d34..f2d268bb9a 100644 --- a/indra/llcommon/llstl.h +++ b/indra/llcommon/llstl.h @@ -1,713 +1,713 @@ -/** - * @file llstl.h - * @brief helper object & functions for use with the stl. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLSTL_H -#define LL_LLSTL_H - -#include "stdtypes.h" -#include <functional> -#include <algorithm> -#include <map> -#include <vector> -#include <list> -#include <set> -#include <typeinfo> - -#ifdef LL_LINUX -// <ND> For strcmp -#include <string.h> -#endif -// Use to compare the first element only of a pair -// e.g. typedef std::set<std::pair<int, Data*>, compare_pair<int, Data*> > some_pair_set_t; -template <typename T1, typename T2> -struct compare_pair_first -{ - bool operator()(const std::pair<T1, T2>& a, const std::pair<T1, T2>& b) const - { - return a.first < b.first; - } -}; - -template <typename T1, typename T2> -struct compare_pair_greater -{ - bool operator()(const std::pair<T1, T2>& a, const std::pair<T1, T2>& b) const - { - if (!(a.first < b.first)) - return true; - else if (!(b.first < a.first)) - return false; - else - return !(a.second < b.second); - } -}; - -// Use to compare the contents of two pointers (e.g. std::string*) -template <typename T> -struct compare_pointer_contents -{ - typedef const T* Tptr; - bool operator()(const Tptr& a, const Tptr& b) const - { - return *a < *b; - } -}; - -// DeletePointer is a simple helper for deleting all pointers in a container. -// The general form is: -// -// std::for_each(cont.begin(), cont.end(), DeletePointer()); -// somemap.clear(); -// -// Don't forget to clear()! - -struct DeletePointer -{ - template<typename T> void operator()(T* ptr) const - { - delete ptr; - } -}; -struct DeletePointerArray -{ - template<typename T> void operator()(T* ptr) const - { - delete[] ptr; - } -}; - -// DeletePairedPointer is a simple helper for deleting all pointers in a map. -// The general form is: -// -// std::for_each(somemap.begin(), somemap.end(), DeletePairedPointer()); -// somemap.clear(); // Don't leave dangling pointers around - -struct DeletePairedPointer -{ - template<typename T> void operator()(T &ptr) const - { - delete ptr.second; - ptr.second = NULL; - } -}; -struct DeletePairedPointerArray -{ - template<typename T> void operator()(T &ptr) const - { - delete[] ptr.second; - ptr.second = NULL; - } -}; - - -// Alternate version of the above so that has a more cumbersome -// syntax, but it can be used with compositional functors. -// NOTE: The functor retuns a bool because msdev bombs during the -// composition if you return void. Once we upgrade to a newer -// compiler, the second unary_function template parameter can be set -// to void. -// -// Here's a snippet showing how you use this object: -// -// typedef std::map<int, widget*> map_type; -// map_type widget_map; -// ... // add elements -// // delete them all -// for_each(widget_map.begin(), -// widget_map.end(), -// llcompose1(DeletePointerFunctor<widget>(), -// llselect2nd<map_type::value_type>())); - -template<typename T> -struct DeletePointerFunctor -{ - bool operator()(T* ptr) const - { - delete ptr; - return true; - } -}; - -// See notes about DeleteArray for why you should consider avoiding this. -template<typename T> -struct DeleteArrayFunctor -{ - bool operator()(T* ptr) const - { - delete[] ptr; - return true; - } -}; - -// CopyNewPointer is a simple helper which accepts a pointer, and -// returns a new pointer built with the copy constructor. Example: -// -// transform(in.begin(), in.end(), out.end(), CopyNewPointer()); - -struct CopyNewPointer -{ - template<typename T> T* operator()(const T* ptr) const - { - return new T(*ptr); - } -}; - -template<typename T, typename ALLOC> -void delete_and_clear(std::list<T*, ALLOC>& list) -{ - std::for_each(list.begin(), list.end(), DeletePointer()); - list.clear(); -} - -template<typename T, typename ALLOC> -void delete_and_clear(std::vector<T*, ALLOC>& vector) -{ - std::for_each(vector.begin(), vector.end(), DeletePointer()); - vector.clear(); -} - -template<typename T, typename COMPARE, typename ALLOC> -void delete_and_clear(std::set<T*, COMPARE, ALLOC>& set) -{ - std::for_each(set.begin(), set.end(), DeletePointer()); - set.clear(); -} - -template<typename K, typename V, typename COMPARE, typename ALLOC> -void delete_and_clear(std::map<K, V*, COMPARE, ALLOC>& map) -{ - std::for_each(map.begin(), map.end(), DeletePairedPointer()); - map.clear(); -} - -template<typename T> -void delete_and_clear(T*& ptr) -{ - delete ptr; - ptr = NULL; -} - - -template<typename T> -void delete_and_clear_array(T*& ptr) -{ - delete[] ptr; - ptr = NULL; -} - -// Simple function to help with finding pointers in maps. -// For example: -// typedef map_t; -// std::map<int, const char*> foo; -// foo[18] = "there"; -// foo[2] = "hello"; -// const char* bar = get_ptr_in_map(foo, 2); // bar -> "hello" -// const char* baz = get_ptr_in_map(foo, 3); // baz == NULL -template <typename K, typename T> -inline T* get_ptr_in_map(const std::map<K,T*>& inmap, const K& key) -{ - // Typedef here avoids warnings because of new c++ naming rules. - typedef typename std::map<K,T*>::const_iterator map_iter; - map_iter iter = inmap.find(key); - if(iter == inmap.end()) - { - return NULL; - } - else - { - return iter->second; - } -}; - -// helper function which returns true if key is in inmap. -template <typename K, typename T> -inline bool is_in_map(const std::map<K,T>& inmap, const K& key) -{ - if(inmap.find(key) == inmap.end()) - { - return false; - } - else - { - return true; - } -} - -// Similar to get_ptr_in_map, but for any type with a valid T(0) constructor. -// To replace LLSkipMap getIfThere, use: -// get_if_there(map, key, 0) -// WARNING: Make sure default_value (generally 0) is not a valid map entry! -template <typename K, typename T> -inline T get_if_there(const std::map<K,T>& inmap, const K& key, T default_value) -{ - // Typedef here avoids warnings because of new c++ naming rules. - typedef typename std::map<K,T>::const_iterator map_iter; - map_iter iter = inmap.find(key); - if(iter == inmap.end()) - { - return default_value; - } - else - { - return iter->second; - } -}; - -// Useful for replacing the removeObj() functionality of LLDynamicArray -// Example: -// for (std::vector<T>::iterator iter = mList.begin(); iter != mList.end(); ) -// { -// if ((*iter)->isMarkedForRemoval()) -// iter = vector_replace_with_last(mList, iter); -// else -// ++iter; -// } -template <typename T> -inline typename std::vector<T>::iterator vector_replace_with_last(std::vector<T>& invec, typename std::vector<T>::iterator iter) -{ - typename std::vector<T>::iterator last = invec.end(); --last; - if (iter == invec.end()) - { - return iter; - } - else if (iter == last) - { - invec.pop_back(); - return invec.end(); - } - else - { - *iter = *last; - invec.pop_back(); - return iter; - } -}; - -// Example: -// vector_replace_with_last(mList, x); -template <typename T> -inline bool vector_replace_with_last(std::vector<T>& invec, const T& val) -{ - typename std::vector<T>::iterator iter = std::find(invec.begin(), invec.end(), val); - if (iter != invec.end()) - { - typename std::vector<T>::iterator last = invec.end(); --last; - *iter = *last; - invec.pop_back(); - return true; - } - return false; -} - -// Append N elements to the vector and return a pointer to the first new element. -template <typename T> -inline T* vector_append(std::vector<T>& invec, S32 N) -{ - U32 sz = invec.size(); - invec.resize(sz+N); - return &(invec[sz]); -} - -// call function f to n members starting at first. similar to std::for_each -template <class InputIter, class Size, class Function> -Function ll_for_n(InputIter first, Size n, Function f) -{ - for ( ; n > 0; --n, ++first) - f(*first); - return f; -} - -// copy first to result n times, incrementing each as we go -template <class InputIter, class Size, class OutputIter> -OutputIter ll_copy_n(InputIter first, Size n, OutputIter result) -{ - for ( ; n > 0; --n, ++result, ++first) - *result = *first; - return result; -} - -// set *result = op(*f) for n elements of f -template <class InputIter, class OutputIter, class Size, class UnaryOp> -OutputIter ll_transform_n( - InputIter first, - Size n, - OutputIter result, - UnaryOp op) -{ - for ( ; n > 0; --n, ++result, ++first) - *result = op(*first); - return result; -} - - - -/* - * - * Copyright (c) 1994 - * Hewlett-Packard Company - * - * Permission to use, copy, modify, distribute and sell this software - * and its documentation for any purpose is hereby granted without fee, - * provided that the above copyright notice appear in all copies and - * that both that copyright notice and this permission notice appear - * in supporting documentation. Hewlett-Packard Company makes no - * representations about the suitability of this software for any - * purpose. It is provided "as is" without express or implied warranty. - * - * - * Copyright (c) 1996-1998 - * Silicon Graphics Computer Systems, Inc. - * - * Permission to use, copy, modify, distribute and sell this software - * and its documentation for any purpose is hereby granted without fee, - * provided that the above copyright notice appear in all copies and - * that both that copyright notice and this permission notice appear - * in supporting documentation. Silicon Graphics makes no - * representations about the suitability of this software for any - * purpose. It is provided "as is" without express or implied warranty. - */ - - -// helper to deal with the fact that MSDev does not package -// select... with the stl. Look up usage on the sgi website. - -template <class _Pair> -struct _LLSelect1st -{ - const auto& operator()(const _Pair& __x) const { - return __x.first; - } -}; - -template <class _Pair> -struct _LLSelect2nd -{ - const auto& operator()(const _Pair& __x) const { - return __x.second; - } -}; - -template <class _Pair> struct llselect1st : public _LLSelect1st<_Pair> {}; -template <class _Pair> struct llselect2nd : public _LLSelect2nd<_Pair> {}; - -// helper to deal with the fact that MSDev does not package -// compose... with the stl. Look up usage on the sgi website. - -template <class _Operation1, class _Operation2> -class ll_unary_compose -{ -protected: - _Operation1 __op1; - _Operation2 __op2; -public: - ll_unary_compose(const _Operation1& __x, const _Operation2& __y) - : __op1(__x), __op2(__y) {} - template <typename _Op2Arg> - auto - operator()(const _Op2Arg& __x) const { - return __op1(__op2(__x)); - } -}; - -template <class _Operation1, class _Operation2> -inline ll_unary_compose<_Operation1,_Operation2> -llcompose1(const _Operation1& __op1, const _Operation2& __op2) -{ - return ll_unary_compose<_Operation1,_Operation2>(__op1, __op2); -} - -template <class _Operation1, class _Operation2, class _Operation3> -class ll_binary_compose -{ -protected: - _Operation1 _M_op1; - _Operation2 _M_op2; - _Operation3 _M_op3; -public: - ll_binary_compose(const _Operation1& __x, const _Operation2& __y, - const _Operation3& __z) - : _M_op1(__x), _M_op2(__y), _M_op3(__z) { } - template<typename OP2ARG> - auto - operator()(const OP2ARG& __x) const { - return _M_op1(_M_op2(__x), _M_op3(__x)); - } -}; - -template <class _Operation1, class _Operation2, class _Operation3> -inline ll_binary_compose<_Operation1, _Operation2, _Operation3> -llcompose2(const _Operation1& __op1, const _Operation2& __op2, - const _Operation3& __op3) -{ - return ll_binary_compose<_Operation1,_Operation2,_Operation3> - (__op1, __op2, __op3); -} - -// helpers to deal with the fact that MSDev does not package -// bind... with the stl. Again, this is from sgi. -template <class _Operation, typename _Arg1> -class llbinder1st -{ -protected: - _Operation op; - _Arg1 value; -public: - llbinder1st(const _Operation& __x, const _Arg1& __y) - : op(__x), value(__y) {} - template <typename _Arg2> - auto - operator()(const _Arg2& __x) const { - return op(value, __x); - } -}; - -template <class _Operation, class _Tp> -inline auto -llbind1st(const _Operation& __oper, const _Tp& __x) -{ - return llbinder1st<_Operation, _Tp>(__oper, __x); -} - -template <class _Operation, typename _Arg2> -class llbinder2nd -{ -protected: - _Operation op; - _Arg2 value; -public: - llbinder2nd(const _Operation& __x, - const _Arg2& __y) - : op(__x), value(__y) {} - template <typename _Arg1> - auto - operator()(const _Arg1& __x) const { - return op(__x, value); - } -}; - -template <class _Operation, class _Tp> -inline auto -llbind2nd(const _Operation& __oper, const _Tp& __x) -{ - return llbinder2nd<_Operation, _Tp>(__oper, __x); -} - -/** - * Compare std::type_info* pointers a la std::less. We break this out as a - * separate function for use in two different std::less specializations. - */ -inline -bool before(const std::type_info* lhs, const std::type_info* rhs) -{ -#if LL_LINUX && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 4)) - // If we're building on Linux with gcc, and it's either gcc 3.x or - // 4.{0,1,2,3}, then we have to use a workaround. Note that we use gcc on - // Mac too, and some people build with gcc on Windows (cygwin or mingw). - // On Linux, different load modules may produce different type_info* - // pointers for the same type. Have to compare name strings to get good - // results. - return strcmp(lhs->name(), rhs->name()) < 0; -#else // not Linux, or gcc 4.4+ - // Just use before(), as we normally would - return lhs->before(*rhs) ? true : false; -#endif -} - -/** - * Specialize std::less<std::type_info*> to use std::type_info::before(). - * See MAINT-1175. It is NEVER a good idea to directly compare std::type_info* - * because, on Linux, you might get different std::type_info* pointers for the - * same type (from different load modules)! - */ -namespace std -{ - template <> - struct less<const std::type_info*> - { - bool operator()(const std::type_info* lhs, const std::type_info* rhs) const - { - return before(lhs, rhs); - } - }; - - template <> - struct less<std::type_info*> - { - bool operator()(std::type_info* lhs, std::type_info* rhs) const - { - return before(lhs, rhs); - } - }; -} // std - - -/** - * Implementation for ll_template_cast() (q.v.). - * - * Default implementation: trying to cast two completely unrelated types - * returns 0. Typically you'd specify T and U as pointer types, but in fact T - * can be any type that can be initialized with 0. - */ -template <typename T, typename U> -struct ll_template_cast_impl -{ - T operator()(U) - { - return 0; - } -}; - -/** - * ll_template_cast<T>(some_value) is for use in a template function when - * some_value might be of arbitrary type, but you want to recognize type T - * specially. - * - * It's designed for use with pointer types. Example: - * @code - * struct SpecialClass - * { - * void someMethod(const std::string&) const; - * }; - * - * template <class REALCLASS> - * void somefunc(const REALCLASS& instance) - * { - * const SpecialClass* ptr = ll_template_cast<const SpecialClass*>(&instance); - * if (ptr) - * { - * ptr->someMethod("Call method only available on SpecialClass"); - * } - * } - * @endcode - * - * Why is this better than dynamic_cast<>? Because unless OtherClass is - * polymorphic, the following won't even compile (gcc 4.0.1): - * @code - * OtherClass other; - * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&other); - * @endcode - * to say nothing of this: - * @code - * void function(int); - * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&function); - * @endcode - * ll_template_cast handles these kinds of cases by returning 0. - */ -template <typename T, typename U> -T ll_template_cast(U value) -{ - return ll_template_cast_impl<T, U>()(value); -} - -/** - * Implementation for ll_template_cast() (q.v.). - * - * Implementation for identical types: return same value. - */ -template <typename T> -struct ll_template_cast_impl<T, T> -{ - T operator()(T value) - { - return value; - } -}; - -/** - * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of - * type @c source, <tt>ll_template_cast<dest>(s)</tt> will return @c s -- - * presuming that @c source can be converted to @c dest by the normal rules of - * C++. - * - * By default, <tt>ll_template_cast<dest>(s)</tt> will return 0 unless @c s's - * type is literally identical to @c dest. (This is because of the - * straightforward application of template specialization rules.) That can - * lead to surprising results, e.g.: - * - * @code - * Foo myFoo; - * const Foo* fooptr = ll_template_cast<const Foo*>(&myFoo); - * @endcode - * - * Here @c fooptr will be 0 because <tt>&myFoo</tt> is of type <tt>Foo*</tt> - * -- @em not <tt>const Foo*</tt>. (Declaring <tt>const Foo myFoo;</tt> would - * force the compiler to do the right thing.) - * - * More disappointingly: - * @code - * struct Base {}; - * struct Subclass: public Base {}; - * Subclass object; - * Base* ptr = ll_template_cast<Base*>(&object); - * @endcode - * - * Here @c ptr will be 0 because <tt>&object</tt> is of type - * <tt>Subclass*</tt> rather than <tt>Base*</tt>. We @em want this cast to - * succeed, but without our help ll_template_cast can't recognize it. - * - * The following would suffice: - * @code - * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); - * ... - * Base* ptr = ll_template_cast<Base*>(&object); - * @endcode - * - * However, as noted earlier, this is easily fooled: - * @code - * const Base* ptr = ll_template_cast<const Base*>(&object); - * @endcode - * would still produce 0 because we haven't yet seen: - * @code - * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); - * @endcode - * - * @TODO - * This macro should use Boost type_traits facilities for stripping and - * re-adding @c const and @c volatile qualifiers so that invoking - * LL_TEMPLATE_CONVERTIBLE(dest, source) will automatically generate all - * permitted permutations. It's really not fair to the coder to require - * separate: - * @code - * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); - * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); - * LL_TEMPLATE_CONVERTIBLE(const Base*, const Subclass*); - * @endcode - * - * (Naturally we omit <tt>LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*)</tt> - * because that's not permitted by normal C++ assignment anyway.) - */ -#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \ -template <> \ -struct ll_template_cast_impl<DEST, SOURCE> \ -{ \ - DEST operator()(SOURCE wrapper) \ - { \ - return wrapper; \ - } \ -} - - -#endif // LL_LLSTL_H +/**
+ * @file llstl.h
+ * @brief helper object & functions for use with the stl.
+ *
+ * $LicenseInfo:firstyear=2003&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLSTL_H
+#define LL_LLSTL_H
+
+#include "stdtypes.h"
+#include <functional>
+#include <algorithm>
+#include <map>
+#include <vector>
+#include <list>
+#include <set>
+#include <typeinfo>
+
+#ifdef LL_LINUX
+// <ND> For strcmp
+#include <string.h>
+#endif
+// Use to compare the first element only of a pair
+// e.g. typedef std::set<std::pair<int, Data*>, compare_pair<int, Data*> > some_pair_set_t;
+template <typename T1, typename T2>
+struct compare_pair_first
+{
+ bool operator()(const std::pair<T1, T2>& a, const std::pair<T1, T2>& b) const
+ {
+ return a.first < b.first;
+ }
+};
+
+template <typename T1, typename T2>
+struct compare_pair_greater
+{
+ bool operator()(const std::pair<T1, T2>& a, const std::pair<T1, T2>& b) const
+ {
+ if (!(a.first < b.first))
+ return true;
+ else if (!(b.first < a.first))
+ return false;
+ else
+ return !(a.second < b.second);
+ }
+};
+
+// Use to compare the contents of two pointers (e.g. std::string*)
+template <typename T>
+struct compare_pointer_contents
+{
+ typedef const T* Tptr;
+ bool operator()(const Tptr& a, const Tptr& b) const
+ {
+ return *a < *b;
+ }
+};
+
+// DeletePointer is a simple helper for deleting all pointers in a container.
+// The general form is:
+//
+// std::for_each(cont.begin(), cont.end(), DeletePointer());
+// somemap.clear();
+//
+// Don't forget to clear()!
+
+struct DeletePointer
+{
+ template<typename T> void operator()(T* ptr) const
+ {
+ delete ptr;
+ }
+};
+struct DeletePointerArray
+{
+ template<typename T> void operator()(T* ptr) const
+ {
+ delete[] ptr;
+ }
+};
+
+// DeletePairedPointer is a simple helper for deleting all pointers in a map.
+// The general form is:
+//
+// std::for_each(somemap.begin(), somemap.end(), DeletePairedPointer());
+// somemap.clear(); // Don't leave dangling pointers around
+
+struct DeletePairedPointer
+{
+ template<typename T> void operator()(T &ptr) const
+ {
+ delete ptr.second;
+ ptr.second = NULL;
+ }
+};
+struct DeletePairedPointerArray
+{
+ template<typename T> void operator()(T &ptr) const
+ {
+ delete[] ptr.second;
+ ptr.second = NULL;
+ }
+};
+
+
+// Alternate version of the above so that has a more cumbersome
+// syntax, but it can be used with compositional functors.
+// NOTE: The functor retuns a bool because msdev bombs during the
+// composition if you return void. Once we upgrade to a newer
+// compiler, the second unary_function template parameter can be set
+// to void.
+//
+// Here's a snippet showing how you use this object:
+//
+// typedef std::map<int, widget*> map_type;
+// map_type widget_map;
+// ... // add elements
+// // delete them all
+// for_each(widget_map.begin(),
+// widget_map.end(),
+// llcompose1(DeletePointerFunctor<widget>(),
+// llselect2nd<map_type::value_type>()));
+
+template<typename T>
+struct DeletePointerFunctor
+{
+ bool operator()(T* ptr) const
+ {
+ delete ptr;
+ return true;
+ }
+};
+
+// See notes about DeleteArray for why you should consider avoiding this.
+template<typename T>
+struct DeleteArrayFunctor
+{
+ bool operator()(T* ptr) const
+ {
+ delete[] ptr;
+ return true;
+ }
+};
+
+// CopyNewPointer is a simple helper which accepts a pointer, and
+// returns a new pointer built with the copy constructor. Example:
+//
+// transform(in.begin(), in.end(), out.end(), CopyNewPointer());
+
+struct CopyNewPointer
+{
+ template<typename T> T* operator()(const T* ptr) const
+ {
+ return new T(*ptr);
+ }
+};
+
+template<typename T, typename ALLOC>
+void delete_and_clear(std::list<T*, ALLOC>& list)
+{
+ std::for_each(list.begin(), list.end(), DeletePointer());
+ list.clear();
+}
+
+template<typename T, typename ALLOC>
+void delete_and_clear(std::vector<T*, ALLOC>& vector)
+{
+ std::for_each(vector.begin(), vector.end(), DeletePointer());
+ vector.clear();
+}
+
+template<typename T, typename COMPARE, typename ALLOC>
+void delete_and_clear(std::set<T*, COMPARE, ALLOC>& set)
+{
+ std::for_each(set.begin(), set.end(), DeletePointer());
+ set.clear();
+}
+
+template<typename K, typename V, typename COMPARE, typename ALLOC>
+void delete_and_clear(std::map<K, V*, COMPARE, ALLOC>& map)
+{
+ std::for_each(map.begin(), map.end(), DeletePairedPointer());
+ map.clear();
+}
+
+template<typename T>
+void delete_and_clear(T*& ptr)
+{
+ delete ptr;
+ ptr = NULL;
+}
+
+
+template<typename T>
+void delete_and_clear_array(T*& ptr)
+{
+ delete[] ptr;
+ ptr = NULL;
+}
+
+// Simple function to help with finding pointers in maps.
+// For example:
+// typedef map_t;
+// std::map<int, const char*> foo;
+// foo[18] = "there";
+// foo[2] = "hello";
+// const char* bar = get_ptr_in_map(foo, 2); // bar -> "hello"
+// const char* baz = get_ptr_in_map(foo, 3); // baz == NULL
+template <typename K, typename T>
+inline T* get_ptr_in_map(const std::map<K,T*>& inmap, const K& key)
+{
+ // Typedef here avoids warnings because of new c++ naming rules.
+ typedef typename std::map<K,T*>::const_iterator map_iter;
+ map_iter iter = inmap.find(key);
+ if(iter == inmap.end())
+ {
+ return NULL;
+ }
+ else
+ {
+ return iter->second;
+ }
+};
+
+// helper function which returns true if key is in inmap.
+template <typename K, typename T>
+inline bool is_in_map(const std::map<K,T>& inmap, const K& key)
+{
+ if(inmap.find(key) == inmap.end())
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+// Similar to get_ptr_in_map, but for any type with a valid T(0) constructor.
+// To replace LLSkipMap getIfThere, use:
+// get_if_there(map, key, 0)
+// WARNING: Make sure default_value (generally 0) is not a valid map entry!
+template <typename K, typename T>
+inline T get_if_there(const std::map<K,T>& inmap, const K& key, T default_value)
+{
+ // Typedef here avoids warnings because of new c++ naming rules.
+ typedef typename std::map<K,T>::const_iterator map_iter;
+ map_iter iter = inmap.find(key);
+ if(iter == inmap.end())
+ {
+ return default_value;
+ }
+ else
+ {
+ return iter->second;
+ }
+};
+
+// Useful for replacing the removeObj() functionality of LLDynamicArray
+// Example:
+// for (std::vector<T>::iterator iter = mList.begin(); iter != mList.end(); )
+// {
+// if ((*iter)->isMarkedForRemoval())
+// iter = vector_replace_with_last(mList, iter);
+// else
+// ++iter;
+// }
+template <typename T>
+inline typename std::vector<T>::iterator vector_replace_with_last(std::vector<T>& invec, typename std::vector<T>::iterator iter)
+{
+ typename std::vector<T>::iterator last = invec.end(); --last;
+ if (iter == invec.end())
+ {
+ return iter;
+ }
+ else if (iter == last)
+ {
+ invec.pop_back();
+ return invec.end();
+ }
+ else
+ {
+ *iter = *last;
+ invec.pop_back();
+ return iter;
+ }
+};
+
+// Example:
+// vector_replace_with_last(mList, x);
+template <typename T>
+inline bool vector_replace_with_last(std::vector<T>& invec, const T& val)
+{
+ typename std::vector<T>::iterator iter = std::find(invec.begin(), invec.end(), val);
+ if (iter != invec.end())
+ {
+ typename std::vector<T>::iterator last = invec.end(); --last;
+ *iter = *last;
+ invec.pop_back();
+ return true;
+ }
+ return false;
+}
+
+// Append N elements to the vector and return a pointer to the first new element.
+template <typename T>
+inline T* vector_append(std::vector<T>& invec, S32 N)
+{
+ U32 sz = invec.size();
+ invec.resize(sz+N);
+ return &(invec[sz]);
+}
+
+// call function f to n members starting at first. similar to std::for_each
+template <class InputIter, class Size, class Function>
+Function ll_for_n(InputIter first, Size n, Function f)
+{
+ for ( ; n > 0; --n, ++first)
+ f(*first);
+ return f;
+}
+
+// copy first to result n times, incrementing each as we go
+template <class InputIter, class Size, class OutputIter>
+OutputIter ll_copy_n(InputIter first, Size n, OutputIter result)
+{
+ for ( ; n > 0; --n, ++result, ++first)
+ *result = *first;
+ return result;
+}
+
+// set *result = op(*f) for n elements of f
+template <class InputIter, class OutputIter, class Size, class UnaryOp>
+OutputIter ll_transform_n(
+ InputIter first,
+ Size n,
+ OutputIter result,
+ UnaryOp op)
+{
+ for ( ; n > 0; --n, ++result, ++first)
+ *result = op(*first);
+ return result;
+}
+
+
+
+/*
+ *
+ * Copyright (c) 1994
+ * Hewlett-Packard Company
+ *
+ * Permission to use, copy, modify, distribute and sell this software
+ * and its documentation for any purpose is hereby granted without fee,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation. Hewlett-Packard Company makes no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ *
+ * Copyright (c) 1996-1998
+ * Silicon Graphics Computer Systems, Inc.
+ *
+ * Permission to use, copy, modify, distribute and sell this software
+ * and its documentation for any purpose is hereby granted without fee,
+ * provided that the above copyright notice appear in all copies and
+ * that both that copyright notice and this permission notice appear
+ * in supporting documentation. Silicon Graphics makes no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ */
+
+
+// helper to deal with the fact that MSDev does not package
+// select... with the stl. Look up usage on the sgi website.
+
+template <class _Pair>
+struct _LLSelect1st
+{
+ const auto& operator()(const _Pair& __x) const {
+ return __x.first;
+ }
+};
+
+template <class _Pair>
+struct _LLSelect2nd
+{
+ const auto& operator()(const _Pair& __x) const {
+ return __x.second;
+ }
+};
+
+template <class _Pair> struct llselect1st : public _LLSelect1st<_Pair> {};
+template <class _Pair> struct llselect2nd : public _LLSelect2nd<_Pair> {};
+
+// helper to deal with the fact that MSDev does not package
+// compose... with the stl. Look up usage on the sgi website.
+
+template <class _Operation1, class _Operation2>
+class ll_unary_compose
+{
+protected:
+ _Operation1 __op1;
+ _Operation2 __op2;
+public:
+ ll_unary_compose(const _Operation1& __x, const _Operation2& __y)
+ : __op1(__x), __op2(__y) {}
+ template <typename _Op2Arg>
+ auto
+ operator()(const _Op2Arg& __x) const {
+ return __op1(__op2(__x));
+ }
+};
+
+template <class _Operation1, class _Operation2>
+inline ll_unary_compose<_Operation1,_Operation2>
+llcompose1(const _Operation1& __op1, const _Operation2& __op2)
+{
+ return ll_unary_compose<_Operation1,_Operation2>(__op1, __op2);
+}
+
+template <class _Operation1, class _Operation2, class _Operation3>
+class ll_binary_compose
+{
+protected:
+ _Operation1 _M_op1;
+ _Operation2 _M_op2;
+ _Operation3 _M_op3;
+public:
+ ll_binary_compose(const _Operation1& __x, const _Operation2& __y,
+ const _Operation3& __z)
+ : _M_op1(__x), _M_op2(__y), _M_op3(__z) { }
+ template<typename OP2ARG>
+ auto
+ operator()(const OP2ARG& __x) const {
+ return _M_op1(_M_op2(__x), _M_op3(__x));
+ }
+};
+
+template <class _Operation1, class _Operation2, class _Operation3>
+inline ll_binary_compose<_Operation1, _Operation2, _Operation3>
+llcompose2(const _Operation1& __op1, const _Operation2& __op2,
+ const _Operation3& __op3)
+{
+ return ll_binary_compose<_Operation1,_Operation2,_Operation3>
+ (__op1, __op2, __op3);
+}
+
+// helpers to deal with the fact that MSDev does not package
+// bind... with the stl. Again, this is from sgi.
+template <class _Operation, typename _Arg1>
+class llbinder1st
+{
+protected:
+ _Operation op;
+ _Arg1 value;
+public:
+ llbinder1st(const _Operation& __x, const _Arg1& __y)
+ : op(__x), value(__y) {}
+ template <typename _Arg2>
+ auto
+ operator()(const _Arg2& __x) const {
+ return op(value, __x);
+ }
+};
+
+template <class _Operation, class _Tp>
+inline auto
+llbind1st(const _Operation& __oper, const _Tp& __x)
+{
+ return llbinder1st<_Operation, _Tp>(__oper, __x);
+}
+
+template <class _Operation, typename _Arg2>
+class llbinder2nd
+{
+protected:
+ _Operation op;
+ _Arg2 value;
+public:
+ llbinder2nd(const _Operation& __x,
+ const _Arg2& __y)
+ : op(__x), value(__y) {}
+ template <typename _Arg1>
+ auto
+ operator()(const _Arg1& __x) const {
+ return op(__x, value);
+ }
+};
+
+template <class _Operation, class _Tp>
+inline auto
+llbind2nd(const _Operation& __oper, const _Tp& __x)
+{
+ return llbinder2nd<_Operation, _Tp>(__oper, __x);
+}
+
+/**
+ * Compare std::type_info* pointers a la std::less. We break this out as a
+ * separate function for use in two different std::less specializations.
+ */
+inline
+bool before(const std::type_info* lhs, const std::type_info* rhs)
+{
+#if LL_LINUX && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 4))
+ // If we're building on Linux with gcc, and it's either gcc 3.x or
+ // 4.{0,1,2,3}, then we have to use a workaround. Note that we use gcc on
+ // Mac too, and some people build with gcc on Windows (cygwin or mingw).
+ // On Linux, different load modules may produce different type_info*
+ // pointers for the same type. Have to compare name strings to get good
+ // results.
+ return strcmp(lhs->name(), rhs->name()) < 0;
+#else // not Linux, or gcc 4.4+
+ // Just use before(), as we normally would
+ return lhs->before(*rhs);
+#endif
+}
+
+/**
+ * Specialize std::less<std::type_info*> to use std::type_info::before().
+ * See MAINT-1175. It is NEVER a good idea to directly compare std::type_info*
+ * because, on Linux, you might get different std::type_info* pointers for the
+ * same type (from different load modules)!
+ */
+namespace std
+{
+ template <>
+ struct less<const std::type_info*>
+ {
+ bool operator()(const std::type_info* lhs, const std::type_info* rhs) const
+ {
+ return before(lhs, rhs);
+ }
+ };
+
+ template <>
+ struct less<std::type_info*>
+ {
+ bool operator()(std::type_info* lhs, std::type_info* rhs) const
+ {
+ return before(lhs, rhs);
+ }
+ };
+} // std
+
+
+/**
+ * Implementation for ll_template_cast() (q.v.).
+ *
+ * Default implementation: trying to cast two completely unrelated types
+ * returns 0. Typically you'd specify T and U as pointer types, but in fact T
+ * can be any type that can be initialized with 0.
+ */
+template <typename T, typename U>
+struct ll_template_cast_impl
+{
+ T operator()(U)
+ {
+ return 0;
+ }
+};
+
+/**
+ * ll_template_cast<T>(some_value) is for use in a template function when
+ * some_value might be of arbitrary type, but you want to recognize type T
+ * specially.
+ *
+ * It's designed for use with pointer types. Example:
+ * @code
+ * struct SpecialClass
+ * {
+ * void someMethod(const std::string&) const;
+ * };
+ *
+ * template <class REALCLASS>
+ * void somefunc(const REALCLASS& instance)
+ * {
+ * const SpecialClass* ptr = ll_template_cast<const SpecialClass*>(&instance);
+ * if (ptr)
+ * {
+ * ptr->someMethod("Call method only available on SpecialClass");
+ * }
+ * }
+ * @endcode
+ *
+ * Why is this better than dynamic_cast<>? Because unless OtherClass is
+ * polymorphic, the following won't even compile (gcc 4.0.1):
+ * @code
+ * OtherClass other;
+ * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&other);
+ * @endcode
+ * to say nothing of this:
+ * @code
+ * void function(int);
+ * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&function);
+ * @endcode
+ * ll_template_cast handles these kinds of cases by returning 0.
+ */
+template <typename T, typename U>
+T ll_template_cast(U value)
+{
+ return ll_template_cast_impl<T, U>()(value);
+}
+
+/**
+ * Implementation for ll_template_cast() (q.v.).
+ *
+ * Implementation for identical types: return same value.
+ */
+template <typename T>
+struct ll_template_cast_impl<T, T>
+{
+ T operator()(T value)
+ {
+ return value;
+ }
+};
+
+/**
+ * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of
+ * type @c source, <tt>ll_template_cast<dest>(s)</tt> will return @c s --
+ * presuming that @c source can be converted to @c dest by the normal rules of
+ * C++.
+ *
+ * By default, <tt>ll_template_cast<dest>(s)</tt> will return 0 unless @c s's
+ * type is literally identical to @c dest. (This is because of the
+ * straightforward application of template specialization rules.) That can
+ * lead to surprising results, e.g.:
+ *
+ * @code
+ * Foo myFoo;
+ * const Foo* fooptr = ll_template_cast<const Foo*>(&myFoo);
+ * @endcode
+ *
+ * Here @c fooptr will be 0 because <tt>&myFoo</tt> is of type <tt>Foo*</tt>
+ * -- @em not <tt>const Foo*</tt>. (Declaring <tt>const Foo myFoo;</tt> would
+ * force the compiler to do the right thing.)
+ *
+ * More disappointingly:
+ * @code
+ * struct Base {};
+ * struct Subclass: public Base {};
+ * Subclass object;
+ * Base* ptr = ll_template_cast<Base*>(&object);
+ * @endcode
+ *
+ * Here @c ptr will be 0 because <tt>&object</tt> is of type
+ * <tt>Subclass*</tt> rather than <tt>Base*</tt>. We @em want this cast to
+ * succeed, but without our help ll_template_cast can't recognize it.
+ *
+ * The following would suffice:
+ * @code
+ * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*);
+ * ...
+ * Base* ptr = ll_template_cast<Base*>(&object);
+ * @endcode
+ *
+ * However, as noted earlier, this is easily fooled:
+ * @code
+ * const Base* ptr = ll_template_cast<const Base*>(&object);
+ * @endcode
+ * would still produce 0 because we haven't yet seen:
+ * @code
+ * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*);
+ * @endcode
+ *
+ * @TODO
+ * This macro should use Boost type_traits facilities for stripping and
+ * re-adding @c const and @c volatile qualifiers so that invoking
+ * LL_TEMPLATE_CONVERTIBLE(dest, source) will automatically generate all
+ * permitted permutations. It's really not fair to the coder to require
+ * separate:
+ * @code
+ * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*);
+ * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*);
+ * LL_TEMPLATE_CONVERTIBLE(const Base*, const Subclass*);
+ * @endcode
+ *
+ * (Naturally we omit <tt>LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*)</tt>
+ * because that's not permitted by normal C++ assignment anyway.)
+ */
+#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \
+template <> \
+struct ll_template_cast_impl<DEST, SOURCE> \
+{ \
+ DEST operator()(SOURCE wrapper) \
+ { \
+ return wrapper; \
+ } \
+}
+
+
+#endif // LL_LLSTL_H
diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 6512bbc392..b3fc9b484a 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -1,1756 +1,1756 @@ -/** - * @file llstring.cpp - * @brief String utility functions and the std::string class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llstring.h" -#include "llerror.h" -#include "llfasttimer.h" -#include "llsd.h" -#include <vector> - -#if LL_WINDOWS -#include "llwin32headerslean.h" -#include <winnls.h> // for WideCharToMultiByte -#endif - -std::string ll_safe_string(const char* in) -{ - if(in) return std::string(in); - return std::string(); -} - -std::string ll_safe_string(const char* in, S32 maxlen) -{ - if(in && maxlen > 0 ) return std::string(in, maxlen); - - return std::string(); -} - -bool is_char_hex(char hex) -{ - if((hex >= '0') && (hex <= '9')) - { - return true; - } - else if((hex >= 'a') && (hex <='f')) - { - return true; - } - else if((hex >= 'A') && (hex <='F')) - { - return true; - } - return false; // uh - oh, not hex any more... -} - -U8 hex_as_nybble(char hex) -{ - if((hex >= '0') && (hex <= '9')) - { - return (U8)(hex - '0'); - } - else if((hex >= 'a') && (hex <='f')) - { - return (U8)(10 + hex - 'a'); - } - else if((hex >= 'A') && (hex <='F')) - { - return (U8)(10 + hex - 'A'); - } - return 0; // uh - oh, not hex any more... -} - -bool iswindividual(llwchar elem) -{ - U32 cur_char = (U32)elem; - bool result = false; - if (0x2E80<= cur_char && cur_char <= 0x9FFF) - { - result = true; - } - else if (0xAC00<= cur_char && cur_char <= 0xD7A0 ) - { - result = true; - } - else if (0xF900<= cur_char && cur_char <= 0xFA60 ) - { - result = true; - } - return result; -} - -bool _read_file_into_string(std::string& str, const std::string& filename) -{ - llifstream ifs(filename.c_str(), llifstream::binary); - if (!ifs.is_open()) - { - LL_INFOS() << "Unable to open file " << filename << LL_ENDL; - return false; - } - - std::ostringstream oss; - - oss << ifs.rdbuf(); - str = oss.str(); - ifs.close(); - return true; -} - - - - -// See http://www.unicode.org/Public/BETA/CVTUTF-1-2/ConvertUTF.c -// for the Unicode implementation - this doesn't match because it was written before finding -// it. - - -std::ostream& operator<<(std::ostream &s, const LLWString &wstr) -{ - std::string utf8_str = wstring_to_utf8str(wstr); - s << utf8_str; - return s; -} - -std::string rawstr_to_utf8(const std::string& raw) -{ - LLWString wstr(utf8str_to_wstring(raw)); - return wstring_to_utf8str(wstr); -} - -std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) -{ - U32 cur_char = (U32)in_char; - char* base = outchars; - if (cur_char < 0x80) - { - *outchars++ = (U8)cur_char; - } - else if (cur_char < 0x800) - { - *outchars++ = 0xC0 | (cur_char >> 6); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else if (cur_char < 0x10000) - { - *outchars++ = 0xE0 | (cur_char >> 12); - *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else if (cur_char < 0x200000) - { - *outchars++ = 0xF0 | (cur_char >> 18); - *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else if (cur_char < 0x4000000) - { - *outchars++ = 0xF8 | (cur_char >> 24); - *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else if (cur_char < 0x80000000) - { - *outchars++ = 0xFC | (cur_char >> 30); - *outchars++ = 0x80 | ((cur_char >> 24) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); - *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); - *outchars++ = 0x80 | (cur_char & 0x3F); - } - else - { - LL_WARNS() << "Invalid Unicode character " << cur_char << "!" << LL_ENDL; - *outchars++ = LL_UNKNOWN_CHAR; - } - return outchars - base; -} - -auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) -{ - const U16* base = inchars; - U16 cur_char = *inchars++; - llwchar char32 = cur_char; - if ((cur_char >= 0xD800) && (cur_char <= 0xDFFF)) - { - // Surrogates - char32 = ((llwchar)(cur_char - 0xD800)) << 10; - cur_char = *inchars++; - char32 += (llwchar)(cur_char - 0xDC00) + 0x0010000UL; - } - else - { - char32 = (llwchar)cur_char; - } - *outchar = char32; - return inchars - base; -} - -llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) -{ - llutf16string out; - - S32 i = 0; - while (i < len) - { - U32 cur_char = utf32str[i]; - if (cur_char > 0xFFFF) - { - out += (0xD7C0 + (cur_char >> 10)); - out += (0xDC00 | (cur_char & 0x3FF)); - } - else - { - out += cur_char; - } - i++; - } - return out; -} - -llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) -{ - LLWString wstr = utf8str_to_wstring ( utf8str, len ); - return wstring_to_utf16str ( wstr ); -} - -LLWString utf16str_to_wstring(const U16* utf16str, size_t len) -{ - LLWString wout; - if (len == 0) return wout; - - S32 i = 0; - const U16* chars16 = utf16str; - while (i < len) - { - llwchar cur_char; - i += utf16chars_to_wchar(chars16+i, &cur_char); - wout += cur_char; - } - return wout; -} - -// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. -S32 utf16str_wstring_length(const llutf16string &utf16str, const S32 utf16_len) -{ - S32 surrogate_pairs = 0; - // ... craziness to make gcc happy (llutf16string.c_str() is tweaked on linux): - const U16 *const utf16_chars = &(*(utf16str.begin())); - S32 i = 0; - while (i < utf16_len) - { - const U16 c = utf16_chars[i++]; - if (c >= 0xD800 && c <= 0xDBFF) // See http://en.wikipedia.org/wiki/UTF-16 - { // Have first byte of a surrogate pair - if (i >= utf16_len) - { - break; - } - const U16 d = utf16_chars[i]; - if (d >= 0xDC00 && d <= 0xDFFF) - { // Have valid second byte of a surrogate pair - surrogate_pairs++; - i++; - } - } - } - return utf16_len - surrogate_pairs; -} - -// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. -S32 wstring_utf16_length(const LLWString &wstr, const S32 woffset, const S32 wlen) -{ - const S32 end = llmin((S32)wstr.length(), woffset + wlen); - if (end < woffset) - { - return 0; - } - else - { - S32 length = end - woffset; - for (S32 i = woffset; i < end; i++) - { - if (wstr[i] >= 0x10000) - { - length++; - } - } - return length; - } -} - -// Given a wstring and an offset in it, returns the length as wstring (i.e., -// number of llwchars) of the longest substring that starts at the offset -// and whose equivalent utf-16 string does not exceeds the given utf16_length. -S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, BOOL *unaligned) -{ - const auto end = wstr.length(); - BOOL u = FALSE; - S32 n = woffset + utf16_length; - S32 i = woffset; - while (i < end) - { - if (wstr[i] >= 0x10000) - { - --n; - } - if (i >= n) - { - u = (i > n); - break; - } - i++; - } - if (unaligned) - { - *unaligned = u; - } - return i - woffset; -} - -S32 wchar_utf8_length(const llwchar wc) -{ - if (wc < 0x80) - { - return 1; - } - else if (wc < 0x800) - { - return 2; - } - else if (wc < 0x10000) - { - return 3; - } - else if (wc < 0x200000) - { - return 4; - } - else if (wc < 0x4000000) - { - return 5; - } - else - { - return 6; - } -} - -std::string wchar_utf8_preview(const llwchar wc) -{ - std::ostringstream oss; - oss << std::hex << std::uppercase << (U32)wc; - - U8 out_bytes[8]; - U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes); - - if (size > 1) - { - oss << " ["; - for (U32 i = 0; i < size; ++i) - { - if (i) - { - oss << ", "; - } - oss << (int)out_bytes[i]; - } - oss << "]"; - } - - return oss.str(); -} - -S32 wstring_utf8_length(const LLWString& wstr) -{ - S32 len = 0; - for (S32 i = 0; i < (S32)wstr.length(); i++) - { - len += wchar_utf8_length(wstr[i]); - } - return len; -} - -LLWString utf8str_to_wstring(const char* utf8str, size_t len) -{ - LLWString wout; - - S32 i = 0; - while (i < len) - { - llwchar unichar; - U8 cur_char = utf8str[i]; - - if (cur_char < 0x80) - { - // Ascii character, just add it - unichar = cur_char; - } - else - { - S32 cont_bytes = 0; - if ((cur_char >> 5) == 0x6) // Two byte UTF8 -> 1 UTF32 - { - unichar = (0x1F&cur_char); - cont_bytes = 1; - } - else if ((cur_char >> 4) == 0xe) // Three byte UTF8 -> 1 UTF32 - { - unichar = (0x0F&cur_char); - cont_bytes = 2; - } - else if ((cur_char >> 3) == 0x1e) // Four byte UTF8 -> 1 UTF32 - { - unichar = (0x07&cur_char); - cont_bytes = 3; - } - else if ((cur_char >> 2) == 0x3e) // Five byte UTF8 -> 1 UTF32 - { - unichar = (0x03&cur_char); - cont_bytes = 4; - } - else if ((cur_char >> 1) == 0x7e) // Six byte UTF8 -> 1 UTF32 - { - unichar = (0x01&cur_char); - cont_bytes = 5; - } - else - { - wout += LL_UNKNOWN_CHAR; - ++i; - continue; - } - - // Check that this character doesn't go past the end of the string - auto end = (len < (i + cont_bytes)) ? len : (i + cont_bytes); - do - { - ++i; - - cur_char = utf8str[i]; - if ( (cur_char >> 6) == 0x2 ) - { - unichar <<= 6; - unichar += (0x3F&cur_char); - } - else - { - // Malformed sequence - roll back to look at this as a new char - unichar = LL_UNKNOWN_CHAR; - --i; - break; - } - } while(i < end); - - // Handle overlong characters and NULL characters - if ( ((cont_bytes == 1) && (unichar < 0x80)) - || ((cont_bytes == 2) && (unichar < 0x800)) - || ((cont_bytes == 3) && (unichar < 0x10000)) - || ((cont_bytes == 4) && (unichar < 0x200000)) - || ((cont_bytes == 5) && (unichar < 0x4000000)) ) - { - unichar = LL_UNKNOWN_CHAR; - } - } - - wout += unichar; - ++i; - } - return wout; -} - -std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) -{ - std::string out; - - S32 i = 0; - while (i < len) - { - char tchars[8]; /* Flawfinder: ignore */ - auto n = wchar_to_utf8chars(utf32str[i], tchars); - tchars[n] = 0; - out += tchars; - i++; - } - return out; -} - -std::string utf16str_to_utf8str(const U16* utf16str, size_t len) -{ - return wstring_to_utf8str(utf16str_to_wstring(utf16str, len)); -} - -std::string utf8str_trim(const std::string& utf8str) -{ - LLWString wstr = utf8str_to_wstring(utf8str); - LLWStringUtil::trim(wstr); - return wstring_to_utf8str(wstr); -} - - -std::string utf8str_tolower(const std::string& utf8str) -{ - LLWString out_str = utf8str_to_wstring(utf8str); - LLWStringUtil::toLower(out_str); - return wstring_to_utf8str(out_str); -} - - -S32 utf8str_compare_insensitive(const std::string& lhs, const std::string& rhs) -{ - LLWString wlhs = utf8str_to_wstring(lhs); - LLWString wrhs = utf8str_to_wstring(rhs); - return LLWStringUtil::compareInsensitive(wlhs, wrhs); -} - -std::string utf8str_truncate(const std::string& utf8str, const S32 max_len) -{ - if (0 == max_len) - { - return std::string(); - } - if ((S32)utf8str.length() <= max_len) - { - return utf8str; - } - else - { - S32 cur_char = max_len; - - // If we're ASCII, we don't need to do anything - if ((U8)utf8str[cur_char] > 0x7f) - { - // If first two bits are (10), it's the tail end of a multibyte char. We need to shift back - // to the first character - while (0x80 == (0xc0 & utf8str[cur_char])) - { - cur_char--; - // Keep moving forward until we hit the first char; - if (cur_char == 0) - { - // Make sure we don't trash memory if we've got a bogus string. - break; - } - } - } - // The byte index we're on is one we want to get rid of, so we only want to copy up to (cur_char-1) chars - return utf8str.substr(0, cur_char); - } -} - -std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len) -{ - if (0 == symbol_len) - { - return std::string(); - } - if ((S32)utf8str.length() <= symbol_len) - { - return utf8str; - } - else - { - int len = 0, byteIndex = 0; - const char* aStr = utf8str.c_str(); - size_t origSize = utf8str.size(); - - for (byteIndex = 0; len < symbol_len && byteIndex < origSize; byteIndex++) - { - if ((aStr[byteIndex] & 0xc0) != 0x80) - { - len += 1; - } - } - return utf8str.substr(0, byteIndex); - } -} - -std::string utf8str_substChar( - const std::string& utf8str, - const llwchar target_char, - const llwchar replace_char) -{ - LLWString wstr = utf8str_to_wstring(utf8str); - LLWStringUtil::replaceChar(wstr, target_char, replace_char); - //wstr = wstring_substChar(wstr, target_char, replace_char); - return wstring_to_utf8str(wstr); -} - -std::string utf8str_makeASCII(const std::string& utf8str) -{ - LLWString wstr = utf8str_to_wstring(utf8str); - LLWStringUtil::_makeASCII(wstr); - return wstring_to_utf8str(wstr); -} - -std::string mbcsstring_makeASCII(const std::string& wstr) -{ - // Replace non-ASCII chars with replace_char - std::string out_str = wstr; - for (S32 i = 0; i < (S32)out_str.length(); i++) - { - if ((U8)out_str[i] > 0x7f) - { - out_str[i] = LL_UNKNOWN_CHAR; - } - } - return out_str; -} - -std::string utf8str_removeCRLF(const std::string& utf8str) -{ - if (0 == utf8str.length()) - { - return std::string(); - } - const char CR = 13; - - std::string out; - out.reserve(utf8str.length()); - const S32 len = (S32)utf8str.length(); - for( S32 i = 0; i < len; i++ ) - { - if( utf8str[i] != CR ) - { - out.push_back(utf8str[i]); - } - } - return out; -} - -llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length) -{ - switch (length) - { - case 2: - return ((utf8str[offset] & 0x1F) << 6) + - (utf8str[offset + 1] & 0x3F); - case 3: - return ((utf8str[offset] & 0x0F) << 12) + - ((utf8str[offset + 1] & 0x3F) << 6) + - (utf8str[offset + 2] & 0x3F); - case 4: - return ((utf8str[offset] & 0x07) << 18) + - ((utf8str[offset + 1] & 0x3F) << 12) + - ((utf8str[offset + 2] & 0x3F) << 6) + - (utf8str[offset + 3] & 0x3F); - case 5: - return ((utf8str[offset] & 0x03) << 24) + - ((utf8str[offset + 1] & 0x3F) << 18) + - ((utf8str[offset + 2] & 0x3F) << 12) + - ((utf8str[offset + 3] & 0x3F) << 6) + - (utf8str[offset + 4] & 0x3F); - case 6: - return ((utf8str[offset] & 0x01) << 30) + - ((utf8str[offset + 1] & 0x3F) << 24) + - ((utf8str[offset + 2] & 0x3F) << 18) + - ((utf8str[offset + 3] & 0x3F) << 12) + - ((utf8str[offset + 4] & 0x3F) << 6) + - (utf8str[offset + 5] & 0x3F); - case 7: - return ((utf8str[offset + 1] & 0x03) << 30) + - ((utf8str[offset + 2] & 0x3F) << 24) + - ((utf8str[offset + 3] & 0x3F) << 18) + - ((utf8str[offset + 4] & 0x3F) << 12) + - ((utf8str[offset + 5] & 0x3F) << 6) + - (utf8str[offset + 6] & 0x3F); - } - return LL_UNKNOWN_CHAR; -} - -std::string utf8str_showBytesUTF8(const std::string& utf8str) -{ - std::string result; - - bool in_sequence = false; - size_t sequence_size = 0; - size_t byte_index = 0; - size_t source_length = utf8str.size(); - - auto open_sequence = [&]() - { - if (!result.empty() && result.back() != '\n') - result += '\n'; // Use LF as a separator before new UTF-8 sequence - result += '['; - in_sequence = true; - }; - - auto close_sequence = [&]() - { - llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size); - if (unicode != LL_UNKNOWN_CHAR) - { - result += llformat("+%04X", unicode); - } - result += ']'; - in_sequence = false; - sequence_size = 0; - }; - - while (byte_index < source_length) - { - U8 byte = utf8str[byte_index]; - if (byte >= 0x80) // Part of an UTF-8 sequence - { - if (!in_sequence) // Start new UTF-8 sequence - { - open_sequence(); - } - else if (byte >= 0xC0) // Start another UTF-8 sequence - { - close_sequence(); - open_sequence(); - } - else // Continue the same UTF-8 sequence - { - result += '.'; - } - result += llformat("%02X", byte); // The byte is represented in hexadecimal form - ++sequence_size; - } - else // ASCII symbol is represented as a character - { - if (in_sequence) // End of UTF-8 sequence - { - close_sequence(); - if (byte != '\n') - { - result += '\n'; // Use LF as a separator between UTF-8 and ASCII - } - } - result += byte; - } - ++byte_index; - } - - if (in_sequence) // End of UTF-8 sequence - { - close_sequence(); - } - - return result; -} - -// Search for any emoji symbol, return true if found -bool wstring_has_emoji(const LLWString& wstr) -{ - for (const llwchar& wch : wstr) - { - if (LLStringOps::isEmoji(wch)) - return true; - } - - return false; -} - -// Cut emoji symbols if exist -bool wstring_remove_emojis(LLWString& wstr) -{ - bool found = false; - for (size_t i = 0; i < wstr.size(); ++i) - { - if (LLStringOps::isEmoji(wstr[i])) - { - wstr.erase(i--, 1); - found = true; - } - } - return found; -} - -// Cut emoji symbols if exist -bool utf8str_remove_emojis(std::string& utf8str) -{ - LLWString wstr = utf8str_to_wstring(utf8str); - if (!wstring_remove_emojis(wstr)) - return false; - utf8str = wstring_to_utf8str(wstr); - return true; -} - -#if LL_WINDOWS -unsigned int ll_wstring_default_code_page() -{ - return CP_UTF8; -} - -std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned int code_page) -{ - std::string out; - if(in) - { - int len_out = WideCharToMultiByte( - code_page, - 0, - in, - len_in, - NULL, - 0, - 0, - 0); - // We will need two more bytes for the double NULL ending - // created in WideCharToMultiByte(). - char* pout = new char [len_out + 2]; - memset(pout, 0, len_out + 2); - if(pout) - { - WideCharToMultiByte( - code_page, - 0, - in, - len_in, - pout, - len_out, - 0, - 0); - out.assign(pout); - delete[] pout; - } - } - return out; -} - -std::wstring ll_convert_string_to_wide(const char* in, size_t len, unsigned int code_page) -{ - // From review: - // We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input, - // plus one for a null terminator, and be guaranteed to not overflow. - - // Normally, I'd call that sort of thing premature optimization, - // but we *are* seeing string operations taking a bunch of time, especially when constructing widgets. -// int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0); - - // reserve an output buffer that will be destroyed on exit, with a place - // to put NULL terminator - std::vector<wchar_t> w_out(len + 1); - - memset(&w_out[0], 0, w_out.size()); - int real_output_str_len = MultiByteToWideChar(code_page, 0, in, len, - &w_out[0], w_out.size() - 1); - - //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858. - w_out[real_output_str_len] = 0; - - // construct string<wchar_t> from our temporary output buffer - return {&w_out[0]}; -} - -LLWString ll_convert_wide_to_wstring(const wchar_t* in, size_t len) -{ - // Whether or not std::wstring and llutf16string are distinct types, they - // both hold UTF-16LE characters. (See header file comments.) Pretend this - // wchar_t* sequence is really a U16* sequence and use the conversion we - // define above. - return utf16str_to_wstring(reinterpret_cast<const U16*>(in), len); -} - -std::wstring ll_convert_wstring_to_wide(const llwchar* in, size_t len) -{ - // first, convert to llutf16string, for which we have a real implementation - auto utf16str{ wstring_to_utf16str(in, len) }; - // then, because each U16 char must be UTF-16LE encoded, pretend the U16* - // string pointer is a wchar_t* and instantiate a std::wstring of the same - // length. - return { reinterpret_cast<const wchar_t*>(utf16str.c_str()), utf16str.length() }; -} - -std::string ll_convert_string_to_utf8_string(const std::string& in) -{ - // If you pass code_page, you must also pass length, otherwise the code - // page parameter will be mistaken for length. - auto w_mesg = ll_convert_string_to_wide(in, in.length(), CP_ACP); - // CP_UTF8 is default -- see ll_wstring_default_code_page() above. - return ll_convert_wide_to_string(w_mesg); -} - -namespace -{ - -void HeapFree_deleter(void* ptr) -{ - // instead of LocalFree(), per https://stackoverflow.com/a/31541205 - HeapFree(GetProcessHeap(), NULL, ptr); -} - -} // anonymous namespace - -template<> -std::wstring windows_message<std::wstring>(DWORD error) -{ - // derived from https://stackoverflow.com/a/455533 - wchar_t* rawptr = nullptr; - auto okay = FormatMessageW( - // use system message tables for GetLastError() codes - FORMAT_MESSAGE_FROM_SYSTEM | - // internally allocate buffer and return its pointer - FORMAT_MESSAGE_ALLOCATE_BUFFER | - // you cannot pass insertion parameters (thanks Gandalf) - FORMAT_MESSAGE_IGNORE_INSERTS | - // ignore line breaks in message definition text - FORMAT_MESSAGE_MAX_WIDTH_MASK, - NULL, // lpSource, unused with FORMAT_MESSAGE_FROM_SYSTEM - error, // dwMessageId - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId - (LPWSTR)&rawptr, // lpBuffer: force-cast wchar_t** to wchar_t* - 0, // nSize, unused with FORMAT_MESSAGE_ALLOCATE_BUFFER - NULL); // Arguments, unused - - // make a unique_ptr from rawptr so it gets cleaned up properly - std::unique_ptr<wchar_t, void(*)(void*)> bufferptr(rawptr, HeapFree_deleter); - - if (okay && bufferptr) - { - // got the message, return it ('okay' is length in characters) - return { bufferptr.get(), okay }; - } - - // did not get the message, synthesize one - auto format_message_error = GetLastError(); - std::wostringstream out; - out << L"GetLastError() " << error << L" (FormatMessageW() failed with " - << format_message_error << L")"; - return out.str(); -} - -boost::optional<std::wstring> llstring_getoptenv(const std::string& key) -{ - auto wkey = ll_convert_string_to_wide(key); - // Take a wild guess as to how big the buffer should be. - std::vector<wchar_t> buffer(1024); - auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); - // If our initial guess was too short, n will indicate the size (in - // wchar_t's) that buffer should have been, including the terminating nul. - if (n > (buffer.size() - 1)) - { - // make it big enough - buffer.resize(n); - // and try again - n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); - } - // did that (ultimately) succeed? - if (n) - { - // great, return populated boost::optional - return boost::optional<std::wstring>(&buffer[0]); - } - - // not successful - auto last_error = GetLastError(); - // Don't bother warning for NOT_FOUND; that's an expected case - if (last_error != ERROR_ENVVAR_NOT_FOUND) - { - LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: " - << windows_message<std::string>(last_error) << LL_ENDL; - } - // return empty boost::optional - return {}; -} - -#else // ! LL_WINDOWS - -boost::optional<std::string> llstring_getoptenv(const std::string& key) -{ - auto found = getenv(key.c_str()); - if (found) - { - // return populated boost::optional - return boost::optional<std::string>(found); - } - else - { - // return empty boost::optional - return {}; - } -} - -#endif // ! LL_WINDOWS - -long LLStringOps::sPacificTimeOffset = 0; -long LLStringOps::sLocalTimeOffset = 0; -bool LLStringOps::sPacificDaylightTime = 0; -std::map<std::string, std::string> LLStringOps::datetimeToCodes; - -std::vector<std::string> LLStringOps::sWeekDayList; -std::vector<std::string> LLStringOps::sWeekDayShortList; -std::vector<std::string> LLStringOps::sMonthList; -std::vector<std::string> LLStringOps::sMonthShortList; - - -std::string LLStringOps::sDayFormat; -std::string LLStringOps::sAM; -std::string LLStringOps::sPM; - -// static -bool LLStringOps::isEmoji(llwchar a) -{ -#if 0 // Do not consider special characters that might have a corresponding - // glyph in the monochorme fallback fonts as a "genuine" emoji. HB - return a == 0xa9 || a == 0xae || (a >= 0x2000 && a < 0x3300) || - (a >= 0x1f000 && a < 0x20000); -#else - // These are indeed "genuine" emojis, we *do want* rendered as such. HB - return a >= 0x1f000 && a < 0x20000; -#endif -} - -S32 LLStringOps::collate(const llwchar* a, const llwchar* b) -{ - #if LL_WINDOWS - // in Windows, wide string functions operator on 16-bit strings, - // not the proper 32 bit wide string - return strcmp(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str()); - #else - return wcscoll(a, b); - #endif -} - -void LLStringOps::setupDatetimeInfo (bool daylight) -{ - time_t nowT, localT, gmtT; - struct tm * tmpT; - - nowT = time (NULL); - - tmpT = gmtime (&nowT); - gmtT = mktime (tmpT); - - tmpT = localtime (&nowT); - localT = mktime (tmpT); - - sLocalTimeOffset = (long) (gmtT - localT); - if (tmpT->tm_isdst) - { - sLocalTimeOffset -= 60 * 60; // 1 hour - } - - sPacificDaylightTime = daylight; - sPacificTimeOffset = (sPacificDaylightTime? 7 : 8 ) * 60 * 60; - - datetimeToCodes["wkday"] = "%a"; // Thu - datetimeToCodes["weekday"] = "%A"; // Thursday - datetimeToCodes["year4"] = "%Y"; // 2009 - datetimeToCodes["year"] = "%Y"; // 2009 - datetimeToCodes["year2"] = "%y"; // 09 - datetimeToCodes["mth"] = "%b"; // Aug - datetimeToCodes["month"] = "%B"; // August - datetimeToCodes["mthnum"] = "%m"; // 08 - datetimeToCodes["day"] = "%d"; // 31 - datetimeToCodes["sday"] = "%-d"; // 9 - datetimeToCodes["hour24"] = "%H"; // 14 - datetimeToCodes["hour"] = "%H"; // 14 - datetimeToCodes["hour12"] = "%I"; // 02 - datetimeToCodes["min"] = "%M"; // 59 - datetimeToCodes["ampm"] = "%p"; // AM - datetimeToCodes["second"] = "%S"; // 59 - datetimeToCodes["timezone"] = "%Z"; // PST -} - -void tokenizeStringToArray(const std::string& data, std::vector<std::string>& output) -{ - output.clear(); - size_t length = data.size(); - - // tokenize it and put it in the array - std::string cur_word; - for(size_t i = 0; i < length; ++i) - { - if(data[i] == ':') - { - output.push_back(cur_word); - cur_word.clear(); - } - else - { - cur_word.append(1, data[i]); - } - } - output.push_back(cur_word); -} - -void LLStringOps::setupWeekDaysNames(const std::string& data) -{ - tokenizeStringToArray(data,sWeekDayList); -} -void LLStringOps::setupWeekDaysShortNames(const std::string& data) -{ - tokenizeStringToArray(data,sWeekDayShortList); -} -void LLStringOps::setupMonthNames(const std::string& data) -{ - tokenizeStringToArray(data,sMonthList); -} -void LLStringOps::setupMonthShortNames(const std::string& data) -{ - tokenizeStringToArray(data,sMonthShortList); -} -void LLStringOps::setupDayFormat(const std::string& data) -{ - sDayFormat = data; -} - - -std::string LLStringOps::getDatetimeCode (std::string key) -{ - std::map<std::string, std::string>::iterator iter; - - iter = datetimeToCodes.find (key); - if (iter != datetimeToCodes.end()) - { - return iter->second; - } - else - { - return std::string(""); - } -} - -std::string LLStringOps::getReadableNumber(F64 num) -{ - if (fabs(num)>=1e9) - { - return llformat("%.2lfB", num / 1e9); - } - else if (fabs(num)>=1e6) - { - return llformat("%.2lfM", num / 1e6); - } - else if (fabs(num)>=1e3) - { - return llformat("%.2lfK", num / 1e3); - } - else - { - return llformat("%.2lf", num); - } -} - -namespace LLStringFn -{ - // NOTE - this restricts output to ascii - void replace_nonprintable_in_ascii(std::basic_string<char>& string, char replacement) - { - const char MIN = 0x20; - std::basic_string<char>::size_type len = string.size(); - for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii) - { - if(string[ii] < MIN) - { - string[ii] = replacement; - } - } - } - - - // NOTE - this restricts output to ascii - void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str, - char replacement) - { - const char MIN = 0x20; - const char PIPE = 0x7c; - std::basic_string<char>::size_type len = str.size(); - for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii) - { - if( (str[ii] < MIN) || (str[ii] == PIPE) ) - { - str[ii] = replacement; - } - } - } - - // https://wiki.lindenlab.com/wiki/Unicode_Guidelines has details on - // allowable code points for XML. Specifically, they are: - // 0x09, 0x0a, 0x0d, and 0x20 on up. JC - std::string strip_invalid_xml(const std::string& instr) - { - std::string output; - output.reserve( instr.size() ); - std::string::const_iterator it = instr.begin(); - while (it != instr.end()) - { - // Must compare as unsigned for >= - // Test most likely match first - const unsigned char c = (unsigned char)*it; - if ( c >= (unsigned char)0x20 // SPACE - || c == (unsigned char)0x09 // TAB - || c == (unsigned char)0x0a // LINE_FEED - || c == (unsigned char)0x0d ) // CARRIAGE_RETURN - { - output.push_back(c); - } - ++it; - } - return output; - } - - /** - * @brief Replace all control characters (c < 0x20) with replacement in - * string. - */ - void replace_ascii_controlchars(std::basic_string<char>& string, char replacement) - { - const unsigned char MIN = 0x20; - std::basic_string<char>::size_type len = string.size(); - for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii) - { - const unsigned char c = (unsigned char) string[ii]; - if(c < MIN) - { - string[ii] = replacement; - } - } - } -} - -//////////////////////////////////////////////////////////// - -// Forward specialization of LLStringUtil::format before use in LLStringUtil::formatDatetime. -template<> -S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions); - -//static -template<> -void LLStringUtil::getTokens(const std::string& instr, std::vector<std::string >& tokens, const std::string& delims) -{ - // Starting at offset 0, scan forward for the next non-delimiter. We're - // done when the only characters left in 'instr' are delimiters. - for (std::string::size_type begIdx, endIdx = 0; - (begIdx = instr.find_first_not_of (delims, endIdx)) != std::string::npos; ) - { - // Found a non-delimiter. After that, find the next delimiter. - endIdx = instr.find_first_of (delims, begIdx); - if (endIdx == std::string::npos) - { - // No more delimiters: this token extends to the end of the string. - endIdx = instr.length(); - } - - // extract the token between begIdx and endIdx; substr() needs length - std::string currToken(instr.substr(begIdx, endIdx - begIdx)); - LLStringUtil::trim (currToken); - tokens.push_back(currToken); - // next scan past delimiters starts at endIdx - } -} - -template<> -LLStringUtil::size_type LLStringUtil::getSubstitution(const std::string& instr, size_type& start, std::vector<std::string>& tokens) -{ - const std::string delims (","); - - // Find the first [ - size_type pos1 = instr.find('[', start); - if (pos1 == std::string::npos) - return std::string::npos; - - //Find the first ] after the initial [ - size_type pos2 = instr.find(']', pos1); - if (pos2 == std::string::npos) - return std::string::npos; - - // Find the last [ before ] in case of nested [[]] - pos1 = instr.find_last_of('[', pos2-1); - if (pos1 == std::string::npos || pos1 < start) - return std::string::npos; - - getTokens(std::string(instr,pos1+1,pos2-pos1-1), tokens, delims); - start = pos2+1; - - return pos1; -} - -// static -template<> -bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const format_map_t& substitutions) -{ - // see if we have a replacement for the bracketed string (without the brackets) - // test first using has() because if we just look up with operator[] we get back an - // empty string even if the value is missing. We want to distinguish between - // missing replacements and deliberately empty replacement strings. - format_map_t::const_iterator iter = substitutions.find(token); - if (iter != substitutions.end()) - { - replacement = iter->second; - return true; - } - // if not, see if there's one WITH brackets - iter = substitutions.find(std::string("[" + token + "]")); - if (iter != substitutions.end()) - { - replacement = iter->second; - return true; - } - - return false; -} - -// static -template<> -bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const LLSD& substitutions) -{ - // see if we have a replacement for the bracketed string (without the brackets) - // test first using has() because if we just look up with operator[] we get back an - // empty string even if the value is missing. We want to distinguish between - // missing replacements and deliberately empty replacement strings. - if (substitutions.has(token)) - { - replacement = substitutions[token].asString(); - return true; - } - // if not, see if there's one WITH brackets - else if (substitutions.has(std::string("[" + token + "]"))) - { - replacement = substitutions[std::string("[" + token + "]")].asString(); - return true; - } - - return false; -} - -//static -template<> -void LLStringUtil::setLocale(std::string inLocale) -{ - sLocale = inLocale; -}; - -//static -template<> -std::string LLStringUtil::getLocale(void) -{ - return sLocale; -}; - -// static -template<> -void LLStringUtil::formatNumber(std::string& numStr, std::string decimals) -{ - std::stringstream strStream; - S32 intDecimals = 0; - - convertToS32 (decimals, intDecimals); - if (!sLocale.empty()) - { - // std::locale() throws if the locale is unknown! (EXT-7926) - try - { - strStream.imbue(std::locale(sLocale.c_str())); - } catch (const std::exception &) - { - LL_WARNS_ONCE("Locale") << "Cannot set locale to " << sLocale << LL_ENDL; - } - } - - if (!intDecimals) - { - S32 intStr; - - if (convertToS32(numStr, intStr)) - { - strStream << intStr; - numStr = strStream.str(); - } - } - else - { - F32 floatStr; - - if (convertToF32(numStr, floatStr)) - { - strStream << std::fixed << std::showpoint << std::setprecision(intDecimals) << floatStr; - numStr = strStream.str(); - } - } -} - -// static -template<> -bool LLStringUtil::formatDatetime(std::string& replacement, std::string token, - std::string param, S32 secFromEpoch) -{ - if (param == "local") // local - { - secFromEpoch -= LLStringOps::getLocalTimeOffset(); - } - else if (param != "utc") // slt - { - secFromEpoch -= LLStringOps::getPacificTimeOffset(); - } - - // if never fell into those two ifs above, param must be utc - if (secFromEpoch < 0) secFromEpoch = 0; - - LLDate datetime((F64)secFromEpoch); - std::string code = LLStringOps::getDatetimeCode (token); - - // special case to handle timezone - if (code == "%Z") { - if (param == "utc") - { - replacement = "GMT"; - } - else if (param == "local") - { - replacement = ""; // user knows their own timezone - } - else - { -#if 0 - // EXT-1565 : Zai Lynch, James Linden : 15/Oct/09 - // [BSI] Feedback: Viewer clock mentions SLT, but would prefer it to show PST/PDT - // "slt" = Second Life Time, which is deprecated. - // If not utc or user local time, fallback to Pacific time - replacement = LLStringOps::getPacificDaylightTime() ? "PDT" : "PST"; -#else - // SL-20370 : Steeltoe Linden : 29/Sep/23 - // Change "PDT" to "SLT" on menu bar - replacement = "SLT"; -#endif - } - return true; - } - - //EXT-7013 - //few codes are not suppotred by strtime function (example - weekdays for Japanise) - //so use predefined ones - - //if sWeekDayList is not empty than current locale doesn't support - //weekday name. - time_t loc_seconds = (time_t) secFromEpoch; - if(LLStringOps::sWeekDayList.size() == 7 && code == "%A") - { - struct tm * gmt = gmtime (&loc_seconds); - replacement = LLStringOps::sWeekDayList[gmt->tm_wday]; - } - else if(LLStringOps::sWeekDayShortList.size() == 7 && code == "%a") - { - struct tm * gmt = gmtime (&loc_seconds); - replacement = LLStringOps::sWeekDayShortList[gmt->tm_wday]; - } - else if(LLStringOps::sMonthList.size() == 12 && code == "%B") - { - struct tm * gmt = gmtime (&loc_seconds); - replacement = LLStringOps::sMonthList[gmt->tm_mon]; - } - else if( !LLStringOps::sDayFormat.empty() && code == "%d" ) - { - struct tm * gmt = gmtime (&loc_seconds); - LLStringUtil::format_map_t args; - args["[MDAY]"] = llformat ("%d", gmt->tm_mday); - replacement = LLStringOps::sDayFormat; - LLStringUtil::format(replacement, args); - } - else if (code == "%-d") - { - struct tm * gmt = gmtime (&loc_seconds); - replacement = llformat ("%d", gmt->tm_mday); // day of the month without leading zero - } - else if( !LLStringOps::sAM.empty() && !LLStringOps::sPM.empty() && code == "%p" ) - { - struct tm * gmt = gmtime (&loc_seconds); - if(gmt->tm_hour<12) - { - replacement = LLStringOps::sAM; - } - else - { - replacement = LLStringOps::sPM; - } - } - else - { - replacement = datetime.toHTTPDateString(code); - } - - // *HACK: delete leading zero from hour string in case 'hour12' (code = %I) time format - // to show time without leading zero, e.g. 08:16 -> 8:16 (EXT-2738). - // We could have used '%l' format instead, but it's not supported by Windows. - if(code == "%I" && token == "hour12" && replacement.at(0) == '0') - { - replacement = replacement.at(1); - } - - return !code.empty(); -} - -// LLStringUtil::format recogizes the following patterns. -// All substitutions *must* be encased in []'s in the input string. -// The []'s are optional in the substitution map. -// [FOO_123] -// [FOO,number,precision] -// [FOO,datetime,format] - - -// static -template<> -S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; - S32 res = 0; - - std::string output; - std::vector<std::string> tokens; - - std::string::size_type start = 0; - std::string::size_type prev_start = 0; - std::string::size_type key_start = 0; - while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) - { - output += std::string(s, prev_start, key_start-prev_start); - prev_start = start; - - bool found_replacement = false; - std::string replacement; - - if (tokens.size() == 0) - { - found_replacement = false; - } - else if (tokens.size() == 1) - { - found_replacement = simpleReplacement (replacement, tokens[0], substitutions); - } - else if (tokens[1] == "number") - { - std::string param = "0"; - - if (tokens.size() > 2) param = tokens[2]; - found_replacement = simpleReplacement (replacement, tokens[0], substitutions); - if (found_replacement) formatNumber (replacement, param); - } - else if (tokens[1] == "datetime") - { - std::string param; - if (tokens.size() > 2) param = tokens[2]; - - format_map_t::const_iterator iter = substitutions.find("datetime"); - if (iter != substitutions.end()) - { - S32 secFromEpoch = 0; - BOOL r = LLStringUtil::convertToS32(iter->second, secFromEpoch); - if (r) - { - found_replacement = formatDatetime(replacement, tokens[0], param, secFromEpoch); - } - } - } - - if (found_replacement) - { - output += replacement; - res++; - } - else - { - // we had no replacement, use the string as is - // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); - } - tokens.clear(); - } - // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; - return res; -} - -//static -template<> -S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; - S32 res = 0; - - if (!substitutions.isMap()) - { - return res; - } - - std::string output; - std::vector<std::string> tokens; - - std::string::size_type start = 0; - std::string::size_type prev_start = 0; - std::string::size_type key_start = 0; - while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) - { - output += std::string(s, prev_start, key_start-prev_start); - prev_start = start; - - bool found_replacement = false; - std::string replacement; - - if (tokens.size() == 0) - { - found_replacement = false; - } - else if (tokens.size() == 1) - { - found_replacement = simpleReplacement (replacement, tokens[0], substitutions); - } - else if (tokens[1] == "number") - { - std::string param = "0"; - - if (tokens.size() > 2) param = tokens[2]; - found_replacement = simpleReplacement (replacement, tokens[0], substitutions); - if (found_replacement) formatNumber (replacement, param); - } - else if (tokens[1] == "datetime") - { - std::string param; - if (tokens.size() > 2) param = tokens[2]; - - S32 secFromEpoch = (S32) substitutions["datetime"].asInteger(); - found_replacement = formatDatetime (replacement, tokens[0], param, secFromEpoch); - } - - if (found_replacement) - { - output += replacement; - res++; - } - else - { - // we had no replacement, use the string as is - // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); - } - tokens.clear(); - } - // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; - return res; -} - -//////////////////////////////////////////////////////////// -// Testing - -#ifdef _DEBUG - -template<class T> -void LLStringUtilBase<T>::testHarness() -{ - std::string s1; - - llassert( s1.c_str() == NULL ); - llassert( s1.size() == 0 ); - llassert( s1.empty() ); - - std::string s2( "hello"); - llassert( !strcmp( s2.c_str(), "hello" ) ); - llassert( s2.size() == 5 ); - llassert( !s2.empty() ); - std::string s3( s2 ); - - llassert( "hello" == s2 ); - llassert( s2 == "hello" ); - llassert( s2 > "gello" ); - llassert( "gello" < s2 ); - llassert( "gello" != s2 ); - llassert( s2 != "gello" ); - - std::string s4 = s2; - llassert( !s4.empty() ); - s4.empty(); - llassert( s4.empty() ); - - std::string s5(""); - llassert( s5.empty() ); - - llassert( isValidIndex(s5, 0) ); - llassert( !isValidIndex(s5, 1) ); - - s3 = s2; - s4 = "hello again"; - - s4 += "!"; - s4 += s4; - llassert( s4 == "hello again!hello again!" ); - - - std::string s6 = s2 + " " + s2; - std::string s7 = s6; - llassert( s6 == s7 ); - llassert( !( s6 != s7) ); - llassert( !(s6 < s7) ); - llassert( !(s6 > s7) ); - - llassert( !(s6 == "hi")); - llassert( s6 == "hello hello"); - llassert( s6 < "hi"); - - llassert( s6[1] == 'e' ); - s6[1] = 'f'; - llassert( s6[1] == 'f' ); - - s2.erase( 4, 1 ); - llassert( s2 == "hell"); - s2.insert( 0, "y" ); - llassert( s2 == "yhell"); - s2.erase( 1, 3 ); - llassert( s2 == "yl"); - s2.insert( 1, "awn, don't yel"); - llassert( s2 == "yawn, don't yell"); - - std::string s8 = s2.substr( 6, 5 ); - llassert( s8 == "don't" ); - - std::string s9 = " \t\ntest \t\t\n "; - trim(s9); - llassert( s9 == "test" ); - - s8 = "abc123&*(ABC"; - - s9 = s8; - toUpper(s9); - llassert( s9 == "ABC123&*(ABC" ); - - s9 = s8; - toLower(s9); - llassert( s9 == "abc123&*(abc" ); - - - std::string s10( 10, 'x' ); - llassert( s10 == "xxxxxxxxxx" ); - - std::string s11( "monkey in the middle", 7, 2 ); - llassert( s11 == "in" ); - - std::string s12; //empty - s12 += "foo"; - llassert( s12 == "foo" ); - - std::string s13; //empty - s13 += 'f'; - llassert( s13 == "f" ); -} - - -#endif // _DEBUG +/**
+ * @file llstring.cpp
+ * @brief String utility functions and the std::string class.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llstring.h"
+#include "llerror.h"
+#include "llfasttimer.h"
+#include "llsd.h"
+#include <vector>
+
+#if LL_WINDOWS
+#include "llwin32headerslean.h"
+#include <winnls.h> // for WideCharToMultiByte
+#endif
+
+std::string ll_safe_string(const char* in)
+{
+ if(in) return std::string(in);
+ return std::string();
+}
+
+std::string ll_safe_string(const char* in, S32 maxlen)
+{
+ if(in && maxlen > 0 ) return std::string(in, maxlen);
+
+ return std::string();
+}
+
+bool is_char_hex(char hex)
+{
+ if((hex >= '0') && (hex <= '9'))
+ {
+ return true;
+ }
+ else if((hex >= 'a') && (hex <='f'))
+ {
+ return true;
+ }
+ else if((hex >= 'A') && (hex <='F'))
+ {
+ return true;
+ }
+ return false; // uh - oh, not hex any more...
+}
+
+U8 hex_as_nybble(char hex)
+{
+ if((hex >= '0') && (hex <= '9'))
+ {
+ return (U8)(hex - '0');
+ }
+ else if((hex >= 'a') && (hex <='f'))
+ {
+ return (U8)(10 + hex - 'a');
+ }
+ else if((hex >= 'A') && (hex <='F'))
+ {
+ return (U8)(10 + hex - 'A');
+ }
+ return 0; // uh - oh, not hex any more...
+}
+
+bool iswindividual(llwchar elem)
+{
+ U32 cur_char = (U32)elem;
+ bool result = false;
+ if (0x2E80<= cur_char && cur_char <= 0x9FFF)
+ {
+ result = true;
+ }
+ else if (0xAC00<= cur_char && cur_char <= 0xD7A0 )
+ {
+ result = true;
+ }
+ else if (0xF900<= cur_char && cur_char <= 0xFA60 )
+ {
+ result = true;
+ }
+ return result;
+}
+
+bool _read_file_into_string(std::string& str, const std::string& filename)
+{
+ llifstream ifs(filename.c_str(), llifstream::binary);
+ if (!ifs.is_open())
+ {
+ LL_INFOS() << "Unable to open file " << filename << LL_ENDL;
+ return false;
+ }
+
+ std::ostringstream oss;
+
+ oss << ifs.rdbuf();
+ str = oss.str();
+ ifs.close();
+ return true;
+}
+
+
+
+
+// See http://www.unicode.org/Public/BETA/CVTUTF-1-2/ConvertUTF.c
+// for the Unicode implementation - this doesn't match because it was written before finding
+// it.
+
+
+std::ostream& operator<<(std::ostream &s, const LLWString &wstr)
+{
+ std::string utf8_str = wstring_to_utf8str(wstr);
+ s << utf8_str;
+ return s;
+}
+
+std::string rawstr_to_utf8(const std::string& raw)
+{
+ LLWString wstr(utf8str_to_wstring(raw));
+ return wstring_to_utf8str(wstr);
+}
+
+std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars)
+{
+ U32 cur_char = (U32)in_char;
+ char* base = outchars;
+ if (cur_char < 0x80)
+ {
+ *outchars++ = (U8)cur_char;
+ }
+ else if (cur_char < 0x800)
+ {
+ *outchars++ = 0xC0 | (cur_char >> 6);
+ *outchars++ = 0x80 | (cur_char & 0x3F);
+ }
+ else if (cur_char < 0x10000)
+ {
+ *outchars++ = 0xE0 | (cur_char >> 12);
+ *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
+ *outchars++ = 0x80 | (cur_char & 0x3F);
+ }
+ else if (cur_char < 0x200000)
+ {
+ *outchars++ = 0xF0 | (cur_char >> 18);
+ *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F);
+ *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
+ *outchars++ = 0x80 | (cur_char & 0x3F);
+ }
+ else if (cur_char < 0x4000000)
+ {
+ *outchars++ = 0xF8 | (cur_char >> 24);
+ *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F);
+ *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F);
+ *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
+ *outchars++ = 0x80 | (cur_char & 0x3F);
+ }
+ else if (cur_char < 0x80000000)
+ {
+ *outchars++ = 0xFC | (cur_char >> 30);
+ *outchars++ = 0x80 | ((cur_char >> 24) & 0x3F);
+ *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F);
+ *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F);
+ *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
+ *outchars++ = 0x80 | (cur_char & 0x3F);
+ }
+ else
+ {
+ LL_WARNS() << "Invalid Unicode character " << cur_char << "!" << LL_ENDL;
+ *outchars++ = LL_UNKNOWN_CHAR;
+ }
+ return outchars - base;
+}
+
+auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar)
+{
+ const U16* base = inchars;
+ U16 cur_char = *inchars++;
+ llwchar char32 = cur_char;
+ if ((cur_char >= 0xD800) && (cur_char <= 0xDFFF))
+ {
+ // Surrogates
+ char32 = ((llwchar)(cur_char - 0xD800)) << 10;
+ cur_char = *inchars++;
+ char32 += (llwchar)(cur_char - 0xDC00) + 0x0010000UL;
+ }
+ else
+ {
+ char32 = (llwchar)cur_char;
+ }
+ *outchar = char32;
+ return inchars - base;
+}
+
+llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len)
+{
+ llutf16string out;
+
+ S32 i = 0;
+ while (i < len)
+ {
+ U32 cur_char = utf32str[i];
+ if (cur_char > 0xFFFF)
+ {
+ out += (0xD7C0 + (cur_char >> 10));
+ out += (0xDC00 | (cur_char & 0x3FF));
+ }
+ else
+ {
+ out += cur_char;
+ }
+ i++;
+ }
+ return out;
+}
+
+llutf16string utf8str_to_utf16str( const char* utf8str, size_t len )
+{
+ LLWString wstr = utf8str_to_wstring ( utf8str, len );
+ return wstring_to_utf16str ( wstr );
+}
+
+LLWString utf16str_to_wstring(const U16* utf16str, size_t len)
+{
+ LLWString wout;
+ if (len == 0) return wout;
+
+ S32 i = 0;
+ const U16* chars16 = utf16str;
+ while (i < len)
+ {
+ llwchar cur_char;
+ i += utf16chars_to_wchar(chars16+i, &cur_char);
+ wout += cur_char;
+ }
+ return wout;
+}
+
+// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string.
+S32 utf16str_wstring_length(const llutf16string &utf16str, const S32 utf16_len)
+{
+ S32 surrogate_pairs = 0;
+ // ... craziness to make gcc happy (llutf16string.c_str() is tweaked on linux):
+ const U16 *const utf16_chars = &(*(utf16str.begin()));
+ S32 i = 0;
+ while (i < utf16_len)
+ {
+ const U16 c = utf16_chars[i++];
+ if (c >= 0xD800 && c <= 0xDBFF) // See http://en.wikipedia.org/wiki/UTF-16
+ { // Have first byte of a surrogate pair
+ if (i >= utf16_len)
+ {
+ break;
+ }
+ const U16 d = utf16_chars[i];
+ if (d >= 0xDC00 && d <= 0xDFFF)
+ { // Have valid second byte of a surrogate pair
+ surrogate_pairs++;
+ i++;
+ }
+ }
+ }
+ return utf16_len - surrogate_pairs;
+}
+
+// Length in utf16string (UTF-16) of wlen wchars beginning at woffset.
+S32 wstring_utf16_length(const LLWString &wstr, const S32 woffset, const S32 wlen)
+{
+ const S32 end = llmin((S32)wstr.length(), woffset + wlen);
+ if (end < woffset)
+ {
+ return 0;
+ }
+ else
+ {
+ S32 length = end - woffset;
+ for (S32 i = woffset; i < end; i++)
+ {
+ if (wstr[i] >= 0x10000)
+ {
+ length++;
+ }
+ }
+ return length;
+ }
+}
+
+// Given a wstring and an offset in it, returns the length as wstring (i.e.,
+// number of llwchars) of the longest substring that starts at the offset
+// and whose equivalent utf-16 string does not exceeds the given utf16_length.
+S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, bool *unaligned)
+{
+ const auto end = wstr.length();
+ bool u{ false };
+ S32 n = woffset + utf16_length;
+ S32 i = woffset;
+ while (i < end)
+ {
+ if (wstr[i] >= 0x10000)
+ {
+ --n;
+ }
+ if (i >= n)
+ {
+ u = (i > n);
+ break;
+ }
+ i++;
+ }
+ if (unaligned)
+ {
+ *unaligned = u;
+ }
+ return i - woffset;
+}
+
+S32 wchar_utf8_length(const llwchar wc)
+{
+ if (wc < 0x80)
+ {
+ return 1;
+ }
+ else if (wc < 0x800)
+ {
+ return 2;
+ }
+ else if (wc < 0x10000)
+ {
+ return 3;
+ }
+ else if (wc < 0x200000)
+ {
+ return 4;
+ }
+ else if (wc < 0x4000000)
+ {
+ return 5;
+ }
+ else
+ {
+ return 6;
+ }
+}
+
+std::string wchar_utf8_preview(const llwchar wc)
+{
+ std::ostringstream oss;
+ oss << std::hex << std::uppercase << (U32)wc;
+
+ U8 out_bytes[8];
+ U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes);
+
+ if (size > 1)
+ {
+ oss << " [";
+ for (U32 i = 0; i < size; ++i)
+ {
+ if (i)
+ {
+ oss << ", ";
+ }
+ oss << (int)out_bytes[i];
+ }
+ oss << "]";
+ }
+
+ return oss.str();
+}
+
+S32 wstring_utf8_length(const LLWString& wstr)
+{
+ S32 len = 0;
+ for (S32 i = 0; i < (S32)wstr.length(); i++)
+ {
+ len += wchar_utf8_length(wstr[i]);
+ }
+ return len;
+}
+
+LLWString utf8str_to_wstring(const char* utf8str, size_t len)
+{
+ LLWString wout;
+
+ S32 i = 0;
+ while (i < len)
+ {
+ llwchar unichar;
+ U8 cur_char = utf8str[i];
+
+ if (cur_char < 0x80)
+ {
+ // Ascii character, just add it
+ unichar = cur_char;
+ }
+ else
+ {
+ S32 cont_bytes = 0;
+ if ((cur_char >> 5) == 0x6) // Two byte UTF8 -> 1 UTF32
+ {
+ unichar = (0x1F&cur_char);
+ cont_bytes = 1;
+ }
+ else if ((cur_char >> 4) == 0xe) // Three byte UTF8 -> 1 UTF32
+ {
+ unichar = (0x0F&cur_char);
+ cont_bytes = 2;
+ }
+ else if ((cur_char >> 3) == 0x1e) // Four byte UTF8 -> 1 UTF32
+ {
+ unichar = (0x07&cur_char);
+ cont_bytes = 3;
+ }
+ else if ((cur_char >> 2) == 0x3e) // Five byte UTF8 -> 1 UTF32
+ {
+ unichar = (0x03&cur_char);
+ cont_bytes = 4;
+ }
+ else if ((cur_char >> 1) == 0x7e) // Six byte UTF8 -> 1 UTF32
+ {
+ unichar = (0x01&cur_char);
+ cont_bytes = 5;
+ }
+ else
+ {
+ wout += LL_UNKNOWN_CHAR;
+ ++i;
+ continue;
+ }
+
+ // Check that this character doesn't go past the end of the string
+ auto end = (len < (i + cont_bytes)) ? len : (i + cont_bytes);
+ do
+ {
+ ++i;
+
+ cur_char = utf8str[i];
+ if ( (cur_char >> 6) == 0x2 )
+ {
+ unichar <<= 6;
+ unichar += (0x3F&cur_char);
+ }
+ else
+ {
+ // Malformed sequence - roll back to look at this as a new char
+ unichar = LL_UNKNOWN_CHAR;
+ --i;
+ break;
+ }
+ } while(i < end);
+
+ // Handle overlong characters and NULL characters
+ if ( ((cont_bytes == 1) && (unichar < 0x80))
+ || ((cont_bytes == 2) && (unichar < 0x800))
+ || ((cont_bytes == 3) && (unichar < 0x10000))
+ || ((cont_bytes == 4) && (unichar < 0x200000))
+ || ((cont_bytes == 5) && (unichar < 0x4000000)) )
+ {
+ unichar = LL_UNKNOWN_CHAR;
+ }
+ }
+
+ wout += unichar;
+ ++i;
+ }
+ return wout;
+}
+
+std::string wstring_to_utf8str(const llwchar* utf32str, size_t len)
+{
+ std::string out;
+
+ S32 i = 0;
+ while (i < len)
+ {
+ char tchars[8]; /* Flawfinder: ignore */
+ auto n = wchar_to_utf8chars(utf32str[i], tchars);
+ tchars[n] = 0;
+ out += tchars;
+ i++;
+ }
+ return out;
+}
+
+std::string utf16str_to_utf8str(const U16* utf16str, size_t len)
+{
+ return wstring_to_utf8str(utf16str_to_wstring(utf16str, len));
+}
+
+std::string utf8str_trim(const std::string& utf8str)
+{
+ LLWString wstr = utf8str_to_wstring(utf8str);
+ LLWStringUtil::trim(wstr);
+ return wstring_to_utf8str(wstr);
+}
+
+
+std::string utf8str_tolower(const std::string& utf8str)
+{
+ LLWString out_str = utf8str_to_wstring(utf8str);
+ LLWStringUtil::toLower(out_str);
+ return wstring_to_utf8str(out_str);
+}
+
+
+S32 utf8str_compare_insensitive(const std::string& lhs, const std::string& rhs)
+{
+ LLWString wlhs = utf8str_to_wstring(lhs);
+ LLWString wrhs = utf8str_to_wstring(rhs);
+ return LLWStringUtil::compareInsensitive(wlhs, wrhs);
+}
+
+std::string utf8str_truncate(const std::string& utf8str, const S32 max_len)
+{
+ if (0 == max_len)
+ {
+ return std::string();
+ }
+ if ((S32)utf8str.length() <= max_len)
+ {
+ return utf8str;
+ }
+ else
+ {
+ S32 cur_char = max_len;
+
+ // If we're ASCII, we don't need to do anything
+ if ((U8)utf8str[cur_char] > 0x7f)
+ {
+ // If first two bits are (10), it's the tail end of a multibyte char. We need to shift back
+ // to the first character
+ while (0x80 == (0xc0 & utf8str[cur_char]))
+ {
+ cur_char--;
+ // Keep moving forward until we hit the first char;
+ if (cur_char == 0)
+ {
+ // Make sure we don't trash memory if we've got a bogus string.
+ break;
+ }
+ }
+ }
+ // The byte index we're on is one we want to get rid of, so we only want to copy up to (cur_char-1) chars
+ return utf8str.substr(0, cur_char);
+ }
+}
+
+std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len)
+{
+ if (0 == symbol_len)
+ {
+ return std::string();
+ }
+ if ((S32)utf8str.length() <= symbol_len)
+ {
+ return utf8str;
+ }
+ else
+ {
+ int len = 0, byteIndex = 0;
+ const char* aStr = utf8str.c_str();
+ size_t origSize = utf8str.size();
+
+ for (byteIndex = 0; len < symbol_len && byteIndex < origSize; byteIndex++)
+ {
+ if ((aStr[byteIndex] & 0xc0) != 0x80)
+ {
+ len += 1;
+ }
+ }
+ return utf8str.substr(0, byteIndex);
+ }
+}
+
+std::string utf8str_substChar(
+ const std::string& utf8str,
+ const llwchar target_char,
+ const llwchar replace_char)
+{
+ LLWString wstr = utf8str_to_wstring(utf8str);
+ LLWStringUtil::replaceChar(wstr, target_char, replace_char);
+ //wstr = wstring_substChar(wstr, target_char, replace_char);
+ return wstring_to_utf8str(wstr);
+}
+
+std::string utf8str_makeASCII(const std::string& utf8str)
+{
+ LLWString wstr = utf8str_to_wstring(utf8str);
+ LLWStringUtil::_makeASCII(wstr);
+ return wstring_to_utf8str(wstr);
+}
+
+std::string mbcsstring_makeASCII(const std::string& wstr)
+{
+ // Replace non-ASCII chars with replace_char
+ std::string out_str = wstr;
+ for (S32 i = 0; i < (S32)out_str.length(); i++)
+ {
+ if ((U8)out_str[i] > 0x7f)
+ {
+ out_str[i] = LL_UNKNOWN_CHAR;
+ }
+ }
+ return out_str;
+}
+
+std::string utf8str_removeCRLF(const std::string& utf8str)
+{
+ if (0 == utf8str.length())
+ {
+ return std::string();
+ }
+ const char CR = 13;
+
+ std::string out;
+ out.reserve(utf8str.length());
+ const S32 len = (S32)utf8str.length();
+ for( S32 i = 0; i < len; i++ )
+ {
+ if( utf8str[i] != CR )
+ {
+ out.push_back(utf8str[i]);
+ }
+ }
+ return out;
+}
+
+llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length)
+{
+ switch (length)
+ {
+ case 2:
+ return ((utf8str[offset] & 0x1F) << 6) +
+ (utf8str[offset + 1] & 0x3F);
+ case 3:
+ return ((utf8str[offset] & 0x0F) << 12) +
+ ((utf8str[offset + 1] & 0x3F) << 6) +
+ (utf8str[offset + 2] & 0x3F);
+ case 4:
+ return ((utf8str[offset] & 0x07) << 18) +
+ ((utf8str[offset + 1] & 0x3F) << 12) +
+ ((utf8str[offset + 2] & 0x3F) << 6) +
+ (utf8str[offset + 3] & 0x3F);
+ case 5:
+ return ((utf8str[offset] & 0x03) << 24) +
+ ((utf8str[offset + 1] & 0x3F) << 18) +
+ ((utf8str[offset + 2] & 0x3F) << 12) +
+ ((utf8str[offset + 3] & 0x3F) << 6) +
+ (utf8str[offset + 4] & 0x3F);
+ case 6:
+ return ((utf8str[offset] & 0x01) << 30) +
+ ((utf8str[offset + 1] & 0x3F) << 24) +
+ ((utf8str[offset + 2] & 0x3F) << 18) +
+ ((utf8str[offset + 3] & 0x3F) << 12) +
+ ((utf8str[offset + 4] & 0x3F) << 6) +
+ (utf8str[offset + 5] & 0x3F);
+ case 7:
+ return ((utf8str[offset + 1] & 0x03) << 30) +
+ ((utf8str[offset + 2] & 0x3F) << 24) +
+ ((utf8str[offset + 3] & 0x3F) << 18) +
+ ((utf8str[offset + 4] & 0x3F) << 12) +
+ ((utf8str[offset + 5] & 0x3F) << 6) +
+ (utf8str[offset + 6] & 0x3F);
+ }
+ return LL_UNKNOWN_CHAR;
+}
+
+std::string utf8str_showBytesUTF8(const std::string& utf8str)
+{
+ std::string result;
+
+ bool in_sequence = false;
+ size_t sequence_size = 0;
+ size_t byte_index = 0;
+ size_t source_length = utf8str.size();
+
+ auto open_sequence = [&]()
+ {
+ if (!result.empty() && result.back() != '\n')
+ result += '\n'; // Use LF as a separator before new UTF-8 sequence
+ result += '[';
+ in_sequence = true;
+ };
+
+ auto close_sequence = [&]()
+ {
+ llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size);
+ if (unicode != LL_UNKNOWN_CHAR)
+ {
+ result += llformat("+%04X", unicode);
+ }
+ result += ']';
+ in_sequence = false;
+ sequence_size = 0;
+ };
+
+ while (byte_index < source_length)
+ {
+ U8 byte = utf8str[byte_index];
+ if (byte >= 0x80) // Part of an UTF-8 sequence
+ {
+ if (!in_sequence) // Start new UTF-8 sequence
+ {
+ open_sequence();
+ }
+ else if (byte >= 0xC0) // Start another UTF-8 sequence
+ {
+ close_sequence();
+ open_sequence();
+ }
+ else // Continue the same UTF-8 sequence
+ {
+ result += '.';
+ }
+ result += llformat("%02X", byte); // The byte is represented in hexadecimal form
+ ++sequence_size;
+ }
+ else // ASCII symbol is represented as a character
+ {
+ if (in_sequence) // End of UTF-8 sequence
+ {
+ close_sequence();
+ if (byte != '\n')
+ {
+ result += '\n'; // Use LF as a separator between UTF-8 and ASCII
+ }
+ }
+ result += byte;
+ }
+ ++byte_index;
+ }
+
+ if (in_sequence) // End of UTF-8 sequence
+ {
+ close_sequence();
+ }
+
+ return result;
+}
+
+// Search for any emoji symbol, return true if found
+bool wstring_has_emoji(const LLWString& wstr)
+{
+ for (const llwchar& wch : wstr)
+ {
+ if (LLStringOps::isEmoji(wch))
+ return true;
+ }
+
+ return false;
+}
+
+// Cut emoji symbols if exist
+bool wstring_remove_emojis(LLWString& wstr)
+{
+ bool found = false;
+ for (size_t i = 0; i < wstr.size(); ++i)
+ {
+ if (LLStringOps::isEmoji(wstr[i]))
+ {
+ wstr.erase(i--, 1);
+ found = true;
+ }
+ }
+ return found;
+}
+
+// Cut emoji symbols if exist
+bool utf8str_remove_emojis(std::string& utf8str)
+{
+ LLWString wstr = utf8str_to_wstring(utf8str);
+ if (!wstring_remove_emojis(wstr))
+ return false;
+ utf8str = wstring_to_utf8str(wstr);
+ return true;
+}
+
+#if LL_WINDOWS
+unsigned int ll_wstring_default_code_page()
+{
+ return CP_UTF8;
+}
+
+std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned int code_page)
+{
+ std::string out;
+ if(in)
+ {
+ int len_out = WideCharToMultiByte(
+ code_page,
+ 0,
+ in,
+ len_in,
+ NULL,
+ 0,
+ 0,
+ 0);
+ // We will need two more bytes for the double NULL ending
+ // created in WideCharToMultiByte().
+ char* pout = new char [len_out + 2];
+ memset(pout, 0, len_out + 2);
+ if(pout)
+ {
+ WideCharToMultiByte(
+ code_page,
+ 0,
+ in,
+ len_in,
+ pout,
+ len_out,
+ 0,
+ 0);
+ out.assign(pout);
+ delete[] pout;
+ }
+ }
+ return out;
+}
+
+std::wstring ll_convert_string_to_wide(const char* in, size_t len, unsigned int code_page)
+{
+ // From review:
+ // We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input,
+ // plus one for a null terminator, and be guaranteed to not overflow.
+
+ // Normally, I'd call that sort of thing premature optimization,
+ // but we *are* seeing string operations taking a bunch of time, especially when constructing widgets.
+// int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0);
+
+ // reserve an output buffer that will be destroyed on exit, with a place
+ // to put NULL terminator
+ std::vector<wchar_t> w_out(len + 1);
+
+ memset(&w_out[0], 0, w_out.size());
+ int real_output_str_len = MultiByteToWideChar(code_page, 0, in, len,
+ &w_out[0], w_out.size() - 1);
+
+ //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858.
+ w_out[real_output_str_len] = 0;
+
+ // construct string<wchar_t> from our temporary output buffer
+ return {&w_out[0]};
+}
+
+LLWString ll_convert_wide_to_wstring(const wchar_t* in, size_t len)
+{
+ // Whether or not std::wstring and llutf16string are distinct types, they
+ // both hold UTF-16LE characters. (See header file comments.) Pretend this
+ // wchar_t* sequence is really a U16* sequence and use the conversion we
+ // define above.
+ return utf16str_to_wstring(reinterpret_cast<const U16*>(in), len);
+}
+
+std::wstring ll_convert_wstring_to_wide(const llwchar* in, size_t len)
+{
+ // first, convert to llutf16string, for which we have a real implementation
+ auto utf16str{ wstring_to_utf16str(in, len) };
+ // then, because each U16 char must be UTF-16LE encoded, pretend the U16*
+ // string pointer is a wchar_t* and instantiate a std::wstring of the same
+ // length.
+ return { reinterpret_cast<const wchar_t*>(utf16str.c_str()), utf16str.length() };
+}
+
+std::string ll_convert_string_to_utf8_string(const std::string& in)
+{
+ // If you pass code_page, you must also pass length, otherwise the code
+ // page parameter will be mistaken for length.
+ auto w_mesg = ll_convert_string_to_wide(in, in.length(), CP_ACP);
+ // CP_UTF8 is default -- see ll_wstring_default_code_page() above.
+ return ll_convert_wide_to_string(w_mesg);
+}
+
+namespace
+{
+
+void HeapFree_deleter(void* ptr)
+{
+ // instead of LocalFree(), per https://stackoverflow.com/a/31541205
+ HeapFree(GetProcessHeap(), NULL, ptr);
+}
+
+} // anonymous namespace
+
+template<>
+std::wstring windows_message<std::wstring>(DWORD error)
+{
+ // derived from https://stackoverflow.com/a/455533
+ wchar_t* rawptr = nullptr;
+ auto okay = FormatMessageW(
+ // use system message tables for GetLastError() codes
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ // internally allocate buffer and return its pointer
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ // you cannot pass insertion parameters (thanks Gandalf)
+ FORMAT_MESSAGE_IGNORE_INSERTS |
+ // ignore line breaks in message definition text
+ FORMAT_MESSAGE_MAX_WIDTH_MASK,
+ NULL, // lpSource, unused with FORMAT_MESSAGE_FROM_SYSTEM
+ error, // dwMessageId
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId
+ (LPWSTR)&rawptr, // lpBuffer: force-cast wchar_t** to wchar_t*
+ 0, // nSize, unused with FORMAT_MESSAGE_ALLOCATE_BUFFER
+ NULL); // Arguments, unused
+
+ // make a unique_ptr from rawptr so it gets cleaned up properly
+ std::unique_ptr<wchar_t, void(*)(void*)> bufferptr(rawptr, HeapFree_deleter);
+
+ if (okay && bufferptr)
+ {
+ // got the message, return it ('okay' is length in characters)
+ return { bufferptr.get(), okay };
+ }
+
+ // did not get the message, synthesize one
+ auto format_message_error = GetLastError();
+ std::wostringstream out;
+ out << L"GetLastError() " << error << L" (FormatMessageW() failed with "
+ << format_message_error << L")";
+ return out.str();
+}
+
+std::optional<std::wstring> llstring_getoptenv(const std::string& key)
+{
+ auto wkey = ll_convert_string_to_wide(key);
+ // Take a wild guess as to how big the buffer should be.
+ std::vector<wchar_t> buffer(1024);
+ auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size());
+ // If our initial guess was too short, n will indicate the size (in
+ // wchar_t's) that buffer should have been, including the terminating nul.
+ if (n > (buffer.size() - 1))
+ {
+ // make it big enough
+ buffer.resize(n);
+ // and try again
+ n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size());
+ }
+ // did that (ultimately) succeed?
+ if (n)
+ {
+ // great, return populated std::optional
+ return std::make_optional<std::wstring>(&buffer[0]);
+ }
+
+ // not successful
+ auto last_error = GetLastError();
+ // Don't bother warning for NOT_FOUND; that's an expected case
+ if (last_error != ERROR_ENVVAR_NOT_FOUND)
+ {
+ LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: "
+ << windows_message<std::string>(last_error) << LL_ENDL;
+ }
+ // return empty std::optional
+ return {};
+}
+
+#else // ! LL_WINDOWS
+
+std::optional<std::string> llstring_getoptenv(const std::string& key)
+{
+ auto found = getenv(key.c_str());
+ if (found)
+ {
+ // return populated std::optional
+ return std::make_optional<std::string>(found);
+ }
+ else
+ {
+ // return empty std::optional
+ return {};
+ }
+}
+
+#endif // ! LL_WINDOWS
+
+long LLStringOps::sPacificTimeOffset = 0;
+long LLStringOps::sLocalTimeOffset = 0;
+bool LLStringOps::sPacificDaylightTime = 0;
+std::map<std::string, std::string> LLStringOps::datetimeToCodes;
+
+std::vector<std::string> LLStringOps::sWeekDayList;
+std::vector<std::string> LLStringOps::sWeekDayShortList;
+std::vector<std::string> LLStringOps::sMonthList;
+std::vector<std::string> LLStringOps::sMonthShortList;
+
+
+std::string LLStringOps::sDayFormat;
+std::string LLStringOps::sAM;
+std::string LLStringOps::sPM;
+
+// static
+bool LLStringOps::isEmoji(llwchar a)
+{
+#if 0 // Do not consider special characters that might have a corresponding
+ // glyph in the monochorme fallback fonts as a "genuine" emoji. HB
+ return a == 0xa9 || a == 0xae || (a >= 0x2000 && a < 0x3300) ||
+ (a >= 0x1f000 && a < 0x20000);
+#else
+ // These are indeed "genuine" emojis, we *do want* rendered as such. HB
+ return a >= 0x1f000 && a < 0x20000;
+#endif
+ }
+
+S32 LLStringOps::collate(const llwchar* a, const llwchar* b)
+{
+ #if LL_WINDOWS
+ // in Windows, wide string functions operator on 16-bit strings,
+ // not the proper 32 bit wide string
+ return strcmp(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str());
+ #else
+ return wcscoll(a, b);
+ #endif
+}
+
+void LLStringOps::setupDatetimeInfo (bool daylight)
+{
+ time_t nowT, localT, gmtT;
+ struct tm * tmpT;
+
+ nowT = time (NULL);
+
+ tmpT = gmtime (&nowT);
+ gmtT = mktime (tmpT);
+
+ tmpT = localtime (&nowT);
+ localT = mktime (tmpT);
+
+ sLocalTimeOffset = (long) (gmtT - localT);
+ if (tmpT->tm_isdst)
+ {
+ sLocalTimeOffset -= 60 * 60; // 1 hour
+ }
+
+ sPacificDaylightTime = daylight;
+ sPacificTimeOffset = (sPacificDaylightTime? 7 : 8 ) * 60 * 60;
+
+ datetimeToCodes["wkday"] = "%a"; // Thu
+ datetimeToCodes["weekday"] = "%A"; // Thursday
+ datetimeToCodes["year4"] = "%Y"; // 2009
+ datetimeToCodes["year"] = "%Y"; // 2009
+ datetimeToCodes["year2"] = "%y"; // 09
+ datetimeToCodes["mth"] = "%b"; // Aug
+ datetimeToCodes["month"] = "%B"; // August
+ datetimeToCodes["mthnum"] = "%m"; // 08
+ datetimeToCodes["day"] = "%d"; // 31
+ datetimeToCodes["sday"] = "%-d"; // 9
+ datetimeToCodes["hour24"] = "%H"; // 14
+ datetimeToCodes["hour"] = "%H"; // 14
+ datetimeToCodes["hour12"] = "%I"; // 02
+ datetimeToCodes["min"] = "%M"; // 59
+ datetimeToCodes["ampm"] = "%p"; // AM
+ datetimeToCodes["second"] = "%S"; // 59
+ datetimeToCodes["timezone"] = "%Z"; // PST
+}
+
+void tokenizeStringToArray(const std::string& data, std::vector<std::string>& output)
+{
+ output.clear();
+ size_t length = data.size();
+
+ // tokenize it and put it in the array
+ std::string cur_word;
+ for(size_t i = 0; i < length; ++i)
+ {
+ if(data[i] == ':')
+ {
+ output.push_back(cur_word);
+ cur_word.clear();
+ }
+ else
+ {
+ cur_word.append(1, data[i]);
+ }
+ }
+ output.push_back(cur_word);
+}
+
+void LLStringOps::setupWeekDaysNames(const std::string& data)
+{
+ tokenizeStringToArray(data,sWeekDayList);
+}
+void LLStringOps::setupWeekDaysShortNames(const std::string& data)
+{
+ tokenizeStringToArray(data,sWeekDayShortList);
+}
+void LLStringOps::setupMonthNames(const std::string& data)
+{
+ tokenizeStringToArray(data,sMonthList);
+}
+void LLStringOps::setupMonthShortNames(const std::string& data)
+{
+ tokenizeStringToArray(data,sMonthShortList);
+}
+void LLStringOps::setupDayFormat(const std::string& data)
+{
+ sDayFormat = data;
+}
+
+
+std::string LLStringOps::getDatetimeCode (std::string key)
+{
+ std::map<std::string, std::string>::iterator iter;
+
+ iter = datetimeToCodes.find (key);
+ if (iter != datetimeToCodes.end())
+ {
+ return iter->second;
+ }
+ else
+ {
+ return std::string("");
+ }
+}
+
+std::string LLStringOps::getReadableNumber(F64 num)
+{
+ if (fabs(num)>=1e9)
+ {
+ return llformat("%.2lfB", num / 1e9);
+ }
+ else if (fabs(num)>=1e6)
+ {
+ return llformat("%.2lfM", num / 1e6);
+ }
+ else if (fabs(num)>=1e3)
+ {
+ return llformat("%.2lfK", num / 1e3);
+ }
+ else
+ {
+ return llformat("%.2lf", num);
+ }
+}
+
+namespace LLStringFn
+{
+ // NOTE - this restricts output to ascii
+ void replace_nonprintable_in_ascii(std::basic_string<char>& string, char replacement)
+ {
+ const char MIN = 0x20;
+ std::basic_string<char>::size_type len = string.size();
+ for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii)
+ {
+ if(string[ii] < MIN)
+ {
+ string[ii] = replacement;
+ }
+ }
+ }
+
+
+ // NOTE - this restricts output to ascii
+ void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str,
+ char replacement)
+ {
+ const char MIN = 0x20;
+ const char PIPE = 0x7c;
+ std::basic_string<char>::size_type len = str.size();
+ for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii)
+ {
+ if( (str[ii] < MIN) || (str[ii] == PIPE) )
+ {
+ str[ii] = replacement;
+ }
+ }
+ }
+
+ // https://wiki.lindenlab.com/wiki/Unicode_Guidelines has details on
+ // allowable code points for XML. Specifically, they are:
+ // 0x09, 0x0a, 0x0d, and 0x20 on up. JC
+ std::string strip_invalid_xml(const std::string& instr)
+ {
+ std::string output;
+ output.reserve( instr.size() );
+ std::string::const_iterator it = instr.begin();
+ while (it != instr.end())
+ {
+ // Must compare as unsigned for >=
+ // Test most likely match first
+ const unsigned char c = (unsigned char)*it;
+ if ( c >= (unsigned char)0x20 // SPACE
+ || c == (unsigned char)0x09 // TAB
+ || c == (unsigned char)0x0a // LINE_FEED
+ || c == (unsigned char)0x0d ) // CARRIAGE_RETURN
+ {
+ output.push_back(c);
+ }
+ ++it;
+ }
+ return output;
+ }
+
+ /**
+ * @brief Replace all control characters (c < 0x20) with replacement in
+ * string.
+ */
+ void replace_ascii_controlchars(std::basic_string<char>& string, char replacement)
+ {
+ const unsigned char MIN = 0x20;
+ std::basic_string<char>::size_type len = string.size();
+ for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii)
+ {
+ const unsigned char c = (unsigned char) string[ii];
+ if(c < MIN)
+ {
+ string[ii] = replacement;
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////
+
+// Forward specialization of LLStringUtil::format before use in LLStringUtil::formatDatetime.
+template<>
+S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions);
+
+//static
+template<>
+void LLStringUtil::getTokens(const std::string& instr, std::vector<std::string >& tokens, const std::string& delims)
+{
+ // Starting at offset 0, scan forward for the next non-delimiter. We're
+ // done when the only characters left in 'instr' are delimiters.
+ for (std::string::size_type begIdx, endIdx = 0;
+ (begIdx = instr.find_first_not_of (delims, endIdx)) != std::string::npos; )
+ {
+ // Found a non-delimiter. After that, find the next delimiter.
+ endIdx = instr.find_first_of (delims, begIdx);
+ if (endIdx == std::string::npos)
+ {
+ // No more delimiters: this token extends to the end of the string.
+ endIdx = instr.length();
+ }
+
+ // extract the token between begIdx and endIdx; substr() needs length
+ std::string currToken(instr.substr(begIdx, endIdx - begIdx));
+ LLStringUtil::trim (currToken);
+ tokens.push_back(currToken);
+ // next scan past delimiters starts at endIdx
+ }
+}
+
+template<>
+LLStringUtil::size_type LLStringUtil::getSubstitution(const std::string& instr, size_type& start, std::vector<std::string>& tokens)
+{
+ const std::string delims (",");
+
+ // Find the first [
+ size_type pos1 = instr.find('[', start);
+ if (pos1 == std::string::npos)
+ return std::string::npos;
+
+ //Find the first ] after the initial [
+ size_type pos2 = instr.find(']', pos1);
+ if (pos2 == std::string::npos)
+ return std::string::npos;
+
+ // Find the last [ before ] in case of nested [[]]
+ pos1 = instr.find_last_of('[', pos2-1);
+ if (pos1 == std::string::npos || pos1 < start)
+ return std::string::npos;
+
+ getTokens(std::string(instr,pos1+1,pos2-pos1-1), tokens, delims);
+ start = pos2+1;
+
+ return pos1;
+}
+
+// static
+template<>
+bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const format_map_t& substitutions)
+{
+ // see if we have a replacement for the bracketed string (without the brackets)
+ // test first using has() because if we just look up with operator[] we get back an
+ // empty string even if the value is missing. We want to distinguish between
+ // missing replacements and deliberately empty replacement strings.
+ format_map_t::const_iterator iter = substitutions.find(token);
+ if (iter != substitutions.end())
+ {
+ replacement = iter->second;
+ return true;
+ }
+ // if not, see if there's one WITH brackets
+ iter = substitutions.find(std::string("[" + token + "]"));
+ if (iter != substitutions.end())
+ {
+ replacement = iter->second;
+ return true;
+ }
+
+ return false;
+}
+
+// static
+template<>
+bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const LLSD& substitutions)
+{
+ // see if we have a replacement for the bracketed string (without the brackets)
+ // test first using has() because if we just look up with operator[] we get back an
+ // empty string even if the value is missing. We want to distinguish between
+ // missing replacements and deliberately empty replacement strings.
+ if (substitutions.has(token))
+ {
+ replacement = substitutions[token].asString();
+ return true;
+ }
+ // if not, see if there's one WITH brackets
+ else if (substitutions.has(std::string("[" + token + "]")))
+ {
+ replacement = substitutions[std::string("[" + token + "]")].asString();
+ return true;
+ }
+
+ return false;
+}
+
+//static
+template<>
+void LLStringUtil::setLocale(std::string inLocale)
+{
+ sLocale = inLocale;
+};
+
+//static
+template<>
+std::string LLStringUtil::getLocale(void)
+{
+ return sLocale;
+};
+
+// static
+template<>
+void LLStringUtil::formatNumber(std::string& numStr, std::string decimals)
+{
+ std::stringstream strStream;
+ S32 intDecimals = 0;
+
+ convertToS32 (decimals, intDecimals);
+ if (!sLocale.empty())
+ {
+ // std::locale() throws if the locale is unknown! (EXT-7926)
+ try
+ {
+ strStream.imbue(std::locale(sLocale.c_str()));
+ } catch (const std::exception &)
+ {
+ LL_WARNS_ONCE("Locale") << "Cannot set locale to " << sLocale << LL_ENDL;
+ }
+ }
+
+ if (!intDecimals)
+ {
+ S32 intStr;
+
+ if (convertToS32(numStr, intStr))
+ {
+ strStream << intStr;
+ numStr = strStream.str();
+ }
+ }
+ else
+ {
+ F32 floatStr;
+
+ if (convertToF32(numStr, floatStr))
+ {
+ strStream << std::fixed << std::showpoint << std::setprecision(intDecimals) << floatStr;
+ numStr = strStream.str();
+ }
+ }
+}
+
+// static
+template<>
+bool LLStringUtil::formatDatetime(std::string& replacement, std::string token,
+ std::string param, S32 secFromEpoch)
+{
+ if (param == "local") // local
+ {
+ secFromEpoch -= LLStringOps::getLocalTimeOffset();
+ }
+ else if (param != "utc") // slt
+ {
+ secFromEpoch -= LLStringOps::getPacificTimeOffset();
+ }
+
+ // if never fell into those two ifs above, param must be utc
+ if (secFromEpoch < 0) secFromEpoch = 0;
+
+ LLDate datetime((F64)secFromEpoch);
+ std::string code = LLStringOps::getDatetimeCode (token);
+
+ // special case to handle timezone
+ if (code == "%Z") {
+ if (param == "utc")
+ {
+ replacement = "GMT";
+ }
+ else if (param == "local")
+ {
+ replacement = ""; // user knows their own timezone
+ }
+ else
+ {
+#if 0
+ // EXT-1565 : Zai Lynch, James Linden : 15/Oct/09
+ // [BSI] Feedback: Viewer clock mentions SLT, but would prefer it to show PST/PDT
+ // "slt" = Second Life Time, which is deprecated.
+ // If not utc or user local time, fallback to Pacific time
+ replacement = LLStringOps::getPacificDaylightTime() ? "PDT" : "PST";
+#else
+ // SL-20370 : Steeltoe Linden : 29/Sep/23
+ // Change "PDT" to "SLT" on menu bar
+ replacement = "SLT";
+#endif
+ }
+ return true;
+ }
+
+ //EXT-7013
+ //few codes are not suppotred by strtime function (example - weekdays for Japanise)
+ //so use predefined ones
+
+ //if sWeekDayList is not empty than current locale doesn't support
+ //weekday name.
+ time_t loc_seconds = (time_t) secFromEpoch;
+ if(LLStringOps::sWeekDayList.size() == 7 && code == "%A")
+ {
+ struct tm * gmt = gmtime (&loc_seconds);
+ replacement = LLStringOps::sWeekDayList[gmt->tm_wday];
+ }
+ else if(LLStringOps::sWeekDayShortList.size() == 7 && code == "%a")
+ {
+ struct tm * gmt = gmtime (&loc_seconds);
+ replacement = LLStringOps::sWeekDayShortList[gmt->tm_wday];
+ }
+ else if(LLStringOps::sMonthList.size() == 12 && code == "%B")
+ {
+ struct tm * gmt = gmtime (&loc_seconds);
+ replacement = LLStringOps::sMonthList[gmt->tm_mon];
+ }
+ else if( !LLStringOps::sDayFormat.empty() && code == "%d" )
+ {
+ struct tm * gmt = gmtime (&loc_seconds);
+ LLStringUtil::format_map_t args;
+ args["[MDAY]"] = llformat ("%d", gmt->tm_mday);
+ replacement = LLStringOps::sDayFormat;
+ LLStringUtil::format(replacement, args);
+ }
+ else if (code == "%-d")
+ {
+ struct tm * gmt = gmtime (&loc_seconds);
+ replacement = llformat ("%d", gmt->tm_mday); // day of the month without leading zero
+ }
+ else if( !LLStringOps::sAM.empty() && !LLStringOps::sPM.empty() && code == "%p" )
+ {
+ struct tm * gmt = gmtime (&loc_seconds);
+ if(gmt->tm_hour<12)
+ {
+ replacement = LLStringOps::sAM;
+ }
+ else
+ {
+ replacement = LLStringOps::sPM;
+ }
+ }
+ else
+ {
+ replacement = datetime.toHTTPDateString(code);
+ }
+
+ // *HACK: delete leading zero from hour string in case 'hour12' (code = %I) time format
+ // to show time without leading zero, e.g. 08:16 -> 8:16 (EXT-2738).
+ // We could have used '%l' format instead, but it's not supported by Windows.
+ if(code == "%I" && token == "hour12" && replacement.at(0) == '0')
+ {
+ replacement = replacement.at(1);
+ }
+
+ return !code.empty();
+}
+
+// LLStringUtil::format recogizes the following patterns.
+// All substitutions *must* be encased in []'s in the input string.
+// The []'s are optional in the substitution map.
+// [FOO_123]
+// [FOO,number,precision]
+// [FOO,datetime,format]
+
+
+// static
+template<>
+S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING;
+ S32 res = 0;
+
+ std::string output;
+ std::vector<std::string> tokens;
+
+ std::string::size_type start = 0;
+ std::string::size_type prev_start = 0;
+ std::string::size_type key_start = 0;
+ while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos)
+ {
+ output += std::string(s, prev_start, key_start-prev_start);
+ prev_start = start;
+
+ bool found_replacement = false;
+ std::string replacement;
+
+ if (tokens.size() == 0)
+ {
+ found_replacement = false;
+ }
+ else if (tokens.size() == 1)
+ {
+ found_replacement = simpleReplacement (replacement, tokens[0], substitutions);
+ }
+ else if (tokens[1] == "number")
+ {
+ std::string param = "0";
+
+ if (tokens.size() > 2) param = tokens[2];
+ found_replacement = simpleReplacement (replacement, tokens[0], substitutions);
+ if (found_replacement) formatNumber (replacement, param);
+ }
+ else if (tokens[1] == "datetime")
+ {
+ std::string param;
+ if (tokens.size() > 2) param = tokens[2];
+
+ format_map_t::const_iterator iter = substitutions.find("datetime");
+ if (iter != substitutions.end())
+ {
+ S32 secFromEpoch = 0;
+ bool r = LLStringUtil::convertToS32(iter->second, secFromEpoch);
+ if (r)
+ {
+ found_replacement = formatDatetime(replacement, tokens[0], param, secFromEpoch);
+ }
+ }
+ }
+
+ if (found_replacement)
+ {
+ output += replacement;
+ res++;
+ }
+ else
+ {
+ // we had no replacement, use the string as is
+ // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-"
+ output += std::string(s, key_start, start-key_start);
+ }
+ tokens.clear();
+ }
+ // send the remainder of the string (with no further matches for bracketed names)
+ output += std::string(s, start);
+ s = output;
+ return res;
+}
+
+//static
+template<>
+S32 LLStringUtil::format(std::string& s, const LLSD& substitutions)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING;
+ S32 res = 0;
+
+ if (!substitutions.isMap())
+ {
+ return res;
+ }
+
+ std::string output;
+ std::vector<std::string> tokens;
+
+ std::string::size_type start = 0;
+ std::string::size_type prev_start = 0;
+ std::string::size_type key_start = 0;
+ while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos)
+ {
+ output += std::string(s, prev_start, key_start-prev_start);
+ prev_start = start;
+
+ bool found_replacement = false;
+ std::string replacement;
+
+ if (tokens.size() == 0)
+ {
+ found_replacement = false;
+ }
+ else if (tokens.size() == 1)
+ {
+ found_replacement = simpleReplacement (replacement, tokens[0], substitutions);
+ }
+ else if (tokens[1] == "number")
+ {
+ std::string param = "0";
+
+ if (tokens.size() > 2) param = tokens[2];
+ found_replacement = simpleReplacement (replacement, tokens[0], substitutions);
+ if (found_replacement) formatNumber (replacement, param);
+ }
+ else if (tokens[1] == "datetime")
+ {
+ std::string param;
+ if (tokens.size() > 2) param = tokens[2];
+
+ S32 secFromEpoch = (S32) substitutions["datetime"].asInteger();
+ found_replacement = formatDatetime (replacement, tokens[0], param, secFromEpoch);
+ }
+
+ if (found_replacement)
+ {
+ output += replacement;
+ res++;
+ }
+ else
+ {
+ // we had no replacement, use the string as is
+ // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-"
+ output += std::string(s, key_start, start-key_start);
+ }
+ tokens.clear();
+ }
+ // send the remainder of the string (with no further matches for bracketed names)
+ output += std::string(s, start);
+ s = output;
+ return res;
+}
+
+////////////////////////////////////////////////////////////
+// Testing
+
+#ifdef _DEBUG
+
+template<class T>
+void LLStringUtilBase<T>::testHarness()
+{
+ std::string s1;
+
+ llassert( s1.c_str() == NULL );
+ llassert( s1.size() == 0 );
+ llassert( s1.empty() );
+
+ std::string s2( "hello");
+ llassert( !strcmp( s2.c_str(), "hello" ) );
+ llassert( s2.size() == 5 );
+ llassert( !s2.empty() );
+ std::string s3( s2 );
+
+ llassert( "hello" == s2 );
+ llassert( s2 == "hello" );
+ llassert( s2 > "gello" );
+ llassert( "gello" < s2 );
+ llassert( "gello" != s2 );
+ llassert( s2 != "gello" );
+
+ std::string s4 = s2;
+ llassert( !s4.empty() );
+ s4.empty();
+ llassert( s4.empty() );
+
+ std::string s5("");
+ llassert( s5.empty() );
+
+ llassert( isValidIndex(s5, 0) );
+ llassert( !isValidIndex(s5, 1) );
+
+ s3 = s2;
+ s4 = "hello again";
+
+ s4 += "!";
+ s4 += s4;
+ llassert( s4 == "hello again!hello again!" );
+
+
+ std::string s6 = s2 + " " + s2;
+ std::string s7 = s6;
+ llassert( s6 == s7 );
+ llassert( !( s6 != s7) );
+ llassert( !(s6 < s7) );
+ llassert( !(s6 > s7) );
+
+ llassert( !(s6 == "hi"));
+ llassert( s6 == "hello hello");
+ llassert( s6 < "hi");
+
+ llassert( s6[1] == 'e' );
+ s6[1] = 'f';
+ llassert( s6[1] == 'f' );
+
+ s2.erase( 4, 1 );
+ llassert( s2 == "hell");
+ s2.insert( 0, "y" );
+ llassert( s2 == "yhell");
+ s2.erase( 1, 3 );
+ llassert( s2 == "yl");
+ s2.insert( 1, "awn, don't yel");
+ llassert( s2 == "yawn, don't yell");
+
+ std::string s8 = s2.substr( 6, 5 );
+ llassert( s8 == "don't" );
+
+ std::string s9 = " \t\ntest \t\t\n ";
+ trim(s9);
+ llassert( s9 == "test" );
+
+ s8 = "abc123&*(ABC";
+
+ s9 = s8;
+ toUpper(s9);
+ llassert( s9 == "ABC123&*(ABC" );
+
+ s9 = s8;
+ toLower(s9);
+ llassert( s9 == "abc123&*(abc" );
+
+
+ std::string s10( 10, 'x' );
+ llassert( s10 == "xxxxxxxxxx" );
+
+ std::string s11( "monkey in the middle", 7, 2 );
+ llassert( s11 == "in" );
+
+ std::string s12; //empty
+ s12 += "foo";
+ llassert( s12 == "foo" );
+
+ std::string s13; //empty
+ s13 += 'f';
+ llassert( s13 == "f" );
+}
+
+
+#endif // _DEBUG
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 6503da2e77..ac05dc3cd0 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -1,2038 +1,2039 @@ -/** - * @file llstring.h - * @brief String utility functions and std::string class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLSTRING_H -#define LL_LLSTRING_H - -#include <boost/call_traits.hpp> -#include <boost/optional/optional.hpp> -#include <string> -#include <cstdio> -#include <cwchar> // std::wcslen() -//#include <locale> -#include <iomanip> -#include <algorithm> -#include <vector> -#include <map> -#include "llformat.h" - -#if LL_LINUX -#include <wctype.h> -#include <wchar.h> -#endif - -#include <string.h> -#include <boost/scoped_ptr.hpp> - -const char LL_UNKNOWN_CHAR = '?'; -class LLSD; - -#if LL_DARWIN || LL_LINUX -// Template specialization of char_traits for U16s. Only necessary on Mac and Linux (exists on Windows already) -#include <cstring> - -namespace std -{ -template<> -struct char_traits<U16> -{ - typedef U16 char_type; - typedef int int_type; - typedef streampos pos_type; - typedef streamoff off_type; - typedef mbstate_t state_type; - - static void - assign(char_type& __c1, const char_type& __c2) - { __c1 = __c2; } - - static bool - eq(const char_type& __c1, const char_type& __c2) - { return __c1 == __c2; } - - static bool - lt(const char_type& __c1, const char_type& __c2) - { return __c1 < __c2; } - - static int - compare(const char_type* __s1, const char_type* __s2, size_t __n) - { return memcmp(__s1, __s2, __n * sizeof(char_type)); } - - static size_t - length(const char_type* __s) - { - const char_type *cur_char = __s; - while (*cur_char != 0) - { - ++cur_char; - } - return cur_char - __s; - } - - static const char_type* - find(const char_type* __s, size_t __n, const char_type& __a) - { return static_cast<const char_type*>(memchr(__s, __a, __n * sizeof(char_type))); } - - static char_type* - move(char_type* __s1, const char_type* __s2, size_t __n) - { return static_cast<char_type*>(memmove(__s1, __s2, __n * sizeof(char_type))); } - - static char_type* - copy(char_type* __s1, const char_type* __s2, size_t __n) - { return static_cast<char_type*>(memcpy(__s1, __s2, __n * sizeof(char_type))); } /* Flawfinder: ignore */ - - static char_type* - assign(char_type* __s, size_t __n, char_type __a) - { - // This isn't right. - //return static_cast<char_type*>(memset(__s, __a, __n * sizeof(char_type))); - - // I don't think there's a standard 'memset' for 16-bit values. - // Do this the old-fashioned way. - - size_t __i; - for(__i = 0; __i < __n; __i++) - { - __s[__i] = __a; - } - return __s; - } - - static char_type - to_char_type(const int_type& __c) - { return static_cast<char_type>(__c); } - - static int_type - to_int_type(const char_type& __c) - { return static_cast<int_type>(__c); } - - static bool - eq_int_type(const int_type& __c1, const int_type& __c2) - { return __c1 == __c2; } - - static int_type - eof() { return static_cast<int_type>(EOF); } - - static int_type - not_eof(const int_type& __c) - { return (__c == eof()) ? 0 : __c; } - }; -}; -#endif - -class LL_COMMON_API LLStringOps -{ -private: - static long sPacificTimeOffset; - static long sLocalTimeOffset; - static bool sPacificDaylightTime; - - static std::map<std::string, std::string> datetimeToCodes; - -public: - static std::vector<std::string> sWeekDayList; - static std::vector<std::string> sWeekDayShortList; - static std::vector<std::string> sMonthList; - static std::vector<std::string> sMonthShortList; - static std::string sDayFormat; - - static std::string sAM; - static std::string sPM; - - static char toUpper(char elem) { return toupper((unsigned char)elem); } - static llwchar toUpper(llwchar elem) { return towupper(elem); } - - static char toLower(char elem) { return tolower((unsigned char)elem); } - static llwchar toLower(llwchar elem) { return towlower(elem); } - - static bool isSpace(char elem) { return isspace((unsigned char)elem) != 0; } - static bool isSpace(llwchar elem) { return iswspace(elem) != 0; } - - static bool isUpper(char elem) { return isupper((unsigned char)elem) != 0; } - static bool isUpper(llwchar elem) { return iswupper(elem) != 0; } - - static bool isLower(char elem) { return islower((unsigned char)elem) != 0; } - static bool isLower(llwchar elem) { return iswlower(elem) != 0; } - - static bool isDigit(char a) { return isdigit((unsigned char)a) != 0; } - static bool isDigit(llwchar a) { return iswdigit(a) != 0; } - - static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; } - static bool isPunct(llwchar a) { return iswpunct(a) != 0; } - - static bool isAlpha(char a) { return isalpha((unsigned char)a) != 0; } - static bool isAlpha(llwchar a) { return iswalpha(a) != 0; } - - static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; } - static bool isAlnum(llwchar a) { return iswalnum(a) != 0; } - - // Returns true when 'a' corresponds to a "genuine" emoji. HB - static bool isEmoji(llwchar a); - - static S32 collate(const char* a, const char* b) { return strcoll(a, b); } - static S32 collate(const llwchar* a, const llwchar* b); - - static void setupDatetimeInfo(bool pacific_daylight_time); - - static void setupWeekDaysNames(const std::string& data); - static void setupWeekDaysShortNames(const std::string& data); - static void setupMonthNames(const std::string& data); - static void setupMonthShortNames(const std::string& data); - static void setupDayFormat(const std::string& data); - - - static long getPacificTimeOffset(void) { return sPacificTimeOffset;} - static long getLocalTimeOffset(void) { return sLocalTimeOffset;} - // Is the Pacific time zone (aka server time zone) - // currently in daylight savings time? - static bool getPacificDaylightTime(void) { return sPacificDaylightTime;} - - static std::string getDatetimeCode (std::string key); - - // Express a value like 1234567 as "1.23M" - static std::string getReadableNumber(F64 num); -}; - -/** - * @brief Return a string constructed from in without crashing if the - * pointer is NULL. - */ -LL_COMMON_API std::string ll_safe_string(const char* in); -LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen); - - -// Allowing assignments from non-strings into format_map_t is apparently -// *really* error-prone, so subclass std::string with just basic c'tors. -class LLFormatMapString -{ -public: - LLFormatMapString() {}; - LLFormatMapString(const char* s) : mString(ll_safe_string(s)) {}; - LLFormatMapString(const std::string& s) : mString(s) {}; - operator std::string() const { return mString; } - bool operator<(const LLFormatMapString& rhs) const { return mString < rhs.mString; } - std::size_t length() const { return mString.length(); } - -private: - std::string mString; -}; - -template <class T> -class LLStringUtilBase -{ -private: - static std::string sLocale; - -public: - typedef std::basic_string<T> string_type; - typedef typename string_type::size_type size_type; - -public: - ///////////////////////////////////////////////////////////////////////////////////////// - // Static Utility functions that operate on std::strings - - static const string_type null; - - typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t; - /// considers any sequence of delims as a single field separator - LL_COMMON_API static void getTokens(const string_type& instr, - std::vector<string_type >& tokens, - const string_type& delims); - /// like simple scan overload, but returns scanned vector - static std::vector<string_type> getTokens(const string_type& instr, - const string_type& delims); - /// add support for keep_delims and quotes (either could be empty string) - static void getTokens(const string_type& instr, - std::vector<string_type>& tokens, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes=string_type()); - /// like keep_delims-and-quotes overload, but returns scanned vector - static std::vector<string_type> getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes=string_type()); - /// add support for escapes (could be empty string) - static void getTokens(const string_type& instr, - std::vector<string_type>& tokens, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes, - const string_type& escapes); - /// like escapes overload, but returns scanned vector - static std::vector<string_type> getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes, - const string_type& escapes); - - LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals); - LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch); - LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions); - LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions); - LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions); - LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions); - LL_COMMON_API static void setLocale (std::string inLocale); - LL_COMMON_API static std::string getLocale (void); - - static bool isValidIndex(const string_type& string, size_type i) - { - return !string.empty() && (0 <= i) && (i <= string.size()); - } - - static bool contains(const string_type& string, T c, size_type i=0) - { - return string.find(c, i) != string_type::npos; - } - - static void trimHead(string_type& string); - static void trimTail(string_type& string); - static void trim(string_type& string) { trimHead(string); trimTail(string); } - static void truncate(string_type& string, size_type count); - - static void toUpper(string_type& string); - static void toLower(string_type& string); - - // True if this is the head of s. - static BOOL isHead( const string_type& string, const T* s ); - - /** - * @brief Returns true if string starts with substr - * - * If etither string or substr are empty, this method returns false. - */ - static bool startsWith( - const string_type& string, - const string_type& substr); - - /** - * @brief Returns true if string ends in substr - * - * If etither string or substr are empty, this method returns false. - */ - static bool endsWith( - const string_type& string, - const string_type& substr); - - /** - * get environment string value with proper Unicode handling - * (key is always UTF-8) - * detect absence by return value == dflt - */ - static string_type getenv(const std::string& key, const string_type& dflt=""); - /** - * get optional environment string value with proper Unicode handling - * (key is always UTF-8) - * detect absence by (! return value) - */ - static boost::optional<string_type> getoptenv(const std::string& key); - - static void addCRLF(string_type& string); - static void removeCRLF(string_type& string); - static void removeWindowsCR(string_type& string); - - static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab ); - static void replaceNonstandardASCII( string_type& string, T replacement ); - static void replaceChar( string_type& string, T target, T replacement ); - static void replaceString( string_type& string, string_type target, string_type replacement ); - static string_type capitalize(const string_type& str); - static void capitalize(string_type& str); - - static BOOL containsNonprintable(const string_type& string); - static void stripNonprintable(string_type& string); - - /** - * Double-quote an argument string if needed, unless it's already - * double-quoted. Decide whether it's needed based on the presence of any - * character in @a triggers (default space or double-quote). If we quote - * it, escape any embedded double-quote with the @a escape string (default - * backslash). - * - * Passing triggers="" means always quote, unless it's already double-quoted. - */ - static string_type quote(const string_type& str, - const string_type& triggers=" \"", - const string_type& escape="\\"); - - /** - * @brief Unsafe way to make ascii characters. You should probably - * only call this when interacting with the host operating system. - * The 1 byte std::string does not work correctly. - * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII - * should work. - */ - static void _makeASCII(string_type& string); - - // Conversion to other data types - static BOOL convertToBOOL(const string_type& string, BOOL& value); - static BOOL convertToU8(const string_type& string, U8& value); - static BOOL convertToS8(const string_type& string, S8& value); - static BOOL convertToS16(const string_type& string, S16& value); - static BOOL convertToU16(const string_type& string, U16& value); - static BOOL convertToU32(const string_type& string, U32& value); - static BOOL convertToS32(const string_type& string, S32& value); - static BOOL convertToF32(const string_type& string, F32& value); - static BOOL convertToF64(const string_type& string, F64& value); - - ///////////////////////////////////////////////////////////////////////////////////////// - // Utility functions for working with char*'s and strings - - // Like strcmp but also handles empty strings. Uses - // current locale. - static S32 compareStrings(const T* lhs, const T* rhs); - static S32 compareStrings(const string_type& lhs, const string_type& rhs); - - // case insensitive version of above. Uses current locale on - // Win32, and falls back to a non-locale aware comparison on - // Linux. - static S32 compareInsensitive(const T* lhs, const T* rhs); - static S32 compareInsensitive(const string_type& lhs, const string_type& rhs); - - // Case sensitive comparison with good handling of numbers. Does not use current locale. - // a.k.a. strdictcmp() - static S32 compareDict(const string_type& a, const string_type& b); - - // Case *in*sensitive comparison with good handling of numbers. Does not use current locale. - // a.k.a. strdictcmp() - static S32 compareDictInsensitive(const string_type& a, const string_type& b); - - // Puts compareDict() in a form appropriate for LL container classes to use for sorting. - static BOOL precedesDict( const string_type& a, const string_type& b ); - - // A replacement for strncpy. - // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds - // up to dst_size-1 characters of src. - static void copy(T* dst, const T* src, size_type dst_size); - - // Copies src into dst at a given offset. - static void copyInto(string_type& dst, const string_type& src, size_type offset); - - static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); } - - -#ifdef _DEBUG - LL_COMMON_API static void testHarness(); -#endif - -private: - LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector<string_type >& tokens); -}; - -template<class T> const std::basic_string<T> LLStringUtilBase<T>::null; -template<class T> std::string LLStringUtilBase<T>::sLocale; - -typedef LLStringUtilBase<char> LLStringUtil; -typedef LLStringUtilBase<llwchar> LLWStringUtil; -typedef std::basic_string<llwchar> LLWString; - -//@ Use this where we want to disallow input in the form of "foo" -// This is used to catch places where english text is embedded in the code -// instead of in a translatable XUI file. -class LLStringExplicit : public std::string -{ -public: - explicit LLStringExplicit(const char* s) : std::string(s) {} - LLStringExplicit(const std::string& s) : std::string(s) {} - LLStringExplicit(const std::string& s, size_type pos, size_type n = std::string::npos) : std::string(s, pos, n) {} -}; - -struct LLDictionaryLess -{ -public: - bool operator()(const std::string& a, const std::string& b) const - { - return (LLStringUtil::precedesDict(a, b) ? true : false); - } -}; - - -/** - * Simple support functions - */ - -/** - * @brief chop off the trailing characters in a string. - * - * This function works on bytes rather than glyphs, so this will - * incorrectly truncate non-single byte strings. - * Use utf8str_truncate() for utf8 strings - * @return a copy of in string minus the trailing count bytes. - */ -inline std::string chop_tail_copy( - const std::string& in, - std::string::size_type count) -{ - return std::string(in, 0, in.length() - count); -} - -/** - * @brief This translates a nybble stored as a hex value from 0-f back - * to a nybble in the low order bits of the return byte. - */ -LL_COMMON_API bool is_char_hex(char hex); -LL_COMMON_API U8 hex_as_nybble(char hex); - -/** - * @brief read the contents of a file into a string. - * - * Since this function has no concept of character encoding, most - * anything you do with this method ill-advised. Please avoid. - * @param str [out] The string which will have. - * @param filename The full name of the file to read. - * @return Returns true on success. If false, str is unmodified. - */ -LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename); -LL_COMMON_API bool iswindividual(llwchar elem); - -/** - * Unicode support - */ - -/// generic conversion aliases -template<typename TO, typename FROM, typename Enable=void> -struct ll_convert_impl -{ - // Don't even provide a generic implementation. We specialize for every - // combination we do support. - TO operator()(const FROM& in) const; -}; - -// Use a function template to get the nice ll_convert<TO>(from_value) API. -template<typename TO, typename FROM> -TO ll_convert(const FROM& in) -{ - return ll_convert_impl<TO, FROM>()(in); -} - -// degenerate case -template<typename T> -struct ll_convert_impl<T, T> -{ - T operator()(const T& in) const { return in; } -}; - -// simple construction from char* -template<typename T> -struct ll_convert_impl<T, const typename T::value_type*> -{ - T operator()(const typename T::value_type* in) const { return { in }; } -}; - -// specialize ll_convert_impl<TO, FROM> to return EXPR -#define ll_convert_alias(TO, FROM, EXPR) \ -template<> \ -struct ll_convert_impl<TO, FROM> \ -{ \ - /* param_type optimally passes both char* and string */ \ - TO operator()(typename boost::call_traits<FROM>::param_type in) const { return EXPR; } \ -} - -// If all we're doing is copying characters, pass this to ll_convert_alias as -// EXPR. Since it expands into the 'return EXPR' slot in the ll_convert_impl -// specialization above, it implies TO{ in.begin(), in.end() }. -#define LL_CONVERT_COPY_CHARS { in.begin(), in.end() } - -// Generic name for strlen() / wcslen() - the default implementation should -// (!) work with U16 and llwchar, but we don't intend to engage it. -template <typename CHARTYPE> -size_t ll_convert_length(const CHARTYPE* zstr) -{ - const CHARTYPE* zp; - // classic C string scan - for (zp = zstr; *zp; ++zp) - ; - return (zp - zstr); -} - -// specialize where we have a library function; may use intrinsic operations -template <> -inline size_t ll_convert_length<wchar_t>(const wchar_t* zstr) { return std::wcslen(zstr); } -template <> -inline size_t ll_convert_length<char> (const char* zstr) { return std::strlen(zstr); } - -// ll_convert_forms() is short for a bunch of boilerplate. It defines -// longname(const char*, len), longname(const char*), longname(const string&) -// and longname(const string&, len) so calls written pre-ll_convert() will -// work. Most of these overloads will be unified once we turn on C++17 and can -// use std::string_view. -// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*) -// and ll_convert<OUTSTR>(const string&) will work. -#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \ -LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \ -inline auto longname(const INSTR& in, size_t len) \ -{ \ - return longname(in.c_str(), len); \ -} \ -inline auto longname(const INSTR::value_type* in) \ -{ \ - return longname(in, ll_convert_length(in)); \ -} \ -inline auto longname(const INSTR& in) \ -{ \ - return longname(in.c_str(), in.length()); \ -} \ -/* string param */ \ -aliasmacro(OUTSTR, INSTR, longname(in)); \ -/* char* param */ \ -aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) - -// Make the incoming string a utf8 string. Replaces any unknown glyph -// with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest -// of the data may not be recovered. -LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw); - -// -// We should never use UTF16 except when communicating with Win32! -// https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t -// nat 2018-12-14: I consider the whole llutf16string thing a mistake, because -// the Windows APIs we want to call are all defined in terms of wchar_t* -// (or worse, LPCTSTR). -// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types - -// While there is no point coding for an ASCII-only world (! defined(UNICODE)), -// use of U16 and llutf16string for Windows APIs locks in /Zc:wchar_t-. Going -// forward, we should code in terms of wchar_t and std::wstring so as to -// support either setting of /Zc:wchar_t. - -// The first link above states that char can be used to hold ASCII or any -// multi-byte character set, and distinguishes wchar_t (UTF-16LE), char16_t -// (UTF-16) and char32_t (UTF-32). Nonetheless, within this code base: -// * char and std::string always hold UTF-8 (of which ASCII is a subset). It -// is a BUG if they are used to pass strings in any other multi-byte -// encoding. -// * wchar_t and std::wstring should be our interface to Windows wide-string -// APIs, and therefore hold UTF-16LE. -// * U16 and llutf16string are the previous but DEPRECATED UTF-16LE type. Do -// not introduce new uses of U16 or llutf16string for string data. -// * llwchar and LLWString hold UTF-32 strings. -// * Do not introduce char16_t or std::u16string. -// * Do not introduce char32_t or std::u32string. -// -// This typedef may or may not be identical to std::wstring, depending on -// LL_WCHAR_T_NATIVE. -typedef std::basic_string<U16> llutf16string; - -// Considering wchar_t, llwchar and U16, there are three relevant cases: -#if LLWCHAR_IS_WCHAR_T // every which way but Windows -// llwchar is identical to wchar_t, LLWString is identical to std::wstring. -// U16 is distinct, llutf16string is distinct (though pretty useless). -// Given conversions to/from LLWString and to/from llutf16string, conversions -// involving std::wstring would collide. -#define ll_convert_wstr_alias(TO, FROM, EXPR) // nothing -// but we can define conversions involving llutf16string without collisions -#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) - -#elif defined(LL_WCHAR_T_NATIVE) // Windows, either clang or MS /Zc:wchar_t -// llwchar (32-bit), wchar_t (16-bit) and U16 are all different types. -// Conversions to/from LLWString, to/from std::wstring and to/from llutf16string -// can all be defined. -#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) -#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) - -#else // ! LL_WCHAR_T_NATIVE: Windows with MS /Zc:wchar_t- -// wchar_t is identical to U16, std::wstring is identical to llutf16string. -// Given conversions to/from LLWString and to/from std::wstring, conversions -// involving llutf16string would collide. -#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing -// but we can define conversions involving std::wstring without collisions -#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) -#endif - -ll_convert_forms(ll_convert_u16_alias, LLWString, llutf16string, utf16str_to_wstring); -ll_convert_forms(ll_convert_u16_alias, llutf16string, LLWString, wstring_to_utf16str); -ll_convert_forms(ll_convert_u16_alias, llutf16string, std::string, utf8str_to_utf16str); -ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_wstring); - -// Same function, better name. JC -inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } - -LL_COMMON_API std::ptrdiff_t wchar_to_utf8chars(llwchar inchar, char* outchars); - -ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str); -ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str); - -// an older alias for utf16str_to_utf8str(llutf16string) -inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);} - -// Length of this UTF32 string in bytes when transformed to UTF8 -LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr); - -// Length in bytes of this wide char in a UTF8 string -LL_COMMON_API S32 wchar_utf8_length(const llwchar wc); - -LL_COMMON_API std::string wchar_utf8_preview(const llwchar wc); - -LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str); - -// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. -LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); - -// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. -LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); - -// Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) -LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL); - -/** - * @brief Properly truncate a utf8 string to a maximum byte count. - * - * The returned string may be less than max_len if the truncation - * happens in the middle of a glyph. If max_len is longer than the - * string passed in, the return value == utf8str. - * @param utf8str A valid utf8 string to truncate. - * @param max_len The maximum number of bytes in the return value. - * @return Returns a valid utf8 string with byte count <= max_len. - */ -LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); - -LL_COMMON_API std::string utf8str_trim(const std::string& utf8str); - -LL_COMMON_API S32 utf8str_compare_insensitive( - const std::string& lhs, - const std::string& rhs); - -/** -* @brief Properly truncate a utf8 string to a maximum character count. -* -* If symbol_len is longer than the string passed in, the return -* value == utf8str. -* @param utf8str A valid utf8 string to truncate. -* @param symbol_len The maximum number of symbols in the return value. -* @return Returns a valid utf8 string with symbol count <= max_len. -*/ -LL_COMMON_API std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len); - -/** - * @brief Replace all occurences of target_char with replace_char - * - * @param utf8str A utf8 string to process. - * @param target_char The wchar to be replaced - * @param replace_char The wchar which is written on replace - */ -LL_COMMON_API std::string utf8str_substChar( - const std::string& utf8str, - const llwchar target_char, - const llwchar replace_char); - -LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str); - -// Hack - used for evil notecards. -LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str); - -LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str); - -LL_COMMON_API llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length); - -LL_COMMON_API std::string utf8str_showBytesUTF8(const std::string& utf8str); - -LL_COMMON_API bool wstring_has_emoji(const LLWString& wstr); - -LL_COMMON_API bool wstring_remove_emojis(LLWString& wstr); - -LL_COMMON_API bool utf8str_remove_emojis(std::string& utf8str); - -#if LL_WINDOWS -/* @name Windows string helpers - */ -//@{ - -/** - * @brief Convert a wide string to/from std::string - * Convert a Windows wide string to/from our LLWString - * - * This replaces the unsafe W2A macro from ATL. - */ -// Avoid requiring this header to #include the Windows header file declaring -// our actual default code_page by delegating this function to our .cpp file. -LL_COMMON_API unsigned int ll_wstring_default_code_page(); - -// This is like ll_convert_forms(), with the added complexity of a code page -// parameter that may or may not be passed. -#define ll_convert_cp_forms(aliasmacro, OUTSTR, INSTR, longname) \ -/* declare the only nontrivial implementation (in .cpp file) */ \ -LL_COMMON_API OUTSTR longname( \ - const INSTR::value_type* in, \ - size_t len, \ - unsigned int code_page=ll_wstring_default_code_page()); \ -/* if passed only a char pointer, scan for nul terminator */ \ -inline auto longname(const INSTR::value_type* in) \ -{ \ - return longname(in, ll_convert_length(in)); \ -} \ -/* if passed string and length, extract its char pointer */ \ -inline auto longname( \ - const INSTR& in, \ - size_t len, \ - unsigned int code_page=ll_wstring_default_code_page()) \ -{ \ - return longname(in.c_str(), len, code_page); \ -} \ -/* if passed only a string object, no scan, pass known length */ \ -inline auto longname(const INSTR& in) \ -{ \ - return longname(in.c_str(), in.length()); \ -} \ -aliasmacro(OUTSTR, INSTR, longname(in)); \ -aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) - -ll_convert_cp_forms(ll_convert_wstr_alias, std::string, std::wstring, ll_convert_wide_to_string); -ll_convert_cp_forms(ll_convert_wstr_alias, std::wstring, std::string, ll_convert_string_to_wide); - ll_convert_forms(ll_convert_wstr_alias, LLWString, std::wstring, ll_convert_wide_to_wstring); - ll_convert_forms(ll_convert_wstr_alias, std::wstring, LLWString, ll_convert_wstring_to_wide); - -/** - * Converts incoming string into utf8 string - * - */ -LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in); - -/// Get Windows message string for passed GetLastError() code -// VS 2013 doesn't let us forward-declare this template, which is what we -// started with, so the implementation could reference the specialization we -// haven't yet declared. Somewhat weirdly, just stating the generic -// implementation in terms of the specialization works, even in this order... - -// the general case is just a conversion from the sole implementation -// Microsoft says DWORD is a typedef for unsigned long -// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types -// so rather than drag windows.h into everybody's include space... -template<typename STRING> -STRING windows_message(unsigned long error) -{ - return ll_convert<STRING>(windows_message<std::wstring>(error)); -} - -/// There's only one real implementation -template<> -LL_COMMON_API std::wstring windows_message<std::wstring>(unsigned long error); - -/// Get Windows message string, implicitly calling GetLastError() -template<typename STRING> -STRING windows_message() { return windows_message<STRING>(GetLastError()); } - -//@} - -LL_COMMON_API boost::optional<std::wstring> llstring_getoptenv(const std::string& key); - -#else // ! LL_WINDOWS - -LL_COMMON_API boost::optional<std::string> llstring_getoptenv(const std::string& key); - -#endif // ! LL_WINDOWS - -/** - * Many of the 'strip' and 'replace' methods of LLStringUtilBase need - * specialization to work with the signed char type. - * Sadly, it is not possible (AFAIK) to specialize a single method of - * a template class. - * That stuff should go here. - */ -namespace LLStringFn -{ - /** - * @brief Replace all non-printable characters with replacement in - * string. - * NOTE - this will zap non-ascii - * - * @param [in,out] string the to modify. out value is the string - * with zero non-printable characters. - * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. - */ - LL_COMMON_API void replace_nonprintable_in_ascii( - std::basic_string<char>& string, - char replacement); - - - /** - * @brief Replace all non-printable characters and pipe characters - * with replacement in a string. - * NOTE - this will zap non-ascii - * - * @param [in,out] the string to modify. out value is the string - * with zero non-printable characters and zero pipe characters. - * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. - */ - LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str, - char replacement); - - - /** - * @brief Remove all characters that are not allowed in XML 1.0. - * Returns a copy of the string with those characters removed. - * Works with US ASCII and UTF-8 encoded strings. JC - */ - LL_COMMON_API std::string strip_invalid_xml(const std::string& input); - - - /** - * @brief Replace all control characters (0 <= c < 0x20) with replacement in - * string. This is safe for utf-8 - * - * @param [in,out] string the to modify. out value is the string - * with zero non-printable characters. - * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. - */ - LL_COMMON_API void replace_ascii_controlchars( - std::basic_string<char>& string, - char replacement); -} - -//////////////////////////////////////////////////////////// -// NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp. -// There is no LLWStringUtil::format implementation currently. -// Calling these for anything other than LLStringUtil will produce link errors. - -//////////////////////////////////////////////////////////// - -// static -template <class T> -std::vector<typename LLStringUtilBase<T>::string_type> -LLStringUtilBase<T>::getTokens(const string_type& instr, const string_type& delims) -{ - std::vector<string_type> tokens; - getTokens(instr, tokens, delims); - return tokens; -} - -// static -template <class T> -std::vector<typename LLStringUtilBase<T>::string_type> -LLStringUtilBase<T>::getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes) -{ - std::vector<string_type> tokens; - getTokens(instr, tokens, drop_delims, keep_delims, quotes); - return tokens; -} - -// static -template <class T> -std::vector<typename LLStringUtilBase<T>::string_type> -LLStringUtilBase<T>::getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes, - const string_type& escapes) -{ - std::vector<string_type> tokens; - getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes); - return tokens; -} - -namespace LLStringUtilBaseImpl -{ - -/** - * Input string scanner helper for getTokens(), or really any other - * character-parsing routine that may have to deal with escape characters. - * This implementation defines the concept (also an interface, should you - * choose to implement the concept by subclassing) and provides trivial - * implementations for a string @em without escape processing. - */ -template <class T> -struct InString -{ - typedef std::basic_string<T> string_type; - typedef typename string_type::const_iterator const_iterator; - - InString(const_iterator b, const_iterator e): - mIter(b), - mEnd(e) - {} - virtual ~InString() {} - - bool done() const { return mIter == mEnd; } - /// Is the current character (*mIter) escaped? This implementation can - /// answer trivially because it doesn't support escapes. - virtual bool escaped() const { return false; } - /// Obtain the current character and advance @c mIter. - virtual T next() { return *mIter++; } - /// Does the current character match specified character? - virtual bool is(T ch) const { return (! done()) && *mIter == ch; } - /// Is the current character any one of the specified characters? - virtual bool oneof(const string_type& delims) const - { - return (! done()) && LLStringUtilBase<T>::contains(delims, *mIter); - } - - /** - * Scan forward from @from until either @a delim or end. This is primarily - * useful for processing quoted substrings. - * - * If we do see @a delim, append everything from @from until (excluding) - * @a delim to @a into, advance @c mIter to skip @a delim, and return @c - * true. - * - * If we do not see @a delim, do not alter @a into or @c mIter and return - * @c false. Do not pass GO, do not collect $200. - * - * @note The @c false case described above implements normal getTokens() - * treatment of an unmatched open quote: treat the quote character as if - * escaped, that is, simply collect it as part of the current token. Other - * plausible behaviors directly affect the way getTokens() deals with an - * unmatched quote: e.g. throwing an exception to treat it as an error, or - * assuming a close quote beyond end of string (in which case return @c - * true). - */ - virtual bool collect_until(string_type& into, const_iterator from, T delim) - { - const_iterator found = std::find(from, mEnd, delim); - // If we didn't find delim, change nothing, just tell caller. - if (found == mEnd) - return false; - // Found delim! Append everything between from and found. - into.append(from, found); - // advance past delim in input - mIter = found + 1; - return true; - } - - const_iterator mIter, mEnd; -}; - -/// InString subclass that handles escape characters -template <class T> -class InEscString: public InString<T> -{ -public: - typedef InString<T> super; - typedef typename super::string_type string_type; - typedef typename super::const_iterator const_iterator; - using super::done; - using super::mIter; - using super::mEnd; - - InEscString(const_iterator b, const_iterator e, const string_type& escapes): - super(b, e), - mEscapes(escapes) - { - // Even though we've already initialized 'mIter' via our base-class - // constructor, set it again to check for initial escape char. - setiter(b); - } - - /// This implementation uses the answer cached by setiter(). - virtual bool escaped() const { return mIsEsc; } - virtual T next() - { - // If we're looking at the escape character of an escape sequence, - // skip that character. This is the one time we can modify 'mIter' - // without using setiter: for this one case we DO NOT CARE if the - // escaped character is itself an escape. - if (mIsEsc) - ++mIter; - // If we were looking at an escape character, this is the escaped - // character; otherwise it's just the next character. - T result(*mIter); - // Advance mIter, checking for escape sequence. - setiter(mIter + 1); - return result; - } - - virtual bool is(T ch) const - { - // Like base-class is(), except that an escaped character matches - // nothing. - return (! done()) && (! mIsEsc) && *mIter == ch; - } - - virtual bool oneof(const string_type& delims) const - { - // Like base-class oneof(), except that an escaped character matches - // nothing. - return (! done()) && (! mIsEsc) && LLStringUtilBase<T>::contains(delims, *mIter); - } - - virtual bool collect_until(string_type& into, const_iterator from, T delim) - { - // Deal with escapes in the characters we collect; that is, an escaped - // character must become just that character without the preceding - // escape. Collect characters in a separate string rather than - // directly appending to 'into' in case we do not find delim, in which - // case we're supposed to leave 'into' unmodified. - string_type collected; - // For scanning purposes, we're going to work directly with 'mIter'. - // Save its current value in case we fail to see delim. - const_iterator save_iter(mIter); - // Okay, set 'mIter', checking for escape. - setiter(from); - while (! done()) - { - // If we see an unescaped delim, stop and report success. - if ((! mIsEsc) && *mIter == delim) - { - // Append collected chars to 'into'. - into.append(collected); - // Don't forget to advance 'mIter' past delim. - setiter(mIter + 1); - return true; - } - // We're not at end, and either we're not looking at delim or it's - // escaped. Collect this character and keep going. - collected.push_back(next()); - } - // Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell - // caller. - setiter(save_iter); - return false; - } - -private: - void setiter(const_iterator i) - { - mIter = i; - - // Every time we change 'mIter', set 'mIsEsc' to be able to repetitively - // answer escaped() without having to rescan 'mEscapes'. mIsEsc caches - // contains(mEscapes, *mIter). - - // We're looking at an escaped char if we're not already at end (that - // is, *mIter is even meaningful); if *mIter is in fact one of the - // specified escape characters; and if there's one more character - // following it. That is, if an escape character is the very last - // character of the input string, it loses its special meaning. - mIsEsc = (! done()) && - LLStringUtilBase<T>::contains(mEscapes, *mIter) && - (mIter+1) != mEnd; - } - - const string_type mEscapes; - bool mIsEsc; -}; - -/// getTokens() implementation based on InString concept -template <typename INSTRING, typename string_type> -void getTokens(INSTRING& instr, std::vector<string_type>& tokens, - const string_type& drop_delims, const string_type& keep_delims, - const string_type& quotes) -{ - // There are times when we want to match either drop_delims or - // keep_delims. Concatenate them up front to speed things up. - string_type all_delims(drop_delims + keep_delims); - // no tokens yet - tokens.clear(); - - // try for another token - while (! instr.done()) - { - // scan past any drop_delims - while (instr.oneof(drop_delims)) - { - // skip this drop_delim - instr.next(); - // but if that was the end of the string, done - if (instr.done()) - return; - } - // found the start of another token: make a slot for it - tokens.push_back(string_type()); - if (instr.oneof(keep_delims)) - { - // *iter is a keep_delim, a token of exactly 1 character. Append - // that character to the new token and proceed. - tokens.back().push_back(instr.next()); - continue; - } - // Here we have a non-delimiter token, which might consist of a mix of - // quoted and unquoted parts. Use bash rules for quoting: you can - // embed a quoted substring in the midst of an unquoted token (e.g. - // ~/"sub dir"/myfile.txt); you can ram two quoted substrings together - // to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge - // from bash in that bash considers an unmatched quote an error. Our - // param signature doesn't allow for errors, so just pretend it's not - // a quote and embed it. - // At this level, keep scanning until we hit the next delimiter of - // either type (drop_delims or keep_delims). - while (! instr.oneof(all_delims)) - { - // If we're looking at an open quote, search forward for - // a close quote, collecting characters along the way. - if (instr.oneof(quotes) && - instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter)) - { - // collect_until is cleverly designed to do exactly what we - // need here. No further action needed if it returns true. - } - else - { - // Either *iter isn't a quote, or there's no matching close - // quote: in other words, just an ordinary char. Append it to - // current token. - tokens.back().push_back(instr.next()); - } - // having scanned that segment of this token, if we've reached the - // end of the string, we're done - if (instr.done()) - return; - } - } -} - -} // namespace LLStringUtilBaseImpl - -// static -template <class T> -void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens, - const string_type& drop_delims, const string_type& keep_delims, - const string_type& quotes) -{ - // Because this overload doesn't support escapes, use simple InString to - // manage input range. - LLStringUtilBaseImpl::InString<T> instring(string.begin(), string.end()); - LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes); -} - -// static -template <class T> -void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens, - const string_type& drop_delims, const string_type& keep_delims, - const string_type& quotes, const string_type& escapes) -{ - // This overload must deal with escapes. Delegate that to InEscString - // (unless there ARE no escapes). - std::unique_ptr< LLStringUtilBaseImpl::InString<T> > instrp; - if (escapes.empty()) - instrp.reset(new LLStringUtilBaseImpl::InString<T>(string.begin(), string.end())); - else - instrp.reset(new LLStringUtilBaseImpl::InEscString<T>(string.begin(), string.end(), escapes)); - LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes); -} - -// static -template<class T> -S32 LLStringUtilBase<T>::compareStrings(const T* lhs, const T* rhs) -{ - S32 result; - if( lhs == rhs ) - { - result = 0; - } - else - if ( !lhs || !lhs[0] ) - { - result = ((!rhs || !rhs[0]) ? 0 : 1); - } - else - if ( !rhs || !rhs[0]) - { - result = -1; - } - else - { - result = LLStringOps::collate(lhs, rhs); - } - return result; -} - -//static -template<class T> -S32 LLStringUtilBase<T>::compareStrings(const string_type& lhs, const string_type& rhs) -{ - return LLStringOps::collate(lhs.c_str(), rhs.c_str()); -} - -// static -template<class T> -S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs ) -{ - S32 result; - if( lhs == rhs ) - { - result = 0; - } - else - if ( !lhs || !lhs[0] ) - { - result = ((!rhs || !rhs[0]) ? 0 : 1); - } - else - if ( !rhs || !rhs[0] ) - { - result = -1; - } - else - { - string_type lhs_string(lhs); - string_type rhs_string(rhs); - LLStringUtilBase<T>::toUpper(lhs_string); - LLStringUtilBase<T>::toUpper(rhs_string); - result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); - } - return result; -} - -//static -template<class T> -S32 LLStringUtilBase<T>::compareInsensitive(const string_type& lhs, const string_type& rhs) -{ - string_type lhs_string(lhs); - string_type rhs_string(rhs); - LLStringUtilBase<T>::toUpper(lhs_string); - LLStringUtilBase<T>::toUpper(rhs_string); - return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); -} - -// Case sensitive comparison with good handling of numbers. Does not use current locale. -// a.k.a. strdictcmp() - -//static -template<class T> -S32 LLStringUtilBase<T>::compareDict(const string_type& astr, const string_type& bstr) -{ - const T* a = astr.c_str(); - const T* b = bstr.c_str(); - T ca, cb; - S32 ai, bi, cnt = 0; - S32 bias = 0; - - ca = *(a++); - cb = *(b++); - while( ca && cb ){ - if( bias==0 ){ - if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); bias--; } - if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); bias++; } - }else{ - if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); } - if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); } - } - if( LLStringOps::isDigit(ca) ){ - if( cnt-->0 ){ - if( cb!=ca ) break; - }else{ - if( !LLStringOps::isDigit(cb) ) break; - for(ai=0; LLStringOps::isDigit(a[ai]); ai++); - for(bi=0; LLStringOps::isDigit(b[bi]); bi++); - if( ai<bi ){ ca=0; break; } - if( bi<ai ){ cb=0; break; } - if( ca!=cb ) break; - cnt = ai; - } - }else if( ca!=cb ){ break; - } - ca = *(a++); - cb = *(b++); - } - if( ca==cb ) ca += bias; - return ca-cb; -} - -// static -template<class T> -S32 LLStringUtilBase<T>::compareDictInsensitive(const string_type& astr, const string_type& bstr) -{ - const T* a = astr.c_str(); - const T* b = bstr.c_str(); - T ca, cb; - S32 ai, bi, cnt = 0; - - ca = *(a++); - cb = *(b++); - while( ca && cb ){ - if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); } - if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); } - if( LLStringOps::isDigit(ca) ){ - if( cnt-->0 ){ - if( cb!=ca ) break; - }else{ - if( !LLStringOps::isDigit(cb) ) break; - for(ai=0; LLStringOps::isDigit(a[ai]); ai++); - for(bi=0; LLStringOps::isDigit(b[bi]); bi++); - if( ai<bi ){ ca=0; break; } - if( bi<ai ){ cb=0; break; } - if( ca!=cb ) break; - cnt = ai; - } - }else if( ca!=cb ){ break; - } - ca = *(a++); - cb = *(b++); - } - return ca-cb; -} - -// Puts compareDict() in a form appropriate for LL container classes to use for sorting. -// static -template<class T> -BOOL LLStringUtilBase<T>::precedesDict( const string_type& a, const string_type& b ) -{ - if( a.size() && b.size() ) - { - return (LLStringUtilBase<T>::compareDict(a.c_str(), b.c_str()) < 0); - } - else - { - return (!b.empty()); - } -} - -//static -template<class T> -void LLStringUtilBase<T>::toUpper(string_type& string) -{ - if( !string.empty() ) - { - std::transform( - string.begin(), - string.end(), - string.begin(), - (T(*)(T)) &LLStringOps::toUpper); - } -} - -//static -template<class T> -void LLStringUtilBase<T>::toLower(string_type& string) -{ - if( !string.empty() ) - { - std::transform( - string.begin(), - string.end(), - string.begin(), - (T(*)(T)) &LLStringOps::toLower); - } -} - -//static -template<class T> -void LLStringUtilBase<T>::trimHead(string_type& string) -{ - if( !string.empty() ) - { - size_type i = 0; - while( i < string.length() && LLStringOps::isSpace( string[i] ) ) - { - i++; - } - string.erase(0, i); - } -} - -//static -template<class T> -void LLStringUtilBase<T>::trimTail(string_type& string) -{ - if( string.size() ) - { - size_type len = string.length(); - size_type i = len; - while( i > 0 && LLStringOps::isSpace( string[i-1] ) ) - { - i--; - } - - string.erase( i, len - i ); - } -} - - -// Replace line feeds with carriage return-line feed pairs. -//static -template<class T> -void LLStringUtilBase<T>::addCRLF(string_type& string) -{ - const T LF = 10; - const T CR = 13; - - // Count the number of line feeds - size_type count = 0; - size_type len = string.size(); - size_type i; - for( i = 0; i < len; i++ ) - { - if( string[i] == LF ) - { - count++; - } - } - - // Insert a carriage return before each line feed - if( count ) - { - size_type size = len + count; - T *t = new T[size]; - size_type j = 0; - for( i = 0; i < len; ++i ) - { - if( string[i] == LF ) - { - t[j] = CR; - ++j; - } - t[j] = string[i]; - ++j; - } - - string.assign(t, size); - delete[] t; - } -} - -// Remove all carriage returns -//static -template<class T> -void LLStringUtilBase<T>::removeCRLF(string_type& string) -{ - const T CR = 13; - - size_type cr_count = 0; - size_type len = string.size(); - size_type i; - for( i = 0; i < len - cr_count; i++ ) - { - if( string[i+cr_count] == CR ) - { - cr_count++; - } - - string[i] = string[i+cr_count]; - } - string.erase(i, cr_count); -} - -//static -template<class T> -void LLStringUtilBase<T>::removeWindowsCR(string_type& string) -{ - if (string.empty()) - { - return; - } - const T LF = 10; - const T CR = 13; - - size_type cr_count = 0; - size_type len = string.size(); - size_type i; - for( i = 0; i < len - cr_count - 1; i++ ) - { - if( string[i+cr_count] == CR && string[i+cr_count+1] == LF) - { - cr_count++; - } - - string[i] = string[i+cr_count]; - } - string.erase(i, cr_count); -} - -//static -template<class T> -void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement ) -{ - size_type found_pos = 0; - while( (found_pos = string.find(target, found_pos)) != string_type::npos ) - { - string[found_pos] = replacement; - found_pos++; // avoid infinite defeat if target == replacement - } -} - -//static -template<class T> -void LLStringUtilBase<T>::replaceString( string_type& string, string_type target, string_type replacement ) -{ - size_type found_pos = 0; - while( (found_pos = string.find(target, found_pos)) != string_type::npos ) - { - string.replace( found_pos, target.length(), replacement ); - found_pos += replacement.length(); // avoid infinite defeat if replacement contains target - } -} - -//static -template<class T> -void LLStringUtilBase<T>::replaceNonstandardASCII( string_type& string, T replacement ) -{ - const char LF = 10; - const S8 MIN = 32; -// const S8 MAX = 127; - - size_type len = string.size(); - for( size_type i = 0; i < len; i++ ) - { - // No need to test MAX < mText[i] because we treat mText[i] as a signed char, - // which has a max value of 127. - if( ( S8(string[i]) < MIN ) && (string[i] != LF) ) - { - string[i] = replacement; - } - } -} - -//static -template<class T> -void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab ) -{ - const T TAB = '\t'; - const T SPACE = ' '; - - string_type out_str; - // Replace tabs with spaces - for (size_type i = 0; i < str.length(); i++) - { - if (str[i] == TAB) - { - for (size_type j = 0; j < spaces_per_tab; j++) - out_str += SPACE; - } - else - { - out_str += str[i]; - } - } - str = out_str; -} - -//static -template<class T> -std::basic_string<T> LLStringUtilBase<T>::capitalize(const string_type& str) -{ - string_type result(str); - capitalize(result); - return result; -} - -//static -template<class T> -void LLStringUtilBase<T>::capitalize(string_type& str) -{ - if (str.size()) - { - auto last = str[0] = toupper(str[0]); - for (U32 i = 1; i < str.size(); ++i) - { - last = (last == ' ' || last == '-' || last == '_') ? str[i] = toupper(str[i]) : str[i]; - } - } -} - -//static -template<class T> -BOOL LLStringUtilBase<T>::containsNonprintable(const string_type& string) -{ - const char MIN = 32; - BOOL rv = FALSE; - for (size_type i = 0; i < string.size(); i++) - { - if(string[i] < MIN) - { - rv = TRUE; - break; - } - } - return rv; -} - -// *TODO: reimplement in terms of algorithm -//static -template<class T> -void LLStringUtilBase<T>::stripNonprintable(string_type& string) -{ - const char MIN = 32; - size_type j = 0; - if (string.empty()) - { - return; - } - size_t src_size = string.size(); - char* c_string = new char[src_size + 1]; - if(c_string == NULL) - { - return; - } - copy(c_string, string.c_str(), src_size+1); - char* write_head = &c_string[0]; - for (size_type i = 0; i < src_size; i++) - { - char* read_head = &string[i]; - write_head = &c_string[j]; - if(!(*read_head < MIN)) - { - *write_head = *read_head; - ++j; - } - } - c_string[j]= '\0'; - string = c_string; - delete []c_string; -} - -// *TODO: reimplement in terms of algorithm -template<class T> -std::basic_string<T> LLStringUtilBase<T>::quote(const string_type& str, - const string_type& triggers, - const string_type& escape) -{ - size_type len(str.length()); - // If the string is already quoted, assume user knows what s/he's doing. - if (len >= 2 && str[0] == '"' && str[len-1] == '"') - { - return str; - } - - // Not already quoted: do we need to? triggers.empty() is a special case - // meaning "always quote." - if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos) - { - // no trigger characters, don't bother quoting - return str; - } - - // For whatever reason, we must quote this string. - string_type result; - result.push_back('"'); - for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) - { - if (*ci == '"') - { - result.append(escape); - } - result.push_back(*ci); - } - result.push_back('"'); - return result; -} - -template<class T> -void LLStringUtilBase<T>::_makeASCII(string_type& string) -{ - // Replace non-ASCII chars with LL_UNKNOWN_CHAR - for (size_type i = 0; i < string.length(); i++) - { - if (string[i] > 0x7f) - { - string[i] = LL_UNKNOWN_CHAR; - } - } -} - -// static -template<class T> -void LLStringUtilBase<T>::copy( T* dst, const T* src, size_type dst_size ) -{ - if( dst_size > 0 ) - { - size_type min_len = 0; - if( src ) - { - min_len = llmin( dst_size - 1, strlen( src ) ); /* Flawfinder: ignore */ - memcpy(dst, src, min_len * sizeof(T)); /* Flawfinder: ignore */ - } - dst[min_len] = '\0'; - } -} - -// static -template<class T> -void LLStringUtilBase<T>::copyInto(string_type& dst, const string_type& src, size_type offset) -{ - if ( offset == dst.length() ) - { - // special case - append to end of string and avoid expensive - // (when strings are large) string manipulations - dst += src; - } - else - { - string_type tail = dst.substr(offset); - - dst = dst.substr(0, offset); - dst += src; - dst += tail; - }; -} - -// True if this is the head of s. -//static -template<class T> -BOOL LLStringUtilBase<T>::isHead( const string_type& string, const T* s ) -{ - if( string.empty() ) - { - // Early exit - return FALSE; - } - else - { - return (strncmp( s, string.c_str(), string.size() ) == 0); - } -} - -// static -template<class T> -bool LLStringUtilBase<T>::startsWith( - const string_type& string, - const string_type& substr) -{ - if(string.empty() || (substr.empty())) return false; - if (substr.length() > string.length()) return false; - if (0 == string.compare(0, substr.length(), substr)) return true; - return false; -} - -// static -template<class T> -bool LLStringUtilBase<T>::endsWith( - const string_type& string, - const string_type& substr) -{ - if(string.empty() || (substr.empty())) return false; - size_t sub_len = substr.length(); - size_t str_len = string.length(); - if (sub_len > str_len) return false; - if (0 == string.compare(str_len - sub_len, sub_len, substr)) return true; - return false; -} - -// static -template<class T> -auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> boost::optional<string_type> -{ - auto found(llstring_getoptenv(key)); - if (found) - { - // return populated boost::optional - return { ll_convert<string_type>(*found) }; - } - else - { - // empty boost::optional - return {}; - } -} - -// static -template<class T> -auto LLStringUtilBase<T>::getenv(const std::string& key, const string_type& dflt) -> string_type -{ - auto found(getoptenv(key)); - if (found) - { - return *found; - } - else - { - return dflt; - } -} - -template<class T> -BOOL LLStringUtilBase<T>::convertToBOOL(const string_type& string, BOOL& value) -{ - if( string.empty() ) - { - return FALSE; - } - - string_type temp( string ); - trim(temp); - if( - (temp == "1") || - (temp == "T") || - (temp == "t") || - (temp == "TRUE") || - (temp == "true") || - (temp == "True") ) - { - value = TRUE; - return TRUE; - } - else - if( - (temp == "0") || - (temp == "F") || - (temp == "f") || - (temp == "FALSE") || - (temp == "false") || - (temp == "False") ) - { - value = FALSE; - return TRUE; - } - - return FALSE; -} - -template<class T> -BOOL LLStringUtilBase<T>::convertToU8(const string_type& string, U8& value) -{ - S32 value32 = 0; - BOOL success = convertToS32(string, value32); - if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) ) - { - value = (U8) value32; - return TRUE; - } - return FALSE; -} - -template<class T> -BOOL LLStringUtilBase<T>::convertToS8(const string_type& string, S8& value) -{ - S32 value32 = 0; - BOOL success = convertToS32(string, value32); - if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) ) - { - value = (S8) value32; - return TRUE; - } - return FALSE; -} - -template<class T> -BOOL LLStringUtilBase<T>::convertToS16(const string_type& string, S16& value) -{ - S32 value32 = 0; - BOOL success = convertToS32(string, value32); - if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) ) - { - value = (S16) value32; - return TRUE; - } - return FALSE; -} - -template<class T> -BOOL LLStringUtilBase<T>::convertToU16(const string_type& string, U16& value) -{ - S32 value32 = 0; - BOOL success = convertToS32(string, value32); - if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) ) - { - value = (U16) value32; - return TRUE; - } - return FALSE; -} - -template<class T> -BOOL LLStringUtilBase<T>::convertToU32(const string_type& string, U32& value) -{ - if( string.empty() ) - { - return FALSE; - } - - string_type temp( string ); - trim(temp); - U32 v; - std::basic_istringstream<T> i_stream((string_type)temp); - if(i_stream >> v) - { - value = v; - return TRUE; - } - return FALSE; -} - -template<class T> -BOOL LLStringUtilBase<T>::convertToS32(const string_type& string, S32& value) -{ - if( string.empty() ) - { - return FALSE; - } - - string_type temp( string ); - trim(temp); - S32 v; - std::basic_istringstream<T> i_stream((string_type)temp); - if(i_stream >> v) - { - //TODO: figure out overflow and underflow reporting here - //if((LONG_MAX == v) || (LONG_MIN == v)) - //{ - // // Underflow or overflow - // return FALSE; - //} - - value = v; - return TRUE; - } - return FALSE; -} - -template<class T> -BOOL LLStringUtilBase<T>::convertToF32(const string_type& string, F32& value) -{ - F64 value64 = 0.0; - BOOL success = convertToF64(string, value64); - if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) ) - { - value = (F32) value64; - return TRUE; - } - return FALSE; -} - -template<class T> -BOOL LLStringUtilBase<T>::convertToF64(const string_type& string, F64& value) -{ - if( string.empty() ) - { - return FALSE; - } - - string_type temp( string ); - trim(temp); - F64 v; - std::basic_istringstream<T> i_stream((string_type)temp); - if(i_stream >> v) - { - //TODO: figure out overflow and underflow reporting here - //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) ) - //{ - // // Underflow or overflow - // return FALSE; - //} - - value = v; - return TRUE; - } - return FALSE; -} - -template<class T> -void LLStringUtilBase<T>::truncate(string_type& string, size_type count) -{ - size_type cur_size = string.size(); - string.resize(count < cur_size ? count : cur_size); -} - -// The good thing about *declaration* macros, vs. usage macros, is that now -// we're done with them: we don't need them to bleed into the consuming source -// file. -#undef ll_convert_alias -#undef ll_convert_u16_alias -#undef ll_convert_wstr_alias -#undef LL_CONVERT_COPY_CHARS -#undef ll_convert_forms -#undef ll_convert_cp_forms - -#endif // LL_STRING_H +/**
+ * @file llstring.h
+ * @brief String utility functions and std::string class.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLSTRING_H
+#define LL_LLSTRING_H
+
+#include <boost/call_traits.hpp>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <cstdio>
+#include <cwchar> // std::wcslen()
+//#include <locale>
+#include <iomanip>
+#include <algorithm>
+#include <vector>
+#include <map>
+#include "llformat.h"
+
+#if LL_LINUX
+#include <wctype.h>
+#include <wchar.h>
+#endif
+
+#include <string.h>
+#include <boost/scoped_ptr.hpp>
+
+const char LL_UNKNOWN_CHAR = '?';
+class LLSD;
+
+#if LL_DARWIN || LL_LINUX
+// Template specialization of char_traits for U16s. Only necessary on Mac and Linux (exists on Windows already)
+#include <cstring>
+
+namespace std
+{
+template<>
+struct char_traits<U16>
+{
+ typedef U16 char_type;
+ typedef int int_type;
+ typedef streampos pos_type;
+ typedef streamoff off_type;
+ typedef mbstate_t state_type;
+
+ static void
+ assign(char_type& __c1, const char_type& __c2)
+ { __c1 = __c2; }
+
+ static bool
+ eq(const char_type& __c1, const char_type& __c2)
+ { return __c1 == __c2; }
+
+ static bool
+ lt(const char_type& __c1, const char_type& __c2)
+ { return __c1 < __c2; }
+
+ static int
+ compare(const char_type* __s1, const char_type* __s2, size_t __n)
+ { return memcmp(__s1, __s2, __n * sizeof(char_type)); }
+
+ static size_t
+ length(const char_type* __s)
+ {
+ const char_type *cur_char = __s;
+ while (*cur_char != 0)
+ {
+ ++cur_char;
+ }
+ return cur_char - __s;
+ }
+
+ static const char_type*
+ find(const char_type* __s, size_t __n, const char_type& __a)
+ { return static_cast<const char_type*>(memchr(__s, __a, __n * sizeof(char_type))); }
+
+ static char_type*
+ move(char_type* __s1, const char_type* __s2, size_t __n)
+ { return static_cast<char_type*>(memmove(__s1, __s2, __n * sizeof(char_type))); }
+
+ static char_type*
+ copy(char_type* __s1, const char_type* __s2, size_t __n)
+ { return static_cast<char_type*>(memcpy(__s1, __s2, __n * sizeof(char_type))); } /* Flawfinder: ignore */
+
+ static char_type*
+ assign(char_type* __s, size_t __n, char_type __a)
+ {
+ // This isn't right.
+ //return static_cast<char_type*>(memset(__s, __a, __n * sizeof(char_type)));
+
+ // I don't think there's a standard 'memset' for 16-bit values.
+ // Do this the old-fashioned way.
+
+ size_t __i;
+ for(__i = 0; __i < __n; __i++)
+ {
+ __s[__i] = __a;
+ }
+ return __s;
+ }
+
+ static char_type
+ to_char_type(const int_type& __c)
+ { return static_cast<char_type>(__c); }
+
+ static int_type
+ to_int_type(const char_type& __c)
+ { return static_cast<int_type>(__c); }
+
+ static bool
+ eq_int_type(const int_type& __c1, const int_type& __c2)
+ { return __c1 == __c2; }
+
+ static int_type
+ eof() { return static_cast<int_type>(EOF); }
+
+ static int_type
+ not_eof(const int_type& __c)
+ { return (__c == eof()) ? 0 : __c; }
+ };
+};
+#endif
+
+class LL_COMMON_API LLStringOps
+{
+private:
+ static long sPacificTimeOffset;
+ static long sLocalTimeOffset;
+ static bool sPacificDaylightTime;
+
+ static std::map<std::string, std::string> datetimeToCodes;
+
+public:
+ static std::vector<std::string> sWeekDayList;
+ static std::vector<std::string> sWeekDayShortList;
+ static std::vector<std::string> sMonthList;
+ static std::vector<std::string> sMonthShortList;
+ static std::string sDayFormat;
+
+ static std::string sAM;
+ static std::string sPM;
+
+ static char toUpper(char elem) { return toupper((unsigned char)elem); }
+ static llwchar toUpper(llwchar elem) { return towupper(elem); }
+
+ static char toLower(char elem) { return tolower((unsigned char)elem); }
+ static llwchar toLower(llwchar elem) { return towlower(elem); }
+
+ static bool isSpace(char elem) { return isspace((unsigned char)elem) != 0; }
+ static bool isSpace(llwchar elem) { return iswspace(elem) != 0; }
+
+ static bool isUpper(char elem) { return isupper((unsigned char)elem) != 0; }
+ static bool isUpper(llwchar elem) { return iswupper(elem) != 0; }
+
+ static bool isLower(char elem) { return islower((unsigned char)elem) != 0; }
+ static bool isLower(llwchar elem) { return iswlower(elem) != 0; }
+
+ static bool isDigit(char a) { return isdigit((unsigned char)a) != 0; }
+ static bool isDigit(llwchar a) { return iswdigit(a) != 0; }
+
+ static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; }
+ static bool isPunct(llwchar a) { return iswpunct(a) != 0; }
+
+ static bool isAlpha(char a) { return isalpha((unsigned char)a) != 0; }
+ static bool isAlpha(llwchar a) { return iswalpha(a) != 0; }
+
+ static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; }
+ static bool isAlnum(llwchar a) { return iswalnum(a) != 0; }
+
+ // Returns true when 'a' corresponds to a "genuine" emoji. HB
+ static bool isEmoji(llwchar a);
+
+ static S32 collate(const char* a, const char* b) { return strcoll(a, b); }
+ static S32 collate(const llwchar* a, const llwchar* b);
+
+ static void setupDatetimeInfo(bool pacific_daylight_time);
+
+ static void setupWeekDaysNames(const std::string& data);
+ static void setupWeekDaysShortNames(const std::string& data);
+ static void setupMonthNames(const std::string& data);
+ static void setupMonthShortNames(const std::string& data);
+ static void setupDayFormat(const std::string& data);
+
+
+ static long getPacificTimeOffset(void) { return sPacificTimeOffset;}
+ static long getLocalTimeOffset(void) { return sLocalTimeOffset;}
+ // Is the Pacific time zone (aka server time zone)
+ // currently in daylight savings time?
+ static bool getPacificDaylightTime(void) { return sPacificDaylightTime;}
+
+ static std::string getDatetimeCode (std::string key);
+
+ // Express a value like 1234567 as "1.23M"
+ static std::string getReadableNumber(F64 num);
+};
+
+/**
+ * @brief Return a string constructed from in without crashing if the
+ * pointer is NULL.
+ */
+LL_COMMON_API std::string ll_safe_string(const char* in);
+LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen);
+
+
+// Allowing assignments from non-strings into format_map_t is apparently
+// *really* error-prone, so subclass std::string with just basic c'tors.
+class LLFormatMapString
+{
+public:
+ LLFormatMapString() {};
+ LLFormatMapString(const char* s) : mString(ll_safe_string(s)) {};
+ LLFormatMapString(const std::string& s) : mString(s) {};
+ operator std::string() const { return mString; }
+ bool operator<(const LLFormatMapString& rhs) const { return mString < rhs.mString; }
+ std::size_t length() const { return mString.length(); }
+
+private:
+ std::string mString;
+};
+
+template <class T>
+class LLStringUtilBase
+{
+private:
+ static std::string sLocale;
+
+public:
+ typedef std::basic_string<T> string_type;
+ typedef typename string_type::size_type size_type;
+
+public:
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // Static Utility functions that operate on std::strings
+
+ static const string_type null;
+
+ typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t;
+ /// considers any sequence of delims as a single field separator
+ LL_COMMON_API static void getTokens(const string_type& instr,
+ std::vector<string_type >& tokens,
+ const string_type& delims);
+ /// like simple scan overload, but returns scanned vector
+ static std::vector<string_type> getTokens(const string_type& instr,
+ const string_type& delims);
+ /// add support for keep_delims and quotes (either could be empty string)
+ static void getTokens(const string_type& instr,
+ std::vector<string_type>& tokens,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes=string_type());
+ /// like keep_delims-and-quotes overload, but returns scanned vector
+ static std::vector<string_type> getTokens(const string_type& instr,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes=string_type());
+ /// add support for escapes (could be empty string)
+ static void getTokens(const string_type& instr,
+ std::vector<string_type>& tokens,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes,
+ const string_type& escapes);
+ /// like escapes overload, but returns scanned vector
+ static std::vector<string_type> getTokens(const string_type& instr,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes,
+ const string_type& escapes);
+
+ LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals);
+ LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch);
+ LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions);
+ LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions);
+ LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions);
+ LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions);
+ LL_COMMON_API static void setLocale (std::string inLocale);
+ LL_COMMON_API static std::string getLocale (void);
+
+ static bool isValidIndex(const string_type& string, size_type i)
+ {
+ return !string.empty() && (0 <= i) && (i <= string.size());
+ }
+
+ static bool contains(const string_type& string, T c, size_type i=0)
+ {
+ return string.find(c, i) != string_type::npos;
+ }
+
+ static void trimHead(string_type& string);
+ static void trimTail(string_type& string);
+ static void trim(string_type& string) { trimHead(string); trimTail(string); }
+ static void truncate(string_type& string, size_type count);
+
+ static void toUpper(string_type& string);
+ static void toLower(string_type& string);
+
+ // True if this is the head of s.
+ static bool isHead( const string_type& string, const T* s );
+
+ /**
+ * @brief Returns true if string starts with substr
+ *
+ * If etither string or substr are empty, this method returns false.
+ */
+ static bool startsWith(
+ const string_type& string,
+ const string_type& substr);
+
+ /**
+ * @brief Returns true if string ends in substr
+ *
+ * If etither string or substr are empty, this method returns false.
+ */
+ static bool endsWith(
+ const string_type& string,
+ const string_type& substr);
+
+ /**
+ * get environment string value with proper Unicode handling
+ * (key is always UTF-8)
+ * detect absence by return value == dflt
+ */
+ static string_type getenv(const std::string& key, const string_type& dflt="");
+ /**
+ * get optional environment string value with proper Unicode handling
+ * (key is always UTF-8)
+ * detect absence by (! return value)
+ */
+ static std::optional<string_type> getoptenv(const std::string& key);
+
+ static void addCRLF(string_type& string);
+ static void removeCRLF(string_type& string);
+ static void removeWindowsCR(string_type& string);
+
+ static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab );
+ static void replaceNonstandardASCII( string_type& string, T replacement );
+ static void replaceChar( string_type& string, T target, T replacement );
+ static void replaceString( string_type& string, string_type target, string_type replacement );
+ static string_type capitalize(const string_type& str);
+ static void capitalize(string_type& str);
+
+ static bool containsNonprintable(const string_type& string);
+ static void stripNonprintable(string_type& string);
+
+ /**
+ * Double-quote an argument string if needed, unless it's already
+ * double-quoted. Decide whether it's needed based on the presence of any
+ * character in @a triggers (default space or double-quote). If we quote
+ * it, escape any embedded double-quote with the @a escape string (default
+ * backslash).
+ *
+ * Passing triggers="" means always quote, unless it's already double-quoted.
+ */
+ static string_type quote(const string_type& str,
+ const string_type& triggers=" \"",
+ const string_type& escape="\\");
+
+ /**
+ * @brief Unsafe way to make ascii characters. You should probably
+ * only call this when interacting with the host operating system.
+ * The 1 byte std::string does not work correctly.
+ * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII
+ * should work.
+ */
+ static void _makeASCII(string_type& string);
+
+ // Conversion to other data types
+ static bool convertToBOOL(const string_type& string, bool& value);
+ static bool convertToU8(const string_type& string, U8& value);
+ static bool convertToS8(const string_type& string, S8& value);
+ static bool convertToS16(const string_type& string, S16& value);
+ static bool convertToU16(const string_type& string, U16& value);
+ static bool convertToU32(const string_type& string, U32& value);
+ static bool convertToS32(const string_type& string, S32& value);
+ static bool convertToF32(const string_type& string, F32& value);
+ static bool convertToF64(const string_type& string, F64& value);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // Utility functions for working with char*'s and strings
+
+ // Like strcmp but also handles empty strings. Uses
+ // current locale.
+ static S32 compareStrings(const T* lhs, const T* rhs);
+ static S32 compareStrings(const string_type& lhs, const string_type& rhs);
+
+ // case insensitive version of above. Uses current locale on
+ // Win32, and falls back to a non-locale aware comparison on
+ // Linux.
+ static S32 compareInsensitive(const T* lhs, const T* rhs);
+ static S32 compareInsensitive(const string_type& lhs, const string_type& rhs);
+
+ // Case sensitive comparison with good handling of numbers. Does not use current locale.
+ // a.k.a. strdictcmp()
+ static S32 compareDict(const string_type& a, const string_type& b);
+
+ // Case *in*sensitive comparison with good handling of numbers. Does not use current locale.
+ // a.k.a. strdictcmp()
+ static S32 compareDictInsensitive(const string_type& a, const string_type& b);
+
+ // Puts compareDict() in a form appropriate for LL container classes to use for sorting.
+ static bool precedesDict( const string_type& a, const string_type& b );
+
+ // A replacement for strncpy.
+ // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds
+ // up to dst_size-1 characters of src.
+ static void copy(T* dst, const T* src, size_type dst_size);
+
+ // Copies src into dst at a given offset.
+ static void copyInto(string_type& dst, const string_type& src, size_type offset);
+
+ static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); }
+
+
+#ifdef _DEBUG
+ LL_COMMON_API static void testHarness();
+#endif
+
+private:
+ LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector<string_type >& tokens);
+};
+
+template<class T> const std::basic_string<T> LLStringUtilBase<T>::null;
+template<class T> std::string LLStringUtilBase<T>::sLocale;
+
+typedef LLStringUtilBase<char> LLStringUtil;
+typedef LLStringUtilBase<llwchar> LLWStringUtil;
+typedef std::basic_string<llwchar> LLWString;
+
+//@ Use this where we want to disallow input in the form of "foo"
+// This is used to catch places where english text is embedded in the code
+// instead of in a translatable XUI file.
+class LLStringExplicit : public std::string
+{
+public:
+ explicit LLStringExplicit(const char* s) : std::string(s) {}
+ LLStringExplicit(const std::string& s) : std::string(s) {}
+ LLStringExplicit(const std::string& s, size_type pos, size_type n = std::string::npos) : std::string(s, pos, n) {}
+};
+
+struct LLDictionaryLess
+{
+public:
+ bool operator()(const std::string& a, const std::string& b) const
+ {
+ return (LLStringUtil::precedesDict(a, b));
+ }
+};
+
+
+/**
+ * Simple support functions
+ */
+
+/**
+ * @brief chop off the trailing characters in a string.
+ *
+ * This function works on bytes rather than glyphs, so this will
+ * incorrectly truncate non-single byte strings.
+ * Use utf8str_truncate() for utf8 strings
+ * @return a copy of in string minus the trailing count bytes.
+ */
+inline std::string chop_tail_copy(
+ const std::string& in,
+ std::string::size_type count)
+{
+ return std::string(in, 0, in.length() - count);
+}
+
+/**
+ * @brief This translates a nybble stored as a hex value from 0-f back
+ * to a nybble in the low order bits of the return byte.
+ */
+LL_COMMON_API bool is_char_hex(char hex);
+LL_COMMON_API U8 hex_as_nybble(char hex);
+
+/**
+ * @brief read the contents of a file into a string.
+ *
+ * Since this function has no concept of character encoding, most
+ * anything you do with this method ill-advised. Please avoid.
+ * @param str [out] The string which will have.
+ * @param filename The full name of the file to read.
+ * @return Returns true on success. If false, str is unmodified.
+ */
+LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename);
+LL_COMMON_API bool iswindividual(llwchar elem);
+
+/**
+ * Unicode support
+ */
+
+/// generic conversion aliases
+template<typename TO, typename FROM, typename Enable=void>
+struct ll_convert_impl
+{
+ // Don't even provide a generic implementation. We specialize for every
+ // combination we do support.
+ TO operator()(const FROM& in) const;
+};
+
+// Use a function template to get the nice ll_convert<TO>(from_value) API.
+template<typename TO, typename FROM>
+TO ll_convert(const FROM& in)
+{
+ return ll_convert_impl<TO, FROM>()(in);
+}
+
+// degenerate case
+template<typename T>
+struct ll_convert_impl<T, T>
+{
+ T operator()(const T& in) const { return in; }
+};
+
+// simple construction from char*
+template<typename T>
+struct ll_convert_impl<T, const typename T::value_type*>
+{
+ T operator()(const typename T::value_type* in) const { return { in }; }
+};
+
+// specialize ll_convert_impl<TO, FROM> to return EXPR
+#define ll_convert_alias(TO, FROM, EXPR) \
+template<> \
+struct ll_convert_impl<TO, FROM> \
+{ \
+ /* param_type optimally passes both char* and string */ \
+ TO operator()(typename boost::call_traits<FROM>::param_type in) const { return EXPR; } \
+}
+
+// If all we're doing is copying characters, pass this to ll_convert_alias as
+// EXPR. Since it expands into the 'return EXPR' slot in the ll_convert_impl
+// specialization above, it implies TO{ in.begin(), in.end() }.
+#define LL_CONVERT_COPY_CHARS { in.begin(), in.end() }
+
+// Generic name for strlen() / wcslen() - the default implementation should
+// (!) work with U16 and llwchar, but we don't intend to engage it.
+template <typename CHARTYPE>
+size_t ll_convert_length(const CHARTYPE* zstr)
+{
+ const CHARTYPE* zp;
+ // classic C string scan
+ for (zp = zstr; *zp; ++zp)
+ ;
+ return (zp - zstr);
+}
+
+// specialize where we have a library function; may use intrinsic operations
+template <>
+inline size_t ll_convert_length<wchar_t>(const wchar_t* zstr) { return std::wcslen(zstr); }
+template <>
+inline size_t ll_convert_length<char> (const char* zstr) { return std::strlen(zstr); }
+
+// ll_convert_forms() is short for a bunch of boilerplate. It defines
+// longname(const char*, len), longname(const char*), longname(const string&)
+// and longname(const string&, len) so calls written pre-ll_convert() will
+// work. Most of these overloads will be unified once we turn on C++17 and can
+// use std::string_view.
+// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*)
+// and ll_convert<OUTSTR>(const string&) will work.
+#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \
+LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \
+inline auto longname(const INSTR& in, size_t len) \
+{ \
+ return longname(in.c_str(), len); \
+} \
+inline auto longname(const INSTR::value_type* in) \
+{ \
+ return longname(in, ll_convert_length(in)); \
+} \
+inline auto longname(const INSTR& in) \
+{ \
+ return longname(in.c_str(), in.length()); \
+} \
+/* string param */ \
+aliasmacro(OUTSTR, INSTR, longname(in)); \
+/* char* param */ \
+aliasmacro(OUTSTR, const INSTR::value_type*, longname(in))
+
+// Make the incoming string a utf8 string. Replaces any unknown glyph
+// with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest
+// of the data may not be recovered.
+LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw);
+
+//
+// We should never use UTF16 except when communicating with Win32!
+// https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t
+// nat 2018-12-14: I consider the whole llutf16string thing a mistake, because
+// the Windows APIs we want to call are all defined in terms of wchar_t*
+// (or worse, LPCTSTR).
+// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types
+
+// While there is no point coding for an ASCII-only world (! defined(UNICODE)),
+// use of U16 and llutf16string for Windows APIs locks in /Zc:wchar_t-. Going
+// forward, we should code in terms of wchar_t and std::wstring so as to
+// support either setting of /Zc:wchar_t.
+
+// The first link above states that char can be used to hold ASCII or any
+// multi-byte character set, and distinguishes wchar_t (UTF-16LE), char16_t
+// (UTF-16) and char32_t (UTF-32). Nonetheless, within this code base:
+// * char and std::string always hold UTF-8 (of which ASCII is a subset). It
+// is a BUG if they are used to pass strings in any other multi-byte
+// encoding.
+// * wchar_t and std::wstring should be our interface to Windows wide-string
+// APIs, and therefore hold UTF-16LE.
+// * U16 and llutf16string are the previous but DEPRECATED UTF-16LE type. Do
+// not introduce new uses of U16 or llutf16string for string data.
+// * llwchar and LLWString hold UTF-32 strings.
+// * Do not introduce char16_t or std::u16string.
+// * Do not introduce char32_t or std::u32string.
+//
+// This typedef may or may not be identical to std::wstring, depending on
+// LL_WCHAR_T_NATIVE.
+typedef std::basic_string<U16> llutf16string;
+
+// Considering wchar_t, llwchar and U16, there are three relevant cases:
+#if LLWCHAR_IS_WCHAR_T // every which way but Windows
+// llwchar is identical to wchar_t, LLWString is identical to std::wstring.
+// U16 is distinct, llutf16string is distinct (though pretty useless).
+// Given conversions to/from LLWString and to/from llutf16string, conversions
+// involving std::wstring would collide.
+#define ll_convert_wstr_alias(TO, FROM, EXPR) // nothing
+// but we can define conversions involving llutf16string without collisions
+#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
+
+#elif defined(LL_WCHAR_T_NATIVE) // Windows, either clang or MS /Zc:wchar_t
+// llwchar (32-bit), wchar_t (16-bit) and U16 are all different types.
+// Conversions to/from LLWString, to/from std::wstring and to/from llutf16string
+// can all be defined.
+#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
+#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
+
+#else // ! LL_WCHAR_T_NATIVE: Windows with MS /Zc:wchar_t-
+// wchar_t is identical to U16, std::wstring is identical to llutf16string.
+// Given conversions to/from LLWString and to/from std::wstring, conversions
+// involving llutf16string would collide.
+#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing
+// but we can define conversions involving std::wstring without collisions
+#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
+#endif
+
+ll_convert_forms(ll_convert_u16_alias, LLWString, llutf16string, utf16str_to_wstring);
+ll_convert_forms(ll_convert_u16_alias, llutf16string, LLWString, wstring_to_utf16str);
+ll_convert_forms(ll_convert_u16_alias, llutf16string, std::string, utf8str_to_utf16str);
+ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_wstring);
+
+// Same function, better name. JC
+inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); }
+
+LL_COMMON_API std::ptrdiff_t wchar_to_utf8chars(llwchar inchar, char* outchars);
+
+ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str);
+ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str);
+
+// an older alias for utf16str_to_utf8str(llutf16string)
+inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);}
+
+// Length of this UTF32 string in bytes when transformed to UTF8
+LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr);
+
+// Length in bytes of this wide char in a UTF8 string
+LL_COMMON_API S32 wchar_utf8_length(const llwchar wc);
+
+LL_COMMON_API std::string wchar_utf8_preview(const llwchar wc);
+
+LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str);
+
+// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string.
+LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len);
+
+// Length in utf16string (UTF-16) of wlen wchars beginning at woffset.
+LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen);
+
+// Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.)
+LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, bool *unaligned = nullptr);
+
+/**
+ * @brief Properly truncate a utf8 string to a maximum byte count.
+ *
+ * The returned string may be less than max_len if the truncation
+ * happens in the middle of a glyph. If max_len is longer than the
+ * string passed in, the return value == utf8str.
+ * @param utf8str A valid utf8 string to truncate.
+ * @param max_len The maximum number of bytes in the return value.
+ * @return Returns a valid utf8 string with byte count <= max_len.
+ */
+LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32 max_len);
+
+LL_COMMON_API std::string utf8str_trim(const std::string& utf8str);
+
+LL_COMMON_API S32 utf8str_compare_insensitive(
+ const std::string& lhs,
+ const std::string& rhs);
+
+/**
+* @brief Properly truncate a utf8 string to a maximum character count.
+*
+* If symbol_len is longer than the string passed in, the return
+* value == utf8str.
+* @param utf8str A valid utf8 string to truncate.
+* @param symbol_len The maximum number of symbols in the return value.
+* @return Returns a valid utf8 string with symbol count <= max_len.
+*/
+LL_COMMON_API std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len);
+
+/**
+ * @brief Replace all occurences of target_char with replace_char
+ *
+ * @param utf8str A utf8 string to process.
+ * @param target_char The wchar to be replaced
+ * @param replace_char The wchar which is written on replace
+ */
+LL_COMMON_API std::string utf8str_substChar(
+ const std::string& utf8str,
+ const llwchar target_char,
+ const llwchar replace_char);
+
+LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str);
+
+// Hack - used for evil notecards.
+LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str);
+
+LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str);
+
+LL_COMMON_API llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length);
+
+LL_COMMON_API std::string utf8str_showBytesUTF8(const std::string& utf8str);
+
+LL_COMMON_API bool wstring_has_emoji(const LLWString& wstr);
+
+LL_COMMON_API bool wstring_remove_emojis(LLWString& wstr);
+
+LL_COMMON_API bool utf8str_remove_emojis(std::string& utf8str);
+
+#if LL_WINDOWS
+/* @name Windows string helpers
+ */
+//@{
+
+/**
+ * @brief Convert a wide string to/from std::string
+ * Convert a Windows wide string to/from our LLWString
+ *
+ * This replaces the unsafe W2A macro from ATL.
+ */
+// Avoid requiring this header to #include the Windows header file declaring
+// our actual default code_page by delegating this function to our .cpp file.
+LL_COMMON_API unsigned int ll_wstring_default_code_page();
+
+// This is like ll_convert_forms(), with the added complexity of a code page
+// parameter that may or may not be passed.
+#define ll_convert_cp_forms(aliasmacro, OUTSTR, INSTR, longname) \
+/* declare the only nontrivial implementation (in .cpp file) */ \
+LL_COMMON_API OUTSTR longname( \
+ const INSTR::value_type* in, \
+ size_t len, \
+ unsigned int code_page=ll_wstring_default_code_page()); \
+/* if passed only a char pointer, scan for nul terminator */ \
+inline auto longname(const INSTR::value_type* in) \
+{ \
+ return longname(in, ll_convert_length(in)); \
+} \
+/* if passed string and length, extract its char pointer */ \
+inline auto longname( \
+ const INSTR& in, \
+ size_t len, \
+ unsigned int code_page=ll_wstring_default_code_page()) \
+{ \
+ return longname(in.c_str(), len, code_page); \
+} \
+/* if passed only a string object, no scan, pass known length */ \
+inline auto longname(const INSTR& in) \
+{ \
+ return longname(in.c_str(), in.length()); \
+} \
+aliasmacro(OUTSTR, INSTR, longname(in)); \
+aliasmacro(OUTSTR, const INSTR::value_type*, longname(in))
+
+ll_convert_cp_forms(ll_convert_wstr_alias, std::string, std::wstring, ll_convert_wide_to_string);
+ll_convert_cp_forms(ll_convert_wstr_alias, std::wstring, std::string, ll_convert_string_to_wide);
+ ll_convert_forms(ll_convert_wstr_alias, LLWString, std::wstring, ll_convert_wide_to_wstring);
+ ll_convert_forms(ll_convert_wstr_alias, std::wstring, LLWString, ll_convert_wstring_to_wide);
+
+/**
+ * Converts incoming string into utf8 string
+ *
+ */
+LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in);
+
+/// Get Windows message string for passed GetLastError() code
+// VS 2013 doesn't let us forward-declare this template, which is what we
+// started with, so the implementation could reference the specialization we
+// haven't yet declared. Somewhat weirdly, just stating the generic
+// implementation in terms of the specialization works, even in this order...
+
+// the general case is just a conversion from the sole implementation
+// Microsoft says DWORD is a typedef for unsigned long
+// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types
+// so rather than drag windows.h into everybody's include space...
+template<typename STRING>
+STRING windows_message(unsigned long error)
+{
+ return ll_convert<STRING>(windows_message<std::wstring>(error));
+}
+
+/// There's only one real implementation
+template<>
+LL_COMMON_API std::wstring windows_message<std::wstring>(unsigned long error);
+
+/// Get Windows message string, implicitly calling GetLastError()
+template<typename STRING>
+STRING windows_message() { return windows_message<STRING>(GetLastError()); }
+
+//@}
+
+LL_COMMON_API std::optional<std::wstring> llstring_getoptenv(const std::string& key);
+
+#else // ! LL_WINDOWS
+
+LL_COMMON_API std::optional<std::string> llstring_getoptenv(const std::string& key);
+
+#endif // ! LL_WINDOWS
+
+/**
+ * Many of the 'strip' and 'replace' methods of LLStringUtilBase need
+ * specialization to work with the signed char type.
+ * Sadly, it is not possible (AFAIK) to specialize a single method of
+ * a template class.
+ * That stuff should go here.
+ */
+namespace LLStringFn
+{
+ /**
+ * @brief Replace all non-printable characters with replacement in
+ * string.
+ * NOTE - this will zap non-ascii
+ *
+ * @param [in,out] string the to modify. out value is the string
+ * with zero non-printable characters.
+ * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
+ */
+ LL_COMMON_API void replace_nonprintable_in_ascii(
+ std::basic_string<char>& string,
+ char replacement);
+
+
+ /**
+ * @brief Replace all non-printable characters and pipe characters
+ * with replacement in a string.
+ * NOTE - this will zap non-ascii
+ *
+ * @param [in,out] the string to modify. out value is the string
+ * with zero non-printable characters and zero pipe characters.
+ * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
+ */
+ LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str,
+ char replacement);
+
+
+ /**
+ * @brief Remove all characters that are not allowed in XML 1.0.
+ * Returns a copy of the string with those characters removed.
+ * Works with US ASCII and UTF-8 encoded strings. JC
+ */
+ LL_COMMON_API std::string strip_invalid_xml(const std::string& input);
+
+
+ /**
+ * @brief Replace all control characters (0 <= c < 0x20) with replacement in
+ * string. This is safe for utf-8
+ *
+ * @param [in,out] string the to modify. out value is the string
+ * with zero non-printable characters.
+ * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
+ */
+ LL_COMMON_API void replace_ascii_controlchars(
+ std::basic_string<char>& string,
+ char replacement);
+}
+
+////////////////////////////////////////////////////////////
+// NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp.
+// There is no LLWStringUtil::format implementation currently.
+// Calling these for anything other than LLStringUtil will produce link errors.
+
+////////////////////////////////////////////////////////////
+
+// static
+template <class T>
+std::vector<typename LLStringUtilBase<T>::string_type>
+LLStringUtilBase<T>::getTokens(const string_type& instr, const string_type& delims)
+{
+ std::vector<string_type> tokens;
+ getTokens(instr, tokens, delims);
+ return tokens;
+}
+
+// static
+template <class T>
+std::vector<typename LLStringUtilBase<T>::string_type>
+LLStringUtilBase<T>::getTokens(const string_type& instr,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes)
+{
+ std::vector<string_type> tokens;
+ getTokens(instr, tokens, drop_delims, keep_delims, quotes);
+ return tokens;
+}
+
+// static
+template <class T>
+std::vector<typename LLStringUtilBase<T>::string_type>
+LLStringUtilBase<T>::getTokens(const string_type& instr,
+ const string_type& drop_delims,
+ const string_type& keep_delims,
+ const string_type& quotes,
+ const string_type& escapes)
+{
+ std::vector<string_type> tokens;
+ getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes);
+ return tokens;
+}
+
+namespace LLStringUtilBaseImpl
+{
+
+/**
+ * Input string scanner helper for getTokens(), or really any other
+ * character-parsing routine that may have to deal with escape characters.
+ * This implementation defines the concept (also an interface, should you
+ * choose to implement the concept by subclassing) and provides trivial
+ * implementations for a string @em without escape processing.
+ */
+template <class T>
+struct InString
+{
+ typedef std::basic_string<T> string_type;
+ typedef typename string_type::const_iterator const_iterator;
+
+ InString(const_iterator b, const_iterator e):
+ mIter(b),
+ mEnd(e)
+ {}
+ virtual ~InString() {}
+
+ bool done() const { return mIter == mEnd; }
+ /// Is the current character (*mIter) escaped? This implementation can
+ /// answer trivially because it doesn't support escapes.
+ virtual bool escaped() const { return false; }
+ /// Obtain the current character and advance @c mIter.
+ virtual T next() { return *mIter++; }
+ /// Does the current character match specified character?
+ virtual bool is(T ch) const { return (! done()) && *mIter == ch; }
+ /// Is the current character any one of the specified characters?
+ virtual bool oneof(const string_type& delims) const
+ {
+ return (! done()) && LLStringUtilBase<T>::contains(delims, *mIter);
+ }
+
+ /**
+ * Scan forward from @from until either @a delim or end. This is primarily
+ * useful for processing quoted substrings.
+ *
+ * If we do see @a delim, append everything from @from until (excluding)
+ * @a delim to @a into, advance @c mIter to skip @a delim, and return @c
+ * true.
+ *
+ * If we do not see @a delim, do not alter @a into or @c mIter and return
+ * @c false. Do not pass GO, do not collect $200.
+ *
+ * @note The @c false case described above implements normal getTokens()
+ * treatment of an unmatched open quote: treat the quote character as if
+ * escaped, that is, simply collect it as part of the current token. Other
+ * plausible behaviors directly affect the way getTokens() deals with an
+ * unmatched quote: e.g. throwing an exception to treat it as an error, or
+ * assuming a close quote beyond end of string (in which case return @c
+ * true).
+ */
+ virtual bool collect_until(string_type& into, const_iterator from, T delim)
+ {
+ const_iterator found = std::find(from, mEnd, delim);
+ // If we didn't find delim, change nothing, just tell caller.
+ if (found == mEnd)
+ return false;
+ // Found delim! Append everything between from and found.
+ into.append(from, found);
+ // advance past delim in input
+ mIter = found + 1;
+ return true;
+ }
+
+ const_iterator mIter, mEnd;
+};
+
+/// InString subclass that handles escape characters
+template <class T>
+class InEscString: public InString<T>
+{
+public:
+ typedef InString<T> super;
+ typedef typename super::string_type string_type;
+ typedef typename super::const_iterator const_iterator;
+ using super::done;
+ using super::mIter;
+ using super::mEnd;
+
+ InEscString(const_iterator b, const_iterator e, const string_type& escapes):
+ super(b, e),
+ mEscapes(escapes)
+ {
+ // Even though we've already initialized 'mIter' via our base-class
+ // constructor, set it again to check for initial escape char.
+ setiter(b);
+ }
+
+ /// This implementation uses the answer cached by setiter().
+ virtual bool escaped() const { return mIsEsc; }
+ virtual T next()
+ {
+ // If we're looking at the escape character of an escape sequence,
+ // skip that character. This is the one time we can modify 'mIter'
+ // without using setiter: for this one case we DO NOT CARE if the
+ // escaped character is itself an escape.
+ if (mIsEsc)
+ ++mIter;
+ // If we were looking at an escape character, this is the escaped
+ // character; otherwise it's just the next character.
+ T result(*mIter);
+ // Advance mIter, checking for escape sequence.
+ setiter(mIter + 1);
+ return result;
+ }
+
+ virtual bool is(T ch) const
+ {
+ // Like base-class is(), except that an escaped character matches
+ // nothing.
+ return (! done()) && (! mIsEsc) && *mIter == ch;
+ }
+
+ virtual bool oneof(const string_type& delims) const
+ {
+ // Like base-class oneof(), except that an escaped character matches
+ // nothing.
+ return (! done()) && (! mIsEsc) && LLStringUtilBase<T>::contains(delims, *mIter);
+ }
+
+ virtual bool collect_until(string_type& into, const_iterator from, T delim)
+ {
+ // Deal with escapes in the characters we collect; that is, an escaped
+ // character must become just that character without the preceding
+ // escape. Collect characters in a separate string rather than
+ // directly appending to 'into' in case we do not find delim, in which
+ // case we're supposed to leave 'into' unmodified.
+ string_type collected;
+ // For scanning purposes, we're going to work directly with 'mIter'.
+ // Save its current value in case we fail to see delim.
+ const_iterator save_iter(mIter);
+ // Okay, set 'mIter', checking for escape.
+ setiter(from);
+ while (! done())
+ {
+ // If we see an unescaped delim, stop and report success.
+ if ((! mIsEsc) && *mIter == delim)
+ {
+ // Append collected chars to 'into'.
+ into.append(collected);
+ // Don't forget to advance 'mIter' past delim.
+ setiter(mIter + 1);
+ return true;
+ }
+ // We're not at end, and either we're not looking at delim or it's
+ // escaped. Collect this character and keep going.
+ collected.push_back(next());
+ }
+ // Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell
+ // caller.
+ setiter(save_iter);
+ return false;
+ }
+
+private:
+ void setiter(const_iterator i)
+ {
+ mIter = i;
+
+ // Every time we change 'mIter', set 'mIsEsc' to be able to repetitively
+ // answer escaped() without having to rescan 'mEscapes'. mIsEsc caches
+ // contains(mEscapes, *mIter).
+
+ // We're looking at an escaped char if we're not already at end (that
+ // is, *mIter is even meaningful); if *mIter is in fact one of the
+ // specified escape characters; and if there's one more character
+ // following it. That is, if an escape character is the very last
+ // character of the input string, it loses its special meaning.
+ mIsEsc = (! done()) &&
+ LLStringUtilBase<T>::contains(mEscapes, *mIter) &&
+ (mIter+1) != mEnd;
+ }
+
+ const string_type mEscapes;
+ bool mIsEsc;
+};
+
+/// getTokens() implementation based on InString concept
+template <typename INSTRING, typename string_type>
+void getTokens(INSTRING& instr, std::vector<string_type>& tokens,
+ const string_type& drop_delims, const string_type& keep_delims,
+ const string_type& quotes)
+{
+ // There are times when we want to match either drop_delims or
+ // keep_delims. Concatenate them up front to speed things up.
+ string_type all_delims(drop_delims + keep_delims);
+ // no tokens yet
+ tokens.clear();
+
+ // try for another token
+ while (! instr.done())
+ {
+ // scan past any drop_delims
+ while (instr.oneof(drop_delims))
+ {
+ // skip this drop_delim
+ instr.next();
+ // but if that was the end of the string, done
+ if (instr.done())
+ return;
+ }
+ // found the start of another token: make a slot for it
+ tokens.push_back(string_type());
+ if (instr.oneof(keep_delims))
+ {
+ // *iter is a keep_delim, a token of exactly 1 character. Append
+ // that character to the new token and proceed.
+ tokens.back().push_back(instr.next());
+ continue;
+ }
+ // Here we have a non-delimiter token, which might consist of a mix of
+ // quoted and unquoted parts. Use bash rules for quoting: you can
+ // embed a quoted substring in the midst of an unquoted token (e.g.
+ // ~/"sub dir"/myfile.txt); you can ram two quoted substrings together
+ // to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge
+ // from bash in that bash considers an unmatched quote an error. Our
+ // param signature doesn't allow for errors, so just pretend it's not
+ // a quote and embed it.
+ // At this level, keep scanning until we hit the next delimiter of
+ // either type (drop_delims or keep_delims).
+ while (! instr.oneof(all_delims))
+ {
+ // If we're looking at an open quote, search forward for
+ // a close quote, collecting characters along the way.
+ if (instr.oneof(quotes) &&
+ instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter))
+ {
+ // collect_until is cleverly designed to do exactly what we
+ // need here. No further action needed if it returns true.
+ }
+ else
+ {
+ // Either *iter isn't a quote, or there's no matching close
+ // quote: in other words, just an ordinary char. Append it to
+ // current token.
+ tokens.back().push_back(instr.next());
+ }
+ // having scanned that segment of this token, if we've reached the
+ // end of the string, we're done
+ if (instr.done())
+ return;
+ }
+ }
+}
+
+} // namespace LLStringUtilBaseImpl
+
+// static
+template <class T>
+void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
+ const string_type& drop_delims, const string_type& keep_delims,
+ const string_type& quotes)
+{
+ // Because this overload doesn't support escapes, use simple InString to
+ // manage input range.
+ LLStringUtilBaseImpl::InString<T> instring(string.begin(), string.end());
+ LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes);
+}
+
+// static
+template <class T>
+void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
+ const string_type& drop_delims, const string_type& keep_delims,
+ const string_type& quotes, const string_type& escapes)
+{
+ // This overload must deal with escapes. Delegate that to InEscString
+ // (unless there ARE no escapes).
+ std::unique_ptr< LLStringUtilBaseImpl::InString<T> > instrp;
+ if (escapes.empty())
+ instrp.reset(new LLStringUtilBaseImpl::InString<T>(string.begin(), string.end()));
+ else
+ instrp.reset(new LLStringUtilBaseImpl::InEscString<T>(string.begin(), string.end(), escapes));
+ LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes);
+}
+
+// static
+template<class T>
+S32 LLStringUtilBase<T>::compareStrings(const T* lhs, const T* rhs)
+{
+ S32 result;
+ if( lhs == rhs )
+ {
+ result = 0;
+ }
+ else
+ if ( !lhs || !lhs[0] )
+ {
+ result = ((!rhs || !rhs[0]) ? 0 : 1);
+ }
+ else
+ if ( !rhs || !rhs[0])
+ {
+ result = -1;
+ }
+ else
+ {
+ result = LLStringOps::collate(lhs, rhs);
+ }
+ return result;
+}
+
+//static
+template<class T>
+S32 LLStringUtilBase<T>::compareStrings(const string_type& lhs, const string_type& rhs)
+{
+ return LLStringOps::collate(lhs.c_str(), rhs.c_str());
+}
+
+// static
+template<class T>
+S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs )
+{
+ S32 result;
+ if( lhs == rhs )
+ {
+ result = 0;
+ }
+ else
+ if ( !lhs || !lhs[0] )
+ {
+ result = ((!rhs || !rhs[0]) ? 0 : 1);
+ }
+ else
+ if ( !rhs || !rhs[0] )
+ {
+ result = -1;
+ }
+ else
+ {
+ string_type lhs_string(lhs);
+ string_type rhs_string(rhs);
+ LLStringUtilBase<T>::toUpper(lhs_string);
+ LLStringUtilBase<T>::toUpper(rhs_string);
+ result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
+ }
+ return result;
+}
+
+//static
+template<class T>
+S32 LLStringUtilBase<T>::compareInsensitive(const string_type& lhs, const string_type& rhs)
+{
+ string_type lhs_string(lhs);
+ string_type rhs_string(rhs);
+ LLStringUtilBase<T>::toUpper(lhs_string);
+ LLStringUtilBase<T>::toUpper(rhs_string);
+ return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
+}
+
+// Case sensitive comparison with good handling of numbers. Does not use current locale.
+// a.k.a. strdictcmp()
+
+//static
+template<class T>
+S32 LLStringUtilBase<T>::compareDict(const string_type& astr, const string_type& bstr)
+{
+ const T* a = astr.c_str();
+ const T* b = bstr.c_str();
+ T ca, cb;
+ S32 ai, bi, cnt = 0;
+ S32 bias = 0;
+
+ ca = *(a++);
+ cb = *(b++);
+ while( ca && cb ){
+ if( bias==0 ){
+ if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); bias--; }
+ if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); bias++; }
+ }else{
+ if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); }
+ if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); }
+ }
+ if( LLStringOps::isDigit(ca) ){
+ if( cnt-->0 ){
+ if( cb!=ca ) break;
+ }else{
+ if( !LLStringOps::isDigit(cb) ) break;
+ for(ai=0; LLStringOps::isDigit(a[ai]); ai++);
+ for(bi=0; LLStringOps::isDigit(b[bi]); bi++);
+ if( ai<bi ){ ca=0; break; }
+ if( bi<ai ){ cb=0; break; }
+ if( ca!=cb ) break;
+ cnt = ai;
+ }
+ }else if( ca!=cb ){ break;
+ }
+ ca = *(a++);
+ cb = *(b++);
+ }
+ if( ca==cb ) ca += bias;
+ return ca-cb;
+}
+
+// static
+template<class T>
+S32 LLStringUtilBase<T>::compareDictInsensitive(const string_type& astr, const string_type& bstr)
+{
+ const T* a = astr.c_str();
+ const T* b = bstr.c_str();
+ T ca, cb;
+ S32 ai, bi, cnt = 0;
+
+ ca = *(a++);
+ cb = *(b++);
+ while( ca && cb ){
+ if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); }
+ if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); }
+ if( LLStringOps::isDigit(ca) ){
+ if( cnt-->0 ){
+ if( cb!=ca ) break;
+ }else{
+ if( !LLStringOps::isDigit(cb) ) break;
+ for(ai=0; LLStringOps::isDigit(a[ai]); ai++);
+ for(bi=0; LLStringOps::isDigit(b[bi]); bi++);
+ if( ai<bi ){ ca=0; break; }
+ if( bi<ai ){ cb=0; break; }
+ if( ca!=cb ) break;
+ cnt = ai;
+ }
+ }else if( ca!=cb ){ break;
+ }
+ ca = *(a++);
+ cb = *(b++);
+ }
+ return ca-cb;
+}
+
+// Puts compareDict() in a form appropriate for LL container classes to use for sorting.
+// static
+template<class T>
+bool LLStringUtilBase<T>::precedesDict( const string_type& a, const string_type& b )
+{
+ if( a.size() && b.size() )
+ {
+ return (LLStringUtilBase<T>::compareDict(a.c_str(), b.c_str()) < 0);
+ }
+ else
+ {
+ return (!b.empty());
+ }
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::toUpper(string_type& string)
+{
+ if( !string.empty() )
+ {
+ std::transform(
+ string.begin(),
+ string.end(),
+ string.begin(),
+ (T(*)(T)) &LLStringOps::toUpper);
+ }
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::toLower(string_type& string)
+{
+ if( !string.empty() )
+ {
+ std::transform(
+ string.begin(),
+ string.end(),
+ string.begin(),
+ (T(*)(T)) &LLStringOps::toLower);
+ }
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::trimHead(string_type& string)
+{
+ if( !string.empty() )
+ {
+ size_type i = 0;
+ while( i < string.length() && LLStringOps::isSpace( string[i] ) )
+ {
+ i++;
+ }
+ string.erase(0, i);
+ }
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::trimTail(string_type& string)
+{
+ if( string.size() )
+ {
+ size_type len = string.length();
+ size_type i = len;
+ while( i > 0 && LLStringOps::isSpace( string[i-1] ) )
+ {
+ i--;
+ }
+
+ string.erase( i, len - i );
+ }
+}
+
+
+// Replace line feeds with carriage return-line feed pairs.
+//static
+template<class T>
+void LLStringUtilBase<T>::addCRLF(string_type& string)
+{
+ const T LF = 10;
+ const T CR = 13;
+
+ // Count the number of line feeds
+ size_type count = 0;
+ size_type len = string.size();
+ size_type i;
+ for( i = 0; i < len; i++ )
+ {
+ if( string[i] == LF )
+ {
+ count++;
+ }
+ }
+
+ // Insert a carriage return before each line feed
+ if( count )
+ {
+ size_type size = len + count;
+ T *t = new T[size];
+ size_type j = 0;
+ for( i = 0; i < len; ++i )
+ {
+ if( string[i] == LF )
+ {
+ t[j] = CR;
+ ++j;
+ }
+ t[j] = string[i];
+ ++j;
+ }
+
+ string.assign(t, size);
+ delete[] t;
+ }
+}
+
+// Remove all carriage returns
+//static
+template<class T>
+void LLStringUtilBase<T>::removeCRLF(string_type& string)
+{
+ const T CR = 13;
+
+ size_type cr_count = 0;
+ size_type len = string.size();
+ size_type i;
+ for( i = 0; i < len - cr_count; i++ )
+ {
+ if( string[i+cr_count] == CR )
+ {
+ cr_count++;
+ }
+
+ string[i] = string[i+cr_count];
+ }
+ string.erase(i, cr_count);
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::removeWindowsCR(string_type& string)
+{
+ if (string.empty())
+ {
+ return;
+ }
+ const T LF = 10;
+ const T CR = 13;
+
+ size_type cr_count = 0;
+ size_type len = string.size();
+ size_type i;
+ for( i = 0; i < len - cr_count - 1; i++ )
+ {
+ if( string[i+cr_count] == CR && string[i+cr_count+1] == LF)
+ {
+ cr_count++;
+ }
+
+ string[i] = string[i+cr_count];
+ }
+ string.erase(i, cr_count);
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement )
+{
+ size_type found_pos = 0;
+ while( (found_pos = string.find(target, found_pos)) != string_type::npos )
+ {
+ string[found_pos] = replacement;
+ found_pos++; // avoid infinite defeat if target == replacement
+ }
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::replaceString( string_type& string, string_type target, string_type replacement )
+{
+ size_type found_pos = 0;
+ while( (found_pos = string.find(target, found_pos)) != string_type::npos )
+ {
+ string.replace( found_pos, target.length(), replacement );
+ found_pos += replacement.length(); // avoid infinite defeat if replacement contains target
+ }
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::replaceNonstandardASCII( string_type& string, T replacement )
+{
+ const char LF = 10;
+ const S8 MIN = 32;
+// const S8 MAX = 127;
+
+ size_type len = string.size();
+ for( size_type i = 0; i < len; i++ )
+ {
+ // No need to test MAX < mText[i] because we treat mText[i] as a signed char,
+ // which has a max value of 127.
+ if( ( S8(string[i]) < MIN ) && (string[i] != LF) )
+ {
+ string[i] = replacement;
+ }
+ }
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab )
+{
+ const T TAB = '\t';
+ const T SPACE = ' ';
+
+ string_type out_str;
+ // Replace tabs with spaces
+ for (size_type i = 0; i < str.length(); i++)
+ {
+ if (str[i] == TAB)
+ {
+ for (size_type j = 0; j < spaces_per_tab; j++)
+ out_str += SPACE;
+ }
+ else
+ {
+ out_str += str[i];
+ }
+ }
+ str = out_str;
+}
+
+//static
+template<class T>
+std::basic_string<T> LLStringUtilBase<T>::capitalize(const string_type& str)
+{
+ string_type result(str);
+ capitalize(result);
+ return result;
+}
+
+//static
+template<class T>
+void LLStringUtilBase<T>::capitalize(string_type& str)
+{
+ if (str.size())
+ {
+ auto last = str[0] = toupper(str[0]);
+ for (U32 i = 1; i < str.size(); ++i)
+ {
+ last = (last == ' ' || last == '-' || last == '_') ? str[i] = toupper(str[i]) : str[i];
+ }
+ }
+}
+
+//static
+template<class T>
+bool LLStringUtilBase<T>::containsNonprintable(const string_type& string)
+{
+ const char MIN = 32;
+ bool rv = false;
+ for (size_type i = 0; i < string.size(); i++)
+ {
+ if(string[i] < MIN)
+ {
+ rv = true;
+ break;
+ }
+ }
+ return rv;
+}
+
+// *TODO: reimplement in terms of algorithm
+//static
+template<class T>
+void LLStringUtilBase<T>::stripNonprintable(string_type& string)
+{
+ const char MIN = 32;
+ size_type j = 0;
+ if (string.empty())
+ {
+ return;
+ }
+ size_t src_size = string.size();
+ char* c_string = new char[src_size + 1];
+ if(c_string == NULL)
+ {
+ return;
+ }
+ copy(c_string, string.c_str(), src_size+1);
+ char* write_head = &c_string[0];
+ for (size_type i = 0; i < src_size; i++)
+ {
+ char* read_head = &string[i];
+ write_head = &c_string[j];
+ if(!(*read_head < MIN))
+ {
+ *write_head = *read_head;
+ ++j;
+ }
+ }
+ c_string[j]= '\0';
+ string = c_string;
+ delete []c_string;
+}
+
+// *TODO: reimplement in terms of algorithm
+template<class T>
+std::basic_string<T> LLStringUtilBase<T>::quote(const string_type& str,
+ const string_type& triggers,
+ const string_type& escape)
+{
+ size_type len(str.length());
+ // If the string is already quoted, assume user knows what s/he's doing.
+ if (len >= 2 && str[0] == '"' && str[len-1] == '"')
+ {
+ return str;
+ }
+
+ // Not already quoted: do we need to? triggers.empty() is a special case
+ // meaning "always quote."
+ if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos)
+ {
+ // no trigger characters, don't bother quoting
+ return str;
+ }
+
+ // For whatever reason, we must quote this string.
+ string_type result;
+ result.push_back('"');
+ for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
+ {
+ if (*ci == '"')
+ {
+ result.append(escape);
+ }
+ result.push_back(*ci);
+ }
+ result.push_back('"');
+ return result;
+}
+
+template<class T>
+void LLStringUtilBase<T>::_makeASCII(string_type& string)
+{
+ // Replace non-ASCII chars with LL_UNKNOWN_CHAR
+ for (size_type i = 0; i < string.length(); i++)
+ {
+ if (string[i] > 0x7f)
+ {
+ string[i] = LL_UNKNOWN_CHAR;
+ }
+ }
+}
+
+// static
+template<class T>
+void LLStringUtilBase<T>::copy( T* dst, const T* src, size_type dst_size )
+{
+ if( dst_size > 0 )
+ {
+ size_type min_len = 0;
+ if( src )
+ {
+ min_len = llmin( dst_size - 1, strlen( src ) ); /* Flawfinder: ignore */
+ memcpy(dst, src, min_len * sizeof(T)); /* Flawfinder: ignore */
+ }
+ dst[min_len] = '\0';
+ }
+}
+
+// static
+template<class T>
+void LLStringUtilBase<T>::copyInto(string_type& dst, const string_type& src, size_type offset)
+{
+ if ( offset == dst.length() )
+ {
+ // special case - append to end of string and avoid expensive
+ // (when strings are large) string manipulations
+ dst += src;
+ }
+ else
+ {
+ string_type tail = dst.substr(offset);
+
+ dst = dst.substr(0, offset);
+ dst += src;
+ dst += tail;
+ };
+}
+
+// True if this is the head of s.
+//static
+template<class T>
+bool LLStringUtilBase<T>::isHead( const string_type& string, const T* s )
+{
+ if( string.empty() )
+ {
+ // Early exit
+ return false;
+ }
+ else
+ {
+ return (strncmp( s, string.c_str(), string.size() ) == 0);
+ }
+}
+
+// static
+template<class T>
+bool LLStringUtilBase<T>::startsWith(
+ const string_type& string,
+ const string_type& substr)
+{
+ if(string.empty() || (substr.empty())) return false;
+ if (substr.length() > string.length()) return false;
+ if (0 == string.compare(0, substr.length(), substr)) return true;
+ return false;
+}
+
+// static
+template<class T>
+bool LLStringUtilBase<T>::endsWith(
+ const string_type& string,
+ const string_type& substr)
+{
+ if(string.empty() || (substr.empty())) return false;
+ size_t sub_len = substr.length();
+ size_t str_len = string.length();
+ if (sub_len > str_len) return false;
+ if (0 == string.compare(str_len - sub_len, sub_len, substr)) return true;
+ return false;
+}
+
+// static
+template<class T>
+auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> std::optional<string_type>
+{
+ auto found(llstring_getoptenv(key));
+ if (found)
+ {
+ // return populated std::optional
+ return { ll_convert<string_type>(*found) };
+ }
+ else
+ {
+ // empty std::optional
+ return {};
+ }
+}
+
+// static
+template<class T>
+auto LLStringUtilBase<T>::getenv(const std::string& key, const string_type& dflt) -> string_type
+{
+ auto found(getoptenv(key));
+ if (found)
+ {
+ return *found;
+ }
+ else
+ {
+ return dflt;
+ }
+}
+
+template<class T>
+bool LLStringUtilBase<T>::convertToBOOL(const string_type& string, bool& value)
+{
+ if( string.empty() )
+ {
+ return false;
+ }
+
+ string_type temp( string );
+ trim(temp);
+ if(
+ (temp == "1") ||
+ (temp == "T") ||
+ (temp == "t") ||
+ (temp == "TRUE") ||
+ (temp == "true") ||
+ (temp == "True") )
+ {
+ value = true;
+ return true;
+ }
+ else
+ if(
+ (temp == "0") ||
+ (temp == "F") ||
+ (temp == "f") ||
+ (temp == "FALSE") ||
+ (temp == "false") ||
+ (temp == "False") )
+ {
+ value = false;
+ return true;
+ }
+
+ return false;
+}
+
+template<class T>
+bool LLStringUtilBase<T>::convertToU8(const string_type& string, U8& value)
+{
+ S32 value32 = 0;
+ bool success = convertToS32(string, value32);
+ if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) )
+ {
+ value = (U8) value32;
+ return true;
+ }
+ return false;
+}
+
+template<class T>
+bool LLStringUtilBase<T>::convertToS8(const string_type& string, S8& value)
+{
+ S32 value32 = 0;
+ bool success = convertToS32(string, value32);
+ if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) )
+ {
+ value = (S8) value32;
+ return true;
+ }
+ return false;
+}
+
+template<class T>
+bool LLStringUtilBase<T>::convertToS16(const string_type& string, S16& value)
+{
+ S32 value32 = 0;
+ bool success = convertToS32(string, value32);
+ if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) )
+ {
+ value = (S16) value32;
+ return true;
+ }
+ return false;
+}
+
+template<class T>
+bool LLStringUtilBase<T>::convertToU16(const string_type& string, U16& value)
+{
+ S32 value32 = 0;
+ bool success = convertToS32(string, value32);
+ if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) )
+ {
+ value = (U16) value32;
+ return true;
+ }
+ return false;
+}
+
+template<class T>
+bool LLStringUtilBase<T>::convertToU32(const string_type& string, U32& value)
+{
+ if( string.empty() )
+ {
+ return false;
+ }
+
+ string_type temp( string );
+ trim(temp);
+ U32 v;
+ std::basic_istringstream<T> i_stream((string_type)temp);
+ if(i_stream >> v)
+ {
+ value = v;
+ return true;
+ }
+ return false;
+}
+
+template<class T>
+bool LLStringUtilBase<T>::convertToS32(const string_type& string, S32& value)
+{
+ if( string.empty() )
+ {
+ return false;
+ }
+
+ string_type temp( string );
+ trim(temp);
+ S32 v;
+ std::basic_istringstream<T> i_stream((string_type)temp);
+ if(i_stream >> v)
+ {
+ //TODO: figure out overflow and underflow reporting here
+ //if((LONG_MAX == v) || (LONG_MIN == v))
+ //{
+ // // Underflow or overflow
+ // return false;
+ //}
+
+ value = v;
+ return true;
+ }
+ return false;
+}
+
+template<class T>
+bool LLStringUtilBase<T>::convertToF32(const string_type& string, F32& value)
+{
+ F64 value64 = 0.0;
+ bool success = convertToF64(string, value64);
+ if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) )
+ {
+ value = (F32) value64;
+ return true;
+ }
+ return false;
+}
+
+template<class T>
+bool LLStringUtilBase<T>::convertToF64(const string_type& string, F64& value)
+{
+ if( string.empty() )
+ {
+ return false;
+ }
+
+ string_type temp( string );
+ trim(temp);
+ F64 v;
+ std::basic_istringstream<T> i_stream((string_type)temp);
+ if(i_stream >> v)
+ {
+ //TODO: figure out overflow and underflow reporting here
+ //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) )
+ //{
+ // // Underflow or overflow
+ // return false;
+ //}
+
+ value = v;
+ return true;
+ }
+ return false;
+}
+
+template<class T>
+void LLStringUtilBase<T>::truncate(string_type& string, size_type count)
+{
+ size_type cur_size = string.size();
+ string.resize(count < cur_size ? count : cur_size);
+}
+
+// The good thing about *declaration* macros, vs. usage macros, is that now
+// we're done with them: we don't need them to bleed into the consuming source
+// file.
+#undef ll_convert_alias
+#undef ll_convert_u16_alias
+#undef ll_convert_wstr_alias
+#undef LL_CONVERT_COPY_CHARS
+#undef ll_convert_forms
+#undef ll_convert_cp_forms
+
+#endif // LL_STRING_H
diff --git a/indra/llcommon/llstringtable.h b/indra/llcommon/llstringtable.h index e41701ce9c..a9b38fb14b 100644 --- a/indra/llcommon/llstringtable.h +++ b/indra/llcommon/llstringtable.h @@ -1,209 +1,209 @@ -/** - * @file llstringtable.h - * @brief The LLStringTable class provides a _fast_ method for finding - * unique copies of strings. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_STRING_TABLE_H -#define LL_STRING_TABLE_H - -#include "lldefs.h" -#include "llformat.h" -#include "llstl.h" -#include <list> -#include <set> - -#if LL_WINDOWS -# if (_MSC_VER >= 1300 && _MSC_VER < 1400) -# define STRING_TABLE_HASH_MAP 1 -# endif -#else -//# define STRING_TABLE_HASH_MAP 1 -#endif - -const U32 MAX_STRINGS_LENGTH = 256; - -class LL_COMMON_API LLStringTableEntry -{ -public: - LLStringTableEntry(const char *str); - ~LLStringTableEntry(); - - void incCount() { mCount++; } - BOOL decCount() { return --mCount; } - - char *mString; - S32 mCount; -}; - -class LL_COMMON_API LLStringTable -{ -public: - LLStringTable(int tablesize); - ~LLStringTable(); - - char *checkString(const char *str); - char *checkString(const std::string& str); - LLStringTableEntry *checkStringEntry(const char *str); - LLStringTableEntry *checkStringEntry(const std::string& str); - - char *addString(const char *str); - char *addString(const std::string& str); - LLStringTableEntry *addStringEntry(const char *str); - LLStringTableEntry *addStringEntry(const std::string& str); - void removeString(const char *str); - - S32 mMaxEntries; - S32 mUniqueEntries; - -#if STRING_TABLE_HASH_MAP -#if LL_WINDOWS - typedef std::hash_multimap<U32, LLStringTableEntry *> string_hash_t; -#else - typedef __gnu_cxx::hash_multimap<U32, LLStringTableEntry *> string_hash_t; -#endif - string_hash_t mStringHash; -#else - typedef std::list<LLStringTableEntry *> string_list_t; - typedef string_list_t * string_list_ptr_t; - string_list_ptr_t *mStringList; -#endif -}; - -extern LL_COMMON_API LLStringTable gStringTable; - -//============================================================================ - -// This class is designed to be used locally, -// e.g. as a member of an LLXmlTree -// Strings can be inserted only, then quickly looked up - -typedef const std::string* LLStdStringHandle; - -class LL_COMMON_API LLStdStringTable -{ -public: - LLStdStringTable(S32 tablesize = 0) - { - if (tablesize == 0) - { - tablesize = 256; // default - } - // Make sure tablesize is power of 2 - for (S32 i = 31; i>0; i--) - { - if (tablesize & (1<<i)) - { - if (tablesize >= (3<<(i-1))) - tablesize = (1<<(i+1)); - else - tablesize = (1<<i); - break; - } - } - mTableSize = tablesize; - mStringList = new string_set_t[tablesize]; - } - ~LLStdStringTable() - { - cleanup(); - delete[] mStringList; - } - void cleanup() - { - // remove strings - for (S32 i = 0; i<mTableSize; i++) - { - string_set_t& stringset = mStringList[i]; - for (LLStdStringHandle str : stringset) - { - delete str; - } - stringset.clear(); - } - } - - LLStdStringHandle lookup(const std::string& s) - { - U32 hashval = makehash(s); - return lookup(hashval, s); - } - - LLStdStringHandle checkString(const std::string& s) - { - U32 hashval = makehash(s); - return lookup(hashval, s); - } - - LLStdStringHandle insert(const std::string& s) - { - U32 hashval = makehash(s); - LLStdStringHandle result = lookup(hashval, s); - if (result == NULL) - { - result = new std::string(s); - mStringList[hashval].insert(result); - } - return result; - } - LLStdStringHandle addString(const std::string& s) - { - return insert(s); - } - -private: - U32 makehash(const std::string& s) - { - S32 len = (S32)s.size(); - const char* c = s.c_str(); - U32 hashval = 0; - for (S32 i=0; i<len; i++) - { - hashval = ((hashval<<5) + hashval) + *c++; - } - return hashval & (mTableSize-1); - } - LLStdStringHandle lookup(U32 hashval, const std::string& s) - { - string_set_t& stringset = mStringList[hashval]; - LLStdStringHandle handle = &s; - string_set_t::iterator iter = stringset.find(handle); // compares actual strings - if (iter != stringset.end()) - { - return *iter; - } - else - { - return NULL; - } - } - -private: - S32 mTableSize; - typedef std::set<LLStdStringHandle, compare_pointer_contents<std::string> > string_set_t; - string_set_t* mStringList; // [mTableSize] -}; - - -#endif +/**
+ * @file llstringtable.h
+ * @brief The LLStringTable class provides a _fast_ method for finding
+ * unique copies of strings.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_STRING_TABLE_H
+#define LL_STRING_TABLE_H
+
+#include "lldefs.h"
+#include "llformat.h"
+#include "llstl.h"
+#include <list>
+#include <set>
+
+#if LL_WINDOWS
+# if (_MSC_VER >= 1300 && _MSC_VER < 1400)
+# define STRING_TABLE_HASH_MAP 1
+# endif
+#else
+//# define STRING_TABLE_HASH_MAP 1
+#endif
+
+const U32 MAX_STRINGS_LENGTH = 256;
+
+class LL_COMMON_API LLStringTableEntry
+{
+public:
+ LLStringTableEntry(const char *str);
+ ~LLStringTableEntry();
+
+ void incCount() { mCount++; }
+ bool decCount() { return --mCount != 0; }
+
+ char *mString;
+ S32 mCount;
+};
+
+class LL_COMMON_API LLStringTable
+{
+public:
+ LLStringTable(int tablesize);
+ ~LLStringTable();
+
+ char *checkString(const char *str);
+ char *checkString(const std::string& str);
+ LLStringTableEntry *checkStringEntry(const char *str);
+ LLStringTableEntry *checkStringEntry(const std::string& str);
+
+ char *addString(const char *str);
+ char *addString(const std::string& str);
+ LLStringTableEntry *addStringEntry(const char *str);
+ LLStringTableEntry *addStringEntry(const std::string& str);
+ void removeString(const char *str);
+
+ S32 mMaxEntries;
+ S32 mUniqueEntries;
+
+#if STRING_TABLE_HASH_MAP
+#if LL_WINDOWS
+ typedef std::hash_multimap<U32, LLStringTableEntry *> string_hash_t;
+#else
+ typedef __gnu_cxx::hash_multimap<U32, LLStringTableEntry *> string_hash_t;
+#endif
+ string_hash_t mStringHash;
+#else
+ typedef std::list<LLStringTableEntry *> string_list_t;
+ typedef string_list_t * string_list_ptr_t;
+ string_list_ptr_t *mStringList;
+#endif
+};
+
+extern LL_COMMON_API LLStringTable gStringTable;
+
+//============================================================================
+
+// This class is designed to be used locally,
+// e.g. as a member of an LLXmlTree
+// Strings can be inserted only, then quickly looked up
+
+typedef const std::string* LLStdStringHandle;
+
+class LL_COMMON_API LLStdStringTable
+{
+public:
+ LLStdStringTable(S32 tablesize = 0)
+ {
+ if (tablesize == 0)
+ {
+ tablesize = 256; // default
+ }
+ // Make sure tablesize is power of 2
+ for (S32 i = 31; i>0; i--)
+ {
+ if (tablesize & (1<<i))
+ {
+ if (tablesize >= (3<<(i-1)))
+ tablesize = (1<<(i+1));
+ else
+ tablesize = (1<<i);
+ break;
+ }
+ }
+ mTableSize = tablesize;
+ mStringList = new string_set_t[tablesize];
+ }
+ ~LLStdStringTable()
+ {
+ cleanup();
+ delete[] mStringList;
+ }
+ void cleanup()
+ {
+ // remove strings
+ for (S32 i = 0; i<mTableSize; i++)
+ {
+ string_set_t& stringset = mStringList[i];
+ for (LLStdStringHandle str : stringset)
+ {
+ delete str;
+ }
+ stringset.clear();
+ }
+ }
+
+ LLStdStringHandle lookup(const std::string& s)
+ {
+ U32 hashval = makehash(s);
+ return lookup(hashval, s);
+ }
+
+ LLStdStringHandle checkString(const std::string& s)
+ {
+ U32 hashval = makehash(s);
+ return lookup(hashval, s);
+ }
+
+ LLStdStringHandle insert(const std::string& s)
+ {
+ U32 hashval = makehash(s);
+ LLStdStringHandle result = lookup(hashval, s);
+ if (result == NULL)
+ {
+ result = new std::string(s);
+ mStringList[hashval].insert(result);
+ }
+ return result;
+ }
+ LLStdStringHandle addString(const std::string& s)
+ {
+ return insert(s);
+ }
+
+private:
+ U32 makehash(const std::string& s)
+ {
+ S32 len = (S32)s.size();
+ const char* c = s.c_str();
+ U32 hashval = 0;
+ for (S32 i=0; i<len; i++)
+ {
+ hashval = ((hashval<<5) + hashval) + *c++;
+ }
+ return hashval & (mTableSize-1);
+ }
+ LLStdStringHandle lookup(U32 hashval, const std::string& s)
+ {
+ string_set_t& stringset = mStringList[hashval];
+ LLStdStringHandle handle = &s;
+ string_set_t::iterator iter = stringset.find(handle); // compares actual strings
+ if (iter != stringset.end())
+ {
+ return *iter;
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+
+private:
+ S32 mTableSize;
+ typedef std::set<LLStdStringHandle, compare_pointer_contents<std::string> > string_set_t;
+ string_set_t* mStringList; // [mTableSize]
+};
+
+
+#endif
diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index b7ffddc023..02fd0733e5 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -1,1414 +1,1414 @@ -/** - * @file llsys.cpp - * @brief Implementation of the basic system query functions. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_WINDOWS -#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally -#endif - -#include "linden_common.h" - -#include "llsys.h" - -#include <iostream> -#ifdef LL_USESYSTEMLIBS -# include <zlib.h> -#else -# include "zlib-ng/zlib.h" -#endif - -#include "llprocessor.h" -#include "llerrorcontrol.h" -#include "llevents.h" -#include "llformat.h" -#include "llregex.h" -#include "lltimer.h" -#include "llsdserialize.h" -#include "llsdutil.h" -#include <boost/bind.hpp> -#include <boost/circular_buffer.hpp> -#include <boost/lexical_cast.hpp> -#include <boost/range.hpp> -#include <boost/utility/enable_if.hpp> -#include <boost/type_traits/is_integral.hpp> -#include <boost/type_traits/is_float.hpp> -#include "llfasttimer.h" - -using namespace llsd; - -#if LL_WINDOWS -# include "llwin32headerslean.h" -# include <psapi.h> // GetPerformanceInfo() et al. -# include <VersionHelpers.h> -#elif LL_DARWIN -# include "llsys_objc.h" -# include <errno.h> -# include <sys/sysctl.h> -# include <sys/utsname.h> -# include <stdint.h> -# include <CoreServices/CoreServices.h> -# include <stdexcept> -# include <mach/host_info.h> -# include <mach/mach_host.h> -# include <mach/task.h> -# include <mach/task_info.h> -#elif LL_LINUX -# include <errno.h> -# include <sys/utsname.h> -# include <unistd.h> -# include <sys/sysinfo.h> -# include <stdexcept> -const char MEMINFO_FILE[] = "/proc/meminfo"; -# include <gnu/libc-version.h> -#endif - -LLCPUInfo gSysCPU; - -// Don't log memory info any more often than this. It also serves as our -// framerate sample size. -static const F32 MEM_INFO_THROTTLE = 20; -// Sliding window of samples. We intentionally limit the length of time we -// remember "the slowest" framerate because framerate is very slow at login. -// If we only triggered FrameWatcher logging when the session framerate -// dropped below the login framerate, we'd have very little additional data. -static const F32 MEM_INFO_WINDOW = 10*60; - -LLOSInfo::LLOSInfo() : - mMajorVer(0), mMinorVer(0), mBuild(0), mOSVersionString("") -{ - -#if LL_WINDOWS - - if (IsWindows10OrGreater()) - { - mMajorVer = 10; - mMinorVer = 0; - mOSStringSimple = "Microsoft Windows 10 "; - } - else if (IsWindows8Point1OrGreater()) - { - mMajorVer = 6; - mMinorVer = 3; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2012 R2 "; - } - else - { - mOSStringSimple = "Microsoft Windows 8.1 "; - } - } - else if (IsWindows8OrGreater()) - { - mMajorVer = 6; - mMinorVer = 2; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2012 "; - } - else - { - mOSStringSimple = "Microsoft Windows 8 "; - } - } - else if (IsWindows7SP1OrGreater()) - { - mMajorVer = 6; - mMinorVer = 1; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2008 R2 SP1 "; - } - else - { - mOSStringSimple = "Microsoft Windows 7 SP1 "; - } - } - else if (IsWindows7OrGreater()) - { - mMajorVer = 6; - mMinorVer = 1; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2008 R2 "; - } - else - { - mOSStringSimple = "Microsoft Windows 7 "; - } - } - else if (IsWindowsVistaSP2OrGreater()) - { - mMajorVer = 6; - mMinorVer = 0; - if (IsWindowsServer()) - { - mOSStringSimple = "Windows Server 2008 SP2 "; - } - else - { - mOSStringSimple = "Microsoft Windows Vista SP2 "; - } - } - else - { - mOSStringSimple = "Unsupported Windows version "; - } - - ///get native system info if available.. - typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); ///function pointer for loading GetNativeSystemInfo - SYSTEM_INFO si; //System Info object file contains architecture info - PGNSI pGNSI; //pointer object - ZeroMemory(&si, sizeof(SYSTEM_INFO)); //zero out the memory in information - pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); //load kernel32 get function - if (NULL != pGNSI) //check if it has failed - pGNSI(&si); //success - else - GetSystemInfo(&si); //if it fails get regular system info - //(Warning: If GetSystemInfo it may result in incorrect information in a WOW64 machine, if the kernel fails to load) - - // Try calling GetVersionEx using the OSVERSIONINFOEX structure. - OSVERSIONINFOEX osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); - if (GetVersionEx((OSVERSIONINFO *)&osvi)) - { - mBuild = osvi.dwBuildNumber & 0xffff; - } - else - { - // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - if (GetVersionEx((OSVERSIONINFO *)&osvi)) - { - mBuild = osvi.dwBuildNumber & 0xffff; - } - } - - S32 ubr = 0; // Windows 10 Update Build Revision, can be retrieved from a registry - if (mMajorVer == 10) - { - DWORD cbData(sizeof(DWORD)); - DWORD data(0); - HKEY key; - BOOL ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key); - if (ERROR_SUCCESS == ret_code) - { - ret_code = RegQueryValueExW(key, L"UBR", 0, NULL, reinterpret_cast<LPBYTE>(&data), &cbData); - if (ERROR_SUCCESS == ret_code) - { - ubr = data; - } - } - - if (mBuild >= 22000) - { - // At release Windows 11 version was 10.0.22000.194 - // Windows 10 version was 10.0.19043.1266 - // There is no warranty that Win10 build won't increase, - // so until better solution is found or Microsoft updates - // SDK with IsWindows11OrGreater(), indicate "10/11" - // - // Current alternatives: - // Query WMI's Win32_OperatingSystem for OS string. Slow - // and likely to return 'compatibility' string. - // Check presence of dlls/libs or may be their version. - mOSStringSimple = "Microsoft Windows 10/11 "; - } - } - - //msdn microsoft finds 32 bit and 64 bit flavors this way.. - //http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx (example code that contains quite a few more flavors - //of windows than this code does (in case it is needed for the future) - if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) //check for 64 bit - { - mOSStringSimple += "64-bit "; - } - else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) - { - mOSStringSimple += "32-bit "; - } - - mOSString = mOSStringSimple; - if (mBuild > 0) - { - mOSString += llformat("(Build %d", mBuild); - if (ubr > 0) - { - mOSString += llformat(".%d", ubr); - } - mOSString += ")"; - } - - LLStringUtil::trim(mOSStringSimple); - LLStringUtil::trim(mOSString); - -#elif LL_DARWIN - - // Initialize mOSStringSimple to something like: - // "Mac OS X 10.6.7" - { - const char * DARWIN_PRODUCT_NAME = "Mac OS X"; - - int64_t major_version, minor_version, bugfix_version = 0; - - if (LLGetDarwinOSInfo(major_version, minor_version, bugfix_version)) - { - mMajorVer = major_version; - mMinorVer = minor_version; - mBuild = bugfix_version; - - std::stringstream os_version_string; - os_version_string << DARWIN_PRODUCT_NAME << " " << mMajorVer << "." << mMinorVer << "." << mBuild; - - // Put it in the OS string we are compiling - mOSStringSimple.append(os_version_string.str()); - } - else - { - mOSStringSimple.append("Unable to collect OS info"); - } - } - - // Initialize mOSString to something like: - // "Mac OS X 10.6.7 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386" - struct utsname un; - if(uname(&un) != -1) - { - mOSString = mOSStringSimple; - mOSString.append(" "); - mOSString.append(un.sysname); - mOSString.append(" "); - mOSString.append(un.release); - mOSString.append(" "); - mOSString.append(un.version); - mOSString.append(" "); - mOSString.append(un.machine); - } - else - { - mOSString = mOSStringSimple; - } - -#elif LL_LINUX - - struct utsname un; - if(uname(&un) != -1) - { - mOSStringSimple.append(un.sysname); - mOSStringSimple.append(" "); - mOSStringSimple.append(un.release); - - mOSString = mOSStringSimple; - mOSString.append(" "); - mOSString.append(un.version); - mOSString.append(" "); - mOSString.append(un.machine); - - // Simplify 'Simple' - std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); - if (ostype == "Linux") - { - // Only care about major and minor Linux versions, truncate at second '.' - std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); - std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; - std::string simple = mOSStringSimple.substr(0, idx2); - if (simple.length() > 0) - mOSStringSimple = simple; - } - } - else - { - mOSStringSimple.append("Unable to collect OS info"); - mOSString = mOSStringSimple; - } - - const char OS_VERSION_MATCH_EXPRESSION[] = "([0-9]+)\\.([0-9]+)(\\.([0-9]+))?"; - boost::regex os_version_parse(OS_VERSION_MATCH_EXPRESSION); - boost::smatch matched; - - std::string glibc_version(gnu_get_libc_version()); - if ( ll_regex_match(glibc_version, matched, os_version_parse) ) - { - LL_INFOS("AppInit") << "Using glibc version '" << glibc_version << "' as OS version" << LL_ENDL; - - std::string version_value; - - if ( matched[1].matched ) // Major version - { - version_value.assign(matched[1].first, matched[1].second); - if (sscanf(version_value.c_str(), "%d", &mMajorVer) != 1) - { - LL_WARNS("AppInit") << "failed to parse major version '" << version_value << "' as a number" << LL_ENDL; - } - } - else - { - LL_ERRS("AppInit") - << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION - << "' returned true, but major version [1] did not match" - << LL_ENDL; - } - - if ( matched[2].matched ) // Minor version - { - version_value.assign(matched[2].first, matched[2].second); - if (sscanf(version_value.c_str(), "%d", &mMinorVer) != 1) - { - LL_ERRS("AppInit") << "failed to parse minor version '" << version_value << "' as a number" << LL_ENDL; - } - } - else - { - LL_ERRS("AppInit") - << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION - << "' returned true, but minor version [1] did not match" - << LL_ENDL; - } - - if ( matched[4].matched ) // Build version (optional) - note that [3] includes the '.' - { - version_value.assign(matched[4].first, matched[4].second); - if (sscanf(version_value.c_str(), "%d", &mBuild) != 1) - { - LL_ERRS("AppInit") << "failed to parse build version '" << version_value << "' as a number" << LL_ENDL; - } - } - else - { - LL_INFOS("AppInit") - << "OS build version not provided; using zero" - << LL_ENDL; - } - } - else - { - LL_WARNS("AppInit") << "glibc version '" << glibc_version << "' cannot be parsed to three numbers; using all zeros" << LL_ENDL; - } - -#else - - struct utsname un; - if(uname(&un) != -1) - { - mOSStringSimple.append(un.sysname); - mOSStringSimple.append(" "); - mOSStringSimple.append(un.release); - - mOSString = mOSStringSimple; - mOSString.append(" "); - mOSString.append(un.version); - mOSString.append(" "); - mOSString.append(un.machine); - - // Simplify 'Simple' - std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); - if (ostype == "Linux") - { - // Only care about major and minor Linux versions, truncate at second '.' - std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); - std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; - std::string simple = mOSStringSimple.substr(0, idx2); - if (simple.length() > 0) - mOSStringSimple = simple; - } - } - else - { - mOSStringSimple.append("Unable to collect OS info"); - mOSString = mOSStringSimple; - } - -#endif - - std::stringstream dotted_version_string; - dotted_version_string << mMajorVer << "." << mMinorVer << "." << mBuild; - mOSVersionString.append(dotted_version_string.str()); - - mOSBitness = is64Bit() ? 64 : 32; - LL_INFOS("LLOSInfo") << "OS bitness: " << mOSBitness << LL_ENDL; -} - -#ifndef LL_WINDOWS -// static -long LLOSInfo::getMaxOpenFiles() -{ - const long OPEN_MAX_GUESS = 256; - -#ifdef OPEN_MAX - static long open_max = OPEN_MAX; -#else - static long open_max = 0; -#endif - - if (0 == open_max) - { - // First time through. - errno = 0; - if ( (open_max = sysconf(_SC_OPEN_MAX)) < 0) - { - if (0 == errno) - { - // Indeterminate. - open_max = OPEN_MAX_GUESS; - } - else - { - LL_ERRS() << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << LL_ENDL; - } - } - } - return open_max; -} -#endif - -void LLOSInfo::stream(std::ostream& s) const -{ - s << mOSString; -} - -const std::string& LLOSInfo::getOSString() const -{ - return mOSString; -} - -const std::string& LLOSInfo::getOSStringSimple() const -{ - return mOSStringSimple; -} - -const std::string& LLOSInfo::getOSVersionString() const -{ - return mOSVersionString; -} - -const S32 LLOSInfo::getOSBitness() const -{ - return mOSBitness; -} - -//static -U32 LLOSInfo::getProcessVirtualSizeKB() -{ - U32 virtual_size = 0; -#if LL_LINUX -# define STATUS_SIZE 2048 - LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); - if (status_filep) - { - S32 numRead = 0; - char buff[STATUS_SIZE]; /* Flawfinder: ignore */ - - size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); - buff[nbytes] = '\0'; - - // All these guys return numbers in KB - char *memp = strstr(buff, "VmSize:"); - if (memp) - { - numRead += sscanf(memp, "%*s %u", &virtual_size); - } - fclose(status_filep); - } -#endif - return virtual_size; -} - -//static -U32 LLOSInfo::getProcessResidentSizeKB() -{ - U32 resident_size = 0; -#if LL_LINUX - LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); - if (status_filep != NULL) - { - S32 numRead = 0; - char buff[STATUS_SIZE]; /* Flawfinder: ignore */ - - size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); - buff[nbytes] = '\0'; - - // All these guys return numbers in KB - char *memp = strstr(buff, "VmRSS:"); - if (memp) - { - numRead += sscanf(memp, "%*s %u", &resident_size); - } - fclose(status_filep); - } -#endif - return resident_size; -} - -//static -bool LLOSInfo::is64Bit() -{ -#if LL_WINDOWS -#if defined(_WIN64) - return true; -#elif defined(_WIN32) - // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate - BOOL f64 = FALSE; - return IsWow64Process(GetCurrentProcess(), &f64) && f64; -#else - return false; -#endif -#else // ! LL_WINDOWS - // we only build a 64-bit mac viewer and currently we don't build for linux at all - return true; -#endif -} - -LLCPUInfo::LLCPUInfo() -{ - std::ostringstream out; - LLProcessorInfo proc; - // proc.WriteInfoTextFile("procInfo.txt"); - mHasSSE = proc.hasSSE(); - mHasSSE2 = proc.hasSSE2(); - mHasSSE3 = proc.hasSSE3(); - mHasSSE3S = proc.hasSSE3S(); - mHasSSE41 = proc.hasSSE41(); - mHasSSE42 = proc.hasSSE42(); - mHasSSE4a = proc.hasSSE4a(); - mHasAltivec = proc.hasAltivec(); - mCPUMHz = (F64)proc.getCPUFrequency(); - mFamily = proc.getCPUFamilyName(); - mCPUString = "Unknown"; - - out << proc.getCPUBrandName(); - if (200 < mCPUMHz && mCPUMHz < 10000) // *NOTE: cpu speed is often way wrong, do a sanity check - { - out << " (" << mCPUMHz << " MHz)"; - } - mCPUString = out.str(); - LLStringUtil::trim(mCPUString); - - if (mHasSSE) - { - mSSEVersions.append("1"); - } - if (mHasSSE2) - { - mSSEVersions.append("2"); - } - if (mHasSSE3) - { - mSSEVersions.append("3"); - } - if (mHasSSE3S) - { - mSSEVersions.append("3S"); - } - if (mHasSSE41) - { - mSSEVersions.append("4.1"); - } - if (mHasSSE42) - { - mSSEVersions.append("4.2"); - } - if (mHasSSE4a) - { - mSSEVersions.append("4a"); - } -} - -bool LLCPUInfo::hasAltivec() const -{ - return mHasAltivec; -} - -bool LLCPUInfo::hasSSE() const -{ - return mHasSSE; -} - -bool LLCPUInfo::hasSSE2() const -{ - return mHasSSE2; -} - -bool LLCPUInfo::hasSSE3() const -{ - return mHasSSE3; -} - -bool LLCPUInfo::hasSSE3S() const -{ - return mHasSSE3S; -} - -bool LLCPUInfo::hasSSE41() const -{ - return mHasSSE41; -} - -bool LLCPUInfo::hasSSE42() const -{ - return mHasSSE42; -} - -bool LLCPUInfo::hasSSE4a() const -{ - return mHasSSE4a; -} - -F64 LLCPUInfo::getMHz() const -{ - return mCPUMHz; -} - -std::string LLCPUInfo::getCPUString() const -{ - return mCPUString; -} - -const LLSD& LLCPUInfo::getSSEVersions() const -{ - return mSSEVersions; -} - -void LLCPUInfo::stream(std::ostream& s) const -{ - // gather machine information. - s << LLProcessorInfo().getCPUFeatureDescription(); - - // These are interesting as they reflect our internal view of the - // CPU's attributes regardless of platform - s << "->mHasSSE: " << (U32)mHasSSE << std::endl; - s << "->mHasSSE2: " << (U32)mHasSSE2 << std::endl; - s << "->mHasSSE3: " << (U32)mHasSSE3 << std::endl; - s << "->mHasSSE3S: " << (U32)mHasSSE3S << std::endl; - s << "->mHasSSE41: " << (U32)mHasSSE41 << std::endl; - s << "->mHasSSE42: " << (U32)mHasSSE42 << std::endl; - s << "->mHasSSE4a: " << (U32)mHasSSE4a << std::endl; - s << "->mHasAltivec: " << (U32)mHasAltivec << std::endl; - s << "->mCPUMHz: " << mCPUMHz << std::endl; - s << "->mCPUString: " << mCPUString << std::endl; -} - -// Helper class for LLMemoryInfo: accumulate stats in the form we store for -// LLMemoryInfo::getStatsMap(). -class Stats -{ -public: - Stats(): - mStats(LLSD::emptyMap()) - {} - - // Store every integer type as LLSD::Integer. - template <class T> - void add(const LLSD::String& name, const T& value, - typename boost::enable_if<boost::is_integral<T> >::type* = 0) - { - mStats[name] = LLSD::Integer(value); - } - - // Store every floating-point type as LLSD::Real. - template <class T> - void add(const LLSD::String& name, const T& value, - typename boost::enable_if<boost::is_float<T> >::type* = 0) - { - mStats[name] = LLSD::Real(value); - } - - // Hope that LLSD::Date values are sufficiently unambiguous. - void add(const LLSD::String& name, const LLSD::Date& value) - { - mStats[name] = value; - } - - LLSD get() const { return mStats; } - -private: - LLSD mStats; -}; - -LLMemoryInfo::LLMemoryInfo() -{ - refresh(); -} - -#if LL_WINDOWS -static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB) -{ - // Moved this here from llfloaterabout.cpp - - //! \bug - // For some reason, the reported amount of memory is always wrong. - // The original adjustment assumes it's always off by one meg, however - // errors of as much as 2520 KB have been observed in the value - // returned from the GetMemoryStatusEx function. Here we keep the - // original adjustment from llfoaterabout.cpp until this can be - // fixed somehow. - inKB += U32Megabytes(1); - - return inKB; -} -#endif - -#if LL_DARWIN -// static -U32Kilobytes LLMemoryInfo::getHardwareMemSize() -{ - // This might work on Linux as well. Someone check... - uint64_t phys = 0; - int mib[2] = { CTL_HW, HW_MEMSIZE }; - - size_t len = sizeof(phys); - sysctl(mib, 2, &phys, &len, NULL, 0); - - return U64Bytes(phys); -} -#endif - -U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const -{ -#if LL_WINDOWS - return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger())); - -#elif LL_DARWIN - return getHardwareMemSize(); - -#elif LL_LINUX - U64 phys = 0; - phys = (U64)(getpagesize()) * (U64)(get_phys_pages()); - return U64Bytes(phys); - -#else - return 0; - -#endif -} - -//static -void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb) -{ -#if LL_WINDOWS - // Sigh, this shouldn't be a static method, then we wouldn't have to - // reload this data separately from refresh() - LLSD statsMap(loadStatsMap()); - - avail_physical_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger(); - avail_virtual_mem_kb = (U32Kilobytes)statsMap["Avail Virtual KB"].asInteger(); - -#elif LL_DARWIN - // mStatsMap is derived from vm_stat, look for (e.g.) "kb free": - // $ vm_stat - // Mach Virtual Memory Statistics: (page size of 4096 bytes) - // Pages free: 462078. - // Pages active: 142010. - // Pages inactive: 220007. - // Pages wired down: 159552. - // "Translation faults": 220825184. - // Pages copy-on-write: 2104153. - // Pages zero filled: 167034876. - // Pages reactivated: 65153. - // Pageins: 2097212. - // Pageouts: 41759. - // Object cache: 841598 hits of 7629869 lookups (11% hit rate) - avail_physical_mem_kb = (U32Kilobytes)-1 ; - avail_virtual_mem_kb = (U32Kilobytes)-1 ; - -#elif LL_LINUX - // mStatsMap is derived from MEMINFO_FILE: - // $ cat /proc/meminfo - // MemTotal: 4108424 kB - // MemFree: 1244064 kB - // Buffers: 85164 kB - // Cached: 1990264 kB - // SwapCached: 0 kB - // Active: 1176648 kB - // Inactive: 1427532 kB - // Active(anon): 529152 kB - // Inactive(anon): 15924 kB - // Active(file): 647496 kB - // Inactive(file): 1411608 kB - // Unevictable: 16 kB - // Mlocked: 16 kB - // HighTotal: 3266316 kB - // HighFree: 721308 kB - // LowTotal: 842108 kB - // LowFree: 522756 kB - // SwapTotal: 6384632 kB - // SwapFree: 6384632 kB - // Dirty: 28 kB - // Writeback: 0 kB - // AnonPages: 528820 kB - // Mapped: 89472 kB - // Shmem: 16324 kB - // Slab: 159624 kB - // SReclaimable: 145168 kB - // SUnreclaim: 14456 kB - // KernelStack: 2560 kB - // PageTables: 5560 kB - // NFS_Unstable: 0 kB - // Bounce: 0 kB - // WritebackTmp: 0 kB - // CommitLimit: 8438844 kB - // Committed_AS: 1271596 kB - // VmallocTotal: 122880 kB - // VmallocUsed: 65252 kB - // VmallocChunk: 52356 kB - // HardwareCorrupted: 0 kB - // HugePages_Total: 0 - // HugePages_Free: 0 - // HugePages_Rsvd: 0 - // HugePages_Surp: 0 - // Hugepagesize: 2048 kB - // DirectMap4k: 434168 kB - // DirectMap2M: 477184 kB - // (could also run 'free', but easier to read a file than run a program) - avail_physical_mem_kb = (U32Kilobytes)-1 ; - avail_virtual_mem_kb = (U32Kilobytes)-1 ; - -#else - //do not know how to collect available memory info for other systems. - //leave it blank here for now. - - avail_physical_mem_kb = (U32Kilobytes)-1 ; - avail_virtual_mem_kb = (U32Kilobytes)-1 ; -#endif -} - -void LLMemoryInfo::stream(std::ostream& s) const -{ - // We want these memory stats to be easy to grep from the log, along with - // the timestamp. So preface each line with the timestamp and a - // distinctive marker. Without that, we'd have to search the log for the - // introducer line, then read subsequent lines, etc... - std::string pfx(LLError::utcTime() + " <mem> "); - - // Max key length - size_t key_width(0); - for (const auto& [key, value] : inMap(mStatsMap)) - { - size_t len(key.length()); - if (len > key_width) - { - key_width = len; - } - } - - // Now stream stats - for (const auto& [key, value] : inMap(mStatsMap)) - { - s << pfx << std::setw(narrow<size_t>(key_width+1)) << (key + ':') << ' '; - if (value.isInteger()) - s << std::setw(12) << value.asInteger(); - else if (value.isReal()) - s << std::fixed << std::setprecision(1) << value.asReal(); - else if (value.isDate()) - value.asDate().toStream(s); - else - s << value; // just use default LLSD formatting - s << std::endl; - } -} - -LLSD LLMemoryInfo::getStatsMap() const -{ - return mStatsMap; -} - -LLMemoryInfo& LLMemoryInfo::refresh() -{ - LL_PROFILE_ZONE_SCOPED - mStatsMap = loadStatsMap(); - - LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n"; - LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT); - LL_ENDL; - - return *this; -} - -LLSD LLMemoryInfo::loadStatsMap() -{ - LL_PROFILE_ZONE_SCOPED; - - // This implementation is derived from stream() code (as of 2011-06-29). - Stats stats; - - // associate timestamp for analysis over time - stats.add("timestamp", LLDate::now()); - -#if LL_WINDOWS - MEMORYSTATUSEX state; - state.dwLength = sizeof(state); - GlobalMemoryStatusEx(&state); - - DWORDLONG div = 1024; - - stats.add("Percent Memory use", state.dwMemoryLoad/div); - stats.add("Total Physical KB", state.ullTotalPhys/div); - stats.add("Avail Physical KB", state.ullAvailPhys/div); - stats.add("Total page KB", state.ullTotalPageFile/div); - stats.add("Avail page KB", state.ullAvailPageFile/div); - stats.add("Total Virtual KB", state.ullTotalVirtual/div); - stats.add("Avail Virtual KB", state.ullAvailVirtual/div); - - // SL-12122 - Call to GetPerformanceInfo() was removed here. Took - // on order of 10 ms, causing unacceptable frame time spike every - // second, and results were never used. If this is needed in the - // future, must find a way to avoid frame time impact (e.g. move - // to another thread, call much less often). - - PROCESS_MEMORY_COUNTERS_EX pmem; - pmem.cb = sizeof(pmem); - // GetProcessMemoryInfo() is documented to accept either - // PROCESS_MEMORY_COUNTERS* or PROCESS_MEMORY_COUNTERS_EX*, presumably - // using the redundant size info to distinguish. But its prototype - // specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a - // classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the - // pointer. - GetProcessMemoryInfo(GetCurrentProcess(), PPROCESS_MEMORY_COUNTERS(&pmem), sizeof(pmem)); - - stats.add("Page Fault Count", pmem.PageFaultCount); - stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div); - stats.add("WorkingSetSize KB", pmem.WorkingSetSize/div); - stats.add("QutaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage/div); - stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage/div); - stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/div); - stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage/div); - stats.add("PagefileUsage KB", pmem.PagefileUsage/div); - stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage/div); - stats.add("PrivateUsage KB", pmem.PrivateUsage/div); - -#elif LL_DARWIN - - const vm_size_t pagekb(vm_page_size / 1024); - - // - // Collect the vm_stat's - // - - { - vm_statistics64_data_t vmstat; - mach_msg_type_number_t vmstatCount = HOST_VM_INFO64_COUNT; - - if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) &vmstat, &vmstatCount) != KERN_SUCCESS) - { - LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL; - } - else - { - stats.add("Pages free KB", pagekb * vmstat.free_count); - stats.add("Pages active KB", pagekb * vmstat.active_count); - stats.add("Pages inactive KB", pagekb * vmstat.inactive_count); - stats.add("Pages wired KB", pagekb * vmstat.wire_count); - - stats.add("Pages zero fill", vmstat.zero_fill_count); - stats.add("Page reactivations", vmstat.reactivations); - stats.add("Page-ins", vmstat.pageins); - stats.add("Page-outs", vmstat.pageouts); - - stats.add("Faults", vmstat.faults); - stats.add("Faults copy-on-write", vmstat.cow_faults); - - stats.add("Cache lookups", vmstat.lookups); - stats.add("Cache hits", vmstat.hits); - - stats.add("Page purgeable count", vmstat.purgeable_count); - stats.add("Page purges", vmstat.purges); - - stats.add("Page speculative reads", vmstat.speculative_count); - } - } - - // - // Collect the misc task info - // - - { - task_events_info_data_t taskinfo; - unsigned taskinfoSize = sizeof(taskinfo); - - if (task_info(mach_task_self(), TASK_EVENTS_INFO, (task_info_t) &taskinfo, &taskinfoSize) != KERN_SUCCESS) - { - LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL; - } - else - { - stats.add("Task page-ins", taskinfo.pageins); - stats.add("Task copy-on-write faults", taskinfo.cow_faults); - stats.add("Task messages sent", taskinfo.messages_sent); - stats.add("Task messages received", taskinfo.messages_received); - stats.add("Task mach system call count", taskinfo.syscalls_mach); - stats.add("Task unix system call count", taskinfo.syscalls_unix); - stats.add("Task context switch count", taskinfo.csw); - } - } - - // - // Collect the basic task info - // - - { - mach_task_basic_info_data_t taskinfo; - mach_msg_type_number_t task_count = MACH_TASK_BASIC_INFO_COUNT; - if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t) &taskinfo, &task_count) != KERN_SUCCESS) - { - LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL; - } - else - { - stats.add("Basic virtual memory KB", taskinfo.virtual_size / 1024); - stats.add("Basic resident memory KB", taskinfo.resident_size / 1024); - stats.add("Basic max resident memory KB", taskinfo.resident_size_max / 1024); - stats.add("Basic new thread policy", taskinfo.policy); - stats.add("Basic suspend count", taskinfo.suspend_count); - } - } - -#elif LL_LINUX - std::ifstream meminfo(MEMINFO_FILE); - if (meminfo.is_open()) - { - // MemTotal: 4108424 kB - // MemFree: 1244064 kB - // Buffers: 85164 kB - // Cached: 1990264 kB - // SwapCached: 0 kB - // Active: 1176648 kB - // Inactive: 1427532 kB - // ... - // VmallocTotal: 122880 kB - // VmallocUsed: 65252 kB - // VmallocChunk: 52356 kB - // HardwareCorrupted: 0 kB - // HugePages_Total: 0 - // HugePages_Free: 0 - // HugePages_Rsvd: 0 - // HugePages_Surp: 0 - // Hugepagesize: 2048 kB - // DirectMap4k: 434168 kB - // DirectMap2M: 477184 kB - - // Intentionally don't pass the boost::no_except flag. This - // boost::regex object is constructed with a string literal, so it - // should be valid every time. If it becomes invalid, we WANT an - // exception, hopefully even before the dev checks in. - boost::regex stat_rx("(.+): +([0-9]+)( kB)?"); - boost::smatch matched; - - std::string line; - while (std::getline(meminfo, line)) - { - LL_DEBUGS("LLMemoryInfo") << line << LL_ENDL; - if (ll_regex_match(line, matched, stat_rx)) - { - // e.g. "MemTotal: 4108424 kB" - LLSD::String key(matched[1].first, matched[1].second); - LLSD::String value_str(matched[2].first, matched[2].second); - LLSD::Integer value(0); - try - { - value = boost::lexical_cast<LLSD::Integer>(value_str); - } - catch (const boost::bad_lexical_cast&) - { - LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str - << "' in " << MEMINFO_FILE << " line: " - << line << LL_ENDL; - continue; - } - // Store this statistic. - stats.add(key, value); - } - else - { - LL_WARNS("LLMemoryInfo") << "unrecognized " << MEMINFO_FILE << " line: " - << line << LL_ENDL; - } - } - } - else - { - LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL; - } - -#else - LL_WARNS("LLMemoryInfo") << "Unknown system; unable to collect memory information" << LL_ENDL; - -#endif - - return stats.get(); -} - -std::ostream& operator<<(std::ostream& s, const LLOSInfo& info) -{ - info.stream(s); - return s; -} - -std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info) -{ - info.stream(s); - return s; -} - -std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info) -{ - info.stream(s); - return s; -} - -class FrameWatcher -{ -public: - FrameWatcher(): - // Hooking onto the "mainloop" event pump gets us one call per frame. - mConnection(LLEventPumps::instance() - .obtain("mainloop") - .listen("FrameWatcher", boost::bind(&FrameWatcher::tick, this, _1))), - // Initializing mSampleStart to an invalid timestamp alerts us to skip - // trying to compute framerate on the first call. - mSampleStart(-1), - // Initializing mSampleEnd to 0 ensures that we treat the first call - // as the completion of a sample window. - mSampleEnd(0), - mFrames(0), - // Both MEM_INFO_WINDOW and MEM_INFO_THROTTLE are in seconds. We need - // the number of integer MEM_INFO_THROTTLE sample slots that will fit - // in MEM_INFO_WINDOW. Round up. - mSamples(int((MEM_INFO_WINDOW / MEM_INFO_THROTTLE) + 0.7)), - // Initializing to F32_MAX means that the first real frame will become - // the slowest ever, which sounds like a good idea. - mSlowest(F32_MAX) - {} - - bool tick(const LLSD&) - { - F32 timestamp(mTimer.getElapsedTimeF32()); - - // Count this frame in the interval just completed. - ++mFrames; - - // Have we finished a sample window yet? - if (timestamp < mSampleEnd) - { - // no, just keep waiting - return false; - } - - // Set up for next sample window. Capture values for previous frame in - // local variables and reset data members. - U32 frames(mFrames); - F32 sampleStart(mSampleStart); - // No frames yet in next window - mFrames = 0; - // which starts right now - mSampleStart = timestamp; - // and ends MEM_INFO_THROTTLE seconds in the future - mSampleEnd = mSampleStart + MEM_INFO_THROTTLE; - - // On the very first call, that's all we can do, no framerate - // computation is possible. - if (sampleStart < 0) - { - return false; - } - - // How long did this actually take? As framerate slows, the duration - // of the frame we just finished could push us WELL beyond our desired - // sample window size. - F32 elapsed(timestamp - sampleStart); - F32 framerate(frames/elapsed); - - // Remember previous slowest framerate because we're just about to - // update it. - F32 slowest(mSlowest); - // Remember previous number of samples. - boost::circular_buffer<F32>::size_type prevSize(mSamples.size()); - - // Capture new framerate in our samples buffer. Once the buffer is - // full (after MEM_INFO_WINDOW seconds), this will displace the oldest - // sample. ("So they all rolled over, and one fell out...") - mSamples.push_back(framerate); - - // Calculate the new minimum framerate. I know of no way to update a - // rolling minimum without ever rescanning the buffer. But since there - // are only a few tens of items in this buffer, rescanning it is - // probably cheaper (and certainly easier to reason about) than - // attempting to optimize away some of the scans. - mSlowest = framerate; // pick an arbitrary entry to start - for (boost::circular_buffer<F32>::const_iterator si(mSamples.begin()), send(mSamples.end()); - si != send; ++si) - { - if (*si < mSlowest) - { - mSlowest = *si; - } - } - - // We're especially interested in memory as framerate drops. Only log - // when framerate drops below the slowest framerate we remember. - // (Should always be true for the end of the very first sample - // window.) - if (framerate >= slowest) - { - return false; - } - // Congratulations, we've hit a new low. :-P - - LL_INFOS("FrameWatcher") << ' '; - if (! prevSize) - { - LL_CONT << "initial framerate "; - } - else - { - LL_CONT << "slowest framerate for last " << int(prevSize * MEM_INFO_THROTTLE) - << " seconds "; - } - - auto precision = LL_CONT.precision(); - - LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n' - << LLMemoryInfo(); - - LL_CONT.precision(precision); - LL_CONT << LL_ENDL; - return false; - } - -private: - // Storing the connection in an LLTempBoundListener ensures it will be - // disconnected when we're destroyed. - LLTempBoundListener mConnection; - // Track elapsed time - LLTimer mTimer; - // Some of what you see here is in fact redundant with functionality you - // can get from LLTimer. Unfortunately the LLTimer API is missing the - // feature we need: has at least the stated interval elapsed, and if so, - // exactly how long has passed? So we have to do it by hand, sigh. - // Time at start, end of sample window - F32 mSampleStart, mSampleEnd; - // Frames this sample window - U32 mFrames; - // Sliding window of framerate samples - boost::circular_buffer<F32> mSamples; - // Slowest framerate in mSamples - F32 mSlowest; -}; - -// Need an instance of FrameWatcher before it does any good -static FrameWatcher sFrameWatcher; - -BOOL gunzip_file(const std::string& srcfile, const std::string& dstfile) -{ - std::string tmpfile; - const S32 UNCOMPRESS_BUFFER_SIZE = 32768; - BOOL retval = FALSE; - gzFile src = NULL; - U8 buffer[UNCOMPRESS_BUFFER_SIZE]; - LLFILE *dst = NULL; - S32 bytes = 0; - tmpfile = dstfile + ".t"; -#ifdef LL_WINDOWS - llutf16string utf16filename = utf8str_to_utf16str(srcfile); - src = gzopen_w(utf16filename.c_str(), "rb"); -#else - src = gzopen(srcfile.c_str(), "rb"); -#endif - if (! src) goto err; - dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */ - if (! dst) goto err; - do - { - bytes = gzread(src, buffer, UNCOMPRESS_BUFFER_SIZE); - size_t nwrit = fwrite(buffer, sizeof(U8), bytes, dst); - if (nwrit < (size_t) bytes) - { - LL_WARNS() << "Short write on " << tmpfile << ": Wrote " << nwrit << " of " << bytes << " bytes." << LL_ENDL; - goto err; - } - } while(gzeof(src) == 0); - fclose(dst); - dst = NULL; -#if LL_WINDOWS - // Rename in windows needs the dstfile to not exist. - LLFile::remove(dstfile, ENOENT); -#endif - if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ - retval = TRUE; -err: - if (src != NULL) gzclose(src); - if (dst != NULL) fclose(dst); - return retval; -} - -BOOL gzip_file(const std::string& srcfile, const std::string& dstfile) -{ - const S32 COMPRESS_BUFFER_SIZE = 32768; - std::string tmpfile; - BOOL retval = FALSE; - U8 buffer[COMPRESS_BUFFER_SIZE]; - gzFile dst = NULL; - LLFILE *src = NULL; - S32 bytes = 0; - tmpfile = dstfile + ".t"; - -#ifdef LL_WINDOWS - llutf16string utf16filename = utf8str_to_utf16str(tmpfile); - dst = gzopen_w(utf16filename.c_str(), "wb"); -#else - dst = gzopen(tmpfile.c_str(), "wb"); -#endif - - if (! dst) goto err; - src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */ - if (! src) goto err; - - while ((bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE, src)) > 0) - { - if (gzwrite(dst, buffer, bytes) <= 0) - { - LL_WARNS() << "gzwrite failed: " << gzerror(dst, NULL) << LL_ENDL; - goto err; - } - } - - if (ferror(src)) - { - LL_WARNS() << "Error reading " << srcfile << LL_ENDL; - goto err; - } - - gzclose(dst); - dst = NULL; -#if LL_WINDOWS - // Rename in windows needs the dstfile to not exist. - LLFile::remove(dstfile); -#endif - if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ - retval = TRUE; - err: - if (src != NULL) fclose(src); - if (dst != NULL) gzclose(dst); - return retval; -} +/**
+ * @file llsys.cpp
+ * @brief Implementation of the basic system query functions.
+ *
+ * $LicenseInfo:firstyear=2002&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_WINDOWS
+#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
+#endif
+
+#include "linden_common.h"
+
+#include "llsys.h"
+
+#include <iostream>
+#ifdef LL_USESYSTEMLIBS
+# include <zlib.h>
+#else
+# include "zlib-ng/zlib.h"
+#endif
+
+#include "llprocessor.h"
+#include "llerrorcontrol.h"
+#include "llevents.h"
+#include "llformat.h"
+#include "llregex.h"
+#include "lltimer.h"
+#include "llsdserialize.h"
+#include "llsdutil.h"
+#include <boost/bind.hpp>
+#include <boost/circular_buffer.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/range.hpp>
+#include <boost/utility/enable_if.hpp>
+#include <boost/type_traits/is_integral.hpp>
+#include <boost/type_traits/is_float.hpp>
+#include "llfasttimer.h"
+
+using namespace llsd;
+
+#if LL_WINDOWS
+# include "llwin32headerslean.h"
+# include <psapi.h> // GetPerformanceInfo() et al.
+# include <VersionHelpers.h>
+#elif LL_DARWIN
+# include "llsys_objc.h"
+# include <errno.h>
+# include <sys/sysctl.h>
+# include <sys/utsname.h>
+# include <stdint.h>
+# include <CoreServices/CoreServices.h>
+# include <stdexcept>
+# include <mach/host_info.h>
+# include <mach/mach_host.h>
+# include <mach/task.h>
+# include <mach/task_info.h>
+#elif LL_LINUX
+# include <errno.h>
+# include <sys/utsname.h>
+# include <unistd.h>
+# include <sys/sysinfo.h>
+# include <stdexcept>
+const char MEMINFO_FILE[] = "/proc/meminfo";
+# include <gnu/libc-version.h>
+#endif
+
+LLCPUInfo gSysCPU;
+
+// Don't log memory info any more often than this. It also serves as our
+// framerate sample size.
+static const F32 MEM_INFO_THROTTLE = 20;
+// Sliding window of samples. We intentionally limit the length of time we
+// remember "the slowest" framerate because framerate is very slow at login.
+// If we only triggered FrameWatcher logging when the session framerate
+// dropped below the login framerate, we'd have very little additional data.
+static const F32 MEM_INFO_WINDOW = 10*60;
+
+LLOSInfo::LLOSInfo() :
+ mMajorVer(0), mMinorVer(0), mBuild(0), mOSVersionString("")
+{
+
+#if LL_WINDOWS
+
+ if (IsWindows10OrGreater())
+ {
+ mMajorVer = 10;
+ mMinorVer = 0;
+ mOSStringSimple = "Microsoft Windows 10 ";
+ }
+ else if (IsWindows8Point1OrGreater())
+ {
+ mMajorVer = 6;
+ mMinorVer = 3;
+ if (IsWindowsServer())
+ {
+ mOSStringSimple = "Windows Server 2012 R2 ";
+ }
+ else
+ {
+ mOSStringSimple = "Microsoft Windows 8.1 ";
+ }
+ }
+ else if (IsWindows8OrGreater())
+ {
+ mMajorVer = 6;
+ mMinorVer = 2;
+ if (IsWindowsServer())
+ {
+ mOSStringSimple = "Windows Server 2012 ";
+ }
+ else
+ {
+ mOSStringSimple = "Microsoft Windows 8 ";
+ }
+ }
+ else if (IsWindows7SP1OrGreater())
+ {
+ mMajorVer = 6;
+ mMinorVer = 1;
+ if (IsWindowsServer())
+ {
+ mOSStringSimple = "Windows Server 2008 R2 SP1 ";
+ }
+ else
+ {
+ mOSStringSimple = "Microsoft Windows 7 SP1 ";
+ }
+ }
+ else if (IsWindows7OrGreater())
+ {
+ mMajorVer = 6;
+ mMinorVer = 1;
+ if (IsWindowsServer())
+ {
+ mOSStringSimple = "Windows Server 2008 R2 ";
+ }
+ else
+ {
+ mOSStringSimple = "Microsoft Windows 7 ";
+ }
+ }
+ else if (IsWindowsVistaSP2OrGreater())
+ {
+ mMajorVer = 6;
+ mMinorVer = 0;
+ if (IsWindowsServer())
+ {
+ mOSStringSimple = "Windows Server 2008 SP2 ";
+ }
+ else
+ {
+ mOSStringSimple = "Microsoft Windows Vista SP2 ";
+ }
+ }
+ else
+ {
+ mOSStringSimple = "Unsupported Windows version ";
+ }
+
+ ///get native system info if available..
+ typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); ///function pointer for loading GetNativeSystemInfo
+ SYSTEM_INFO si; //System Info object file contains architecture info
+ PGNSI pGNSI; //pointer object
+ ZeroMemory(&si, sizeof(SYSTEM_INFO)); //zero out the memory in information
+ pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); //load kernel32 get function
+ if (NULL != pGNSI) //check if it has failed
+ pGNSI(&si); //success
+ else
+ GetSystemInfo(&si); //if it fails get regular system info
+ //(Warning: If GetSystemInfo it may result in incorrect information in a WOW64 machine, if the kernel fails to load)
+
+ // Try calling GetVersionEx using the OSVERSIONINFOEX structure.
+ OSVERSIONINFOEX osvi;
+ ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ if (GetVersionEx((OSVERSIONINFO *)&osvi))
+ {
+ mBuild = osvi.dwBuildNumber & 0xffff;
+ }
+ else
+ {
+ // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ if (GetVersionEx((OSVERSIONINFO *)&osvi))
+ {
+ mBuild = osvi.dwBuildNumber & 0xffff;
+ }
+ }
+
+ S32 ubr = 0; // Windows 10 Update Build Revision, can be retrieved from a registry
+ if (mMajorVer == 10)
+ {
+ DWORD cbData(sizeof(DWORD));
+ DWORD data(0);
+ HKEY key;
+ LSTATUS ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key);
+ if (ERROR_SUCCESS == ret_code)
+ {
+ ret_code = RegQueryValueExW(key, L"UBR", 0, NULL, reinterpret_cast<LPBYTE>(&data), &cbData);
+ if (ERROR_SUCCESS == ret_code)
+ {
+ ubr = data;
+ }
+ }
+
+ if (mBuild >= 22000)
+ {
+ // At release Windows 11 version was 10.0.22000.194
+ // Windows 10 version was 10.0.19043.1266
+ // There is no warranty that Win10 build won't increase,
+ // so until better solution is found or Microsoft updates
+ // SDK with IsWindows11OrGreater(), indicate "10/11"
+ //
+ // Current alternatives:
+ // Query WMI's Win32_OperatingSystem for OS string. Slow
+ // and likely to return 'compatibility' string.
+ // Check presence of dlls/libs or may be their version.
+ mOSStringSimple = "Microsoft Windows 10/11 ";
+ }
+ }
+
+ //msdn microsoft finds 32 bit and 64 bit flavors this way..
+ //http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx (example code that contains quite a few more flavors
+ //of windows than this code does (in case it is needed for the future)
+ if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) //check for 64 bit
+ {
+ mOSStringSimple += "64-bit ";
+ }
+ else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)
+ {
+ mOSStringSimple += "32-bit ";
+ }
+
+ mOSString = mOSStringSimple;
+ if (mBuild > 0)
+ {
+ mOSString += llformat("(Build %d", mBuild);
+ if (ubr > 0)
+ {
+ mOSString += llformat(".%d", ubr);
+ }
+ mOSString += ")";
+ }
+
+ LLStringUtil::trim(mOSStringSimple);
+ LLStringUtil::trim(mOSString);
+
+#elif LL_DARWIN
+
+ // Initialize mOSStringSimple to something like:
+ // "macOS 10.13.1"
+ {
+ const char * DARWIN_PRODUCT_NAME = "macOS";
+
+ int64_t major_version, minor_version, bugfix_version = 0;
+
+ if (LLGetDarwinOSInfo(major_version, minor_version, bugfix_version))
+ {
+ mMajorVer = major_version;
+ mMinorVer = minor_version;
+ mBuild = bugfix_version;
+
+ std::stringstream os_version_string;
+ os_version_string << DARWIN_PRODUCT_NAME << " " << mMajorVer << "." << mMinorVer << "." << mBuild;
+
+ // Put it in the OS string we are compiling
+ mOSStringSimple.append(os_version_string.str());
+ }
+ else
+ {
+ mOSStringSimple.append("Unable to collect OS info");
+ }
+ }
+
+ // Initialize mOSString to something like:
+ // "macOS 10.13.1 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386"
+ struct utsname un;
+ if(uname(&un) != -1)
+ {
+ mOSString = mOSStringSimple;
+ mOSString.append(" ");
+ mOSString.append(un.sysname);
+ mOSString.append(" ");
+ mOSString.append(un.release);
+ mOSString.append(" ");
+ mOSString.append(un.version);
+ mOSString.append(" ");
+ mOSString.append(un.machine);
+ }
+ else
+ {
+ mOSString = mOSStringSimple;
+ }
+
+#elif LL_LINUX
+
+ struct utsname un;
+ if(uname(&un) != -1)
+ {
+ mOSStringSimple.append(un.sysname);
+ mOSStringSimple.append(" ");
+ mOSStringSimple.append(un.release);
+
+ mOSString = mOSStringSimple;
+ mOSString.append(" ");
+ mOSString.append(un.version);
+ mOSString.append(" ");
+ mOSString.append(un.machine);
+
+ // Simplify 'Simple'
+ std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0));
+ if (ostype == "Linux")
+ {
+ // Only care about major and minor Linux versions, truncate at second '.'
+ std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0);
+ std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos;
+ std::string simple = mOSStringSimple.substr(0, idx2);
+ if (simple.length() > 0)
+ mOSStringSimple = simple;
+ }
+ }
+ else
+ {
+ mOSStringSimple.append("Unable to collect OS info");
+ mOSString = mOSStringSimple;
+ }
+
+ const char OS_VERSION_MATCH_EXPRESSION[] = "([0-9]+)\\.([0-9]+)(\\.([0-9]+))?";
+ boost::regex os_version_parse(OS_VERSION_MATCH_EXPRESSION);
+ boost::smatch matched;
+
+ std::string glibc_version(gnu_get_libc_version());
+ if ( ll_regex_match(glibc_version, matched, os_version_parse) )
+ {
+ LL_INFOS("AppInit") << "Using glibc version '" << glibc_version << "' as OS version" << LL_ENDL;
+
+ std::string version_value;
+
+ if ( matched[1].matched ) // Major version
+ {
+ version_value.assign(matched[1].first, matched[1].second);
+ if (sscanf(version_value.c_str(), "%d", &mMajorVer) != 1)
+ {
+ LL_WARNS("AppInit") << "failed to parse major version '" << version_value << "' as a number" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_ERRS("AppInit")
+ << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION
+ << "' returned true, but major version [1] did not match"
+ << LL_ENDL;
+ }
+
+ if ( matched[2].matched ) // Minor version
+ {
+ version_value.assign(matched[2].first, matched[2].second);
+ if (sscanf(version_value.c_str(), "%d", &mMinorVer) != 1)
+ {
+ LL_ERRS("AppInit") << "failed to parse minor version '" << version_value << "' as a number" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_ERRS("AppInit")
+ << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION
+ << "' returned true, but minor version [1] did not match"
+ << LL_ENDL;
+ }
+
+ if ( matched[4].matched ) // Build version (optional) - note that [3] includes the '.'
+ {
+ version_value.assign(matched[4].first, matched[4].second);
+ if (sscanf(version_value.c_str(), "%d", &mBuild) != 1)
+ {
+ LL_ERRS("AppInit") << "failed to parse build version '" << version_value << "' as a number" << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_INFOS("AppInit")
+ << "OS build version not provided; using zero"
+ << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("AppInit") << "glibc version '" << glibc_version << "' cannot be parsed to three numbers; using all zeros" << LL_ENDL;
+ }
+
+#else
+
+ struct utsname un;
+ if(uname(&un) != -1)
+ {
+ mOSStringSimple.append(un.sysname);
+ mOSStringSimple.append(" ");
+ mOSStringSimple.append(un.release);
+
+ mOSString = mOSStringSimple;
+ mOSString.append(" ");
+ mOSString.append(un.version);
+ mOSString.append(" ");
+ mOSString.append(un.machine);
+
+ // Simplify 'Simple'
+ std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0));
+ if (ostype == "Linux")
+ {
+ // Only care about major and minor Linux versions, truncate at second '.'
+ std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0);
+ std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos;
+ std::string simple = mOSStringSimple.substr(0, idx2);
+ if (simple.length() > 0)
+ mOSStringSimple = simple;
+ }
+ }
+ else
+ {
+ mOSStringSimple.append("Unable to collect OS info");
+ mOSString = mOSStringSimple;
+ }
+
+#endif
+
+ std::stringstream dotted_version_string;
+ dotted_version_string << mMajorVer << "." << mMinorVer << "." << mBuild;
+ mOSVersionString.append(dotted_version_string.str());
+
+ mOSBitness = is64Bit() ? 64 : 32;
+ LL_INFOS("LLOSInfo") << "OS bitness: " << mOSBitness << LL_ENDL;
+}
+
+#ifndef LL_WINDOWS
+// static
+long LLOSInfo::getMaxOpenFiles()
+{
+ const long OPEN_MAX_GUESS = 256;
+
+#ifdef OPEN_MAX
+ static long open_max = OPEN_MAX;
+#else
+ static long open_max = 0;
+#endif
+
+ if (0 == open_max)
+ {
+ // First time through.
+ errno = 0;
+ if ( (open_max = sysconf(_SC_OPEN_MAX)) < 0)
+ {
+ if (0 == errno)
+ {
+ // Indeterminate.
+ open_max = OPEN_MAX_GUESS;
+ }
+ else
+ {
+ LL_ERRS() << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << LL_ENDL;
+ }
+ }
+ }
+ return open_max;
+}
+#endif
+
+void LLOSInfo::stream(std::ostream& s) const
+{
+ s << mOSString;
+}
+
+const std::string& LLOSInfo::getOSString() const
+{
+ return mOSString;
+}
+
+const std::string& LLOSInfo::getOSStringSimple() const
+{
+ return mOSStringSimple;
+}
+
+const std::string& LLOSInfo::getOSVersionString() const
+{
+ return mOSVersionString;
+}
+
+const S32 LLOSInfo::getOSBitness() const
+{
+ return mOSBitness;
+}
+
+//static
+U32 LLOSInfo::getProcessVirtualSizeKB()
+{
+ U32 virtual_size = 0;
+#if LL_LINUX
+# define STATUS_SIZE 2048
+ LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb");
+ if (status_filep)
+ {
+ S32 numRead = 0;
+ char buff[STATUS_SIZE]; /* Flawfinder: ignore */
+
+ size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep);
+ buff[nbytes] = '\0';
+
+ // All these guys return numbers in KB
+ char *memp = strstr(buff, "VmSize:");
+ if (memp)
+ {
+ numRead += sscanf(memp, "%*s %u", &virtual_size);
+ }
+ fclose(status_filep);
+ }
+#endif
+ return virtual_size;
+}
+
+//static
+U32 LLOSInfo::getProcessResidentSizeKB()
+{
+ U32 resident_size = 0;
+#if LL_LINUX
+ LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb");
+ if (status_filep != NULL)
+ {
+ S32 numRead = 0;
+ char buff[STATUS_SIZE]; /* Flawfinder: ignore */
+
+ size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep);
+ buff[nbytes] = '\0';
+
+ // All these guys return numbers in KB
+ char *memp = strstr(buff, "VmRSS:");
+ if (memp)
+ {
+ numRead += sscanf(memp, "%*s %u", &resident_size);
+ }
+ fclose(status_filep);
+ }
+#endif
+ return resident_size;
+}
+
+//static
+bool LLOSInfo::is64Bit()
+{
+#if LL_WINDOWS
+#if defined(_WIN64)
+ return true;
+#elif defined(_WIN32)
+ // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate
+ bool f64 = false;
+ return IsWow64Process(GetCurrentProcess(), &f64) && f64;
+#else
+ return false;
+#endif
+#else // ! LL_WINDOWS
+ // we only build a 64-bit mac viewer and currently we don't build for linux at all
+ return true;
+#endif
+}
+
+LLCPUInfo::LLCPUInfo()
+{
+ std::ostringstream out;
+ LLProcessorInfo proc;
+ // proc.WriteInfoTextFile("procInfo.txt");
+ mHasSSE = proc.hasSSE();
+ mHasSSE2 = proc.hasSSE2();
+ mHasSSE3 = proc.hasSSE3();
+ mHasSSE3S = proc.hasSSE3S();
+ mHasSSE41 = proc.hasSSE41();
+ mHasSSE42 = proc.hasSSE42();
+ mHasSSE4a = proc.hasSSE4a();
+ mHasAltivec = proc.hasAltivec();
+ mCPUMHz = (F64)proc.getCPUFrequency();
+ mFamily = proc.getCPUFamilyName();
+ mCPUString = "Unknown";
+
+ out << proc.getCPUBrandName();
+ if (200 < mCPUMHz && mCPUMHz < 10000) // *NOTE: cpu speed is often way wrong, do a sanity check
+ {
+ out << " (" << mCPUMHz << " MHz)";
+ }
+ mCPUString = out.str();
+ LLStringUtil::trim(mCPUString);
+
+ if (mHasSSE)
+ {
+ mSSEVersions.append("1");
+ }
+ if (mHasSSE2)
+ {
+ mSSEVersions.append("2");
+ }
+ if (mHasSSE3)
+ {
+ mSSEVersions.append("3");
+ }
+ if (mHasSSE3S)
+ {
+ mSSEVersions.append("3S");
+ }
+ if (mHasSSE41)
+ {
+ mSSEVersions.append("4.1");
+ }
+ if (mHasSSE42)
+ {
+ mSSEVersions.append("4.2");
+ }
+ if (mHasSSE4a)
+ {
+ mSSEVersions.append("4a");
+ }
+}
+
+bool LLCPUInfo::hasAltivec() const
+{
+ return mHasAltivec;
+}
+
+bool LLCPUInfo::hasSSE() const
+{
+ return mHasSSE;
+}
+
+bool LLCPUInfo::hasSSE2() const
+{
+ return mHasSSE2;
+}
+
+bool LLCPUInfo::hasSSE3() const
+{
+ return mHasSSE3;
+}
+
+bool LLCPUInfo::hasSSE3S() const
+{
+ return mHasSSE3S;
+}
+
+bool LLCPUInfo::hasSSE41() const
+{
+ return mHasSSE41;
+}
+
+bool LLCPUInfo::hasSSE42() const
+{
+ return mHasSSE42;
+}
+
+bool LLCPUInfo::hasSSE4a() const
+{
+ return mHasSSE4a;
+}
+
+F64 LLCPUInfo::getMHz() const
+{
+ return mCPUMHz;
+}
+
+std::string LLCPUInfo::getCPUString() const
+{
+ return mCPUString;
+}
+
+const LLSD& LLCPUInfo::getSSEVersions() const
+{
+ return mSSEVersions;
+}
+
+void LLCPUInfo::stream(std::ostream& s) const
+{
+ // gather machine information.
+ s << LLProcessorInfo().getCPUFeatureDescription();
+
+ // These are interesting as they reflect our internal view of the
+ // CPU's attributes regardless of platform
+ s << "->mHasSSE: " << (U32)mHasSSE << std::endl;
+ s << "->mHasSSE2: " << (U32)mHasSSE2 << std::endl;
+ s << "->mHasSSE3: " << (U32)mHasSSE3 << std::endl;
+ s << "->mHasSSE3S: " << (U32)mHasSSE3S << std::endl;
+ s << "->mHasSSE41: " << (U32)mHasSSE41 << std::endl;
+ s << "->mHasSSE42: " << (U32)mHasSSE42 << std::endl;
+ s << "->mHasSSE4a: " << (U32)mHasSSE4a << std::endl;
+ s << "->mHasAltivec: " << (U32)mHasAltivec << std::endl;
+ s << "->mCPUMHz: " << mCPUMHz << std::endl;
+ s << "->mCPUString: " << mCPUString << std::endl;
+}
+
+// Helper class for LLMemoryInfo: accumulate stats in the form we store for
+// LLMemoryInfo::getStatsMap().
+class Stats
+{
+public:
+ Stats():
+ mStats(LLSD::emptyMap())
+ {}
+
+ // Store every integer type as LLSD::Integer.
+ template <class T>
+ void add(const LLSD::String& name, const T& value,
+ typename boost::enable_if<boost::is_integral<T> >::type* = 0)
+ {
+ mStats[name] = LLSD::Integer(value);
+ }
+
+ // Store every floating-point type as LLSD::Real.
+ template <class T>
+ void add(const LLSD::String& name, const T& value,
+ typename boost::enable_if<boost::is_float<T> >::type* = 0)
+ {
+ mStats[name] = LLSD::Real(value);
+ }
+
+ // Hope that LLSD::Date values are sufficiently unambiguous.
+ void add(const LLSD::String& name, const LLSD::Date& value)
+ {
+ mStats[name] = value;
+ }
+
+ LLSD get() const { return mStats; }
+
+private:
+ LLSD mStats;
+};
+
+LLMemoryInfo::LLMemoryInfo()
+{
+ refresh();
+}
+
+#if LL_WINDOWS
+static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB)
+{
+ // Moved this here from llfloaterabout.cpp
+
+ //! \bug
+ // For some reason, the reported amount of memory is always wrong.
+ // The original adjustment assumes it's always off by one meg, however
+ // errors of as much as 2520 KB have been observed in the value
+ // returned from the GetMemoryStatusEx function. Here we keep the
+ // original adjustment from llfoaterabout.cpp until this can be
+ // fixed somehow.
+ inKB += U32Megabytes(1);
+
+ return inKB;
+}
+#endif
+
+#if LL_DARWIN
+// static
+U32Kilobytes LLMemoryInfo::getHardwareMemSize()
+{
+ // This might work on Linux as well. Someone check...
+ uint64_t phys = 0;
+ int mib[2] = { CTL_HW, HW_MEMSIZE };
+
+ size_t len = sizeof(phys);
+ sysctl(mib, 2, &phys, &len, NULL, 0);
+
+ return U64Bytes(phys);
+}
+#endif
+
+U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const
+{
+#if LL_WINDOWS
+ return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger()));
+
+#elif LL_DARWIN
+ return getHardwareMemSize();
+
+#elif LL_LINUX
+ U64 phys = 0;
+ phys = (U64)(getpagesize()) * (U64)(get_phys_pages());
+ return U64Bytes(phys);
+
+#else
+ return 0;
+
+#endif
+}
+
+//static
+void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb)
+{
+#if LL_WINDOWS
+ // Sigh, this shouldn't be a static method, then we wouldn't have to
+ // reload this data separately from refresh()
+ LLSD statsMap(loadStatsMap());
+
+ avail_physical_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger();
+ avail_virtual_mem_kb = (U32Kilobytes)statsMap["Avail Virtual KB"].asInteger();
+
+#elif LL_DARWIN
+ // mStatsMap is derived from vm_stat, look for (e.g.) "kb free":
+ // $ vm_stat
+ // Mach Virtual Memory Statistics: (page size of 4096 bytes)
+ // Pages free: 462078.
+ // Pages active: 142010.
+ // Pages inactive: 220007.
+ // Pages wired down: 159552.
+ // "Translation faults": 220825184.
+ // Pages copy-on-write: 2104153.
+ // Pages zero filled: 167034876.
+ // Pages reactivated: 65153.
+ // Pageins: 2097212.
+ // Pageouts: 41759.
+ // Object cache: 841598 hits of 7629869 lookups (11% hit rate)
+ avail_physical_mem_kb = (U32Kilobytes)-1 ;
+ avail_virtual_mem_kb = (U32Kilobytes)-1 ;
+
+#elif LL_LINUX
+ // mStatsMap is derived from MEMINFO_FILE:
+ // $ cat /proc/meminfo
+ // MemTotal: 4108424 kB
+ // MemFree: 1244064 kB
+ // Buffers: 85164 kB
+ // Cached: 1990264 kB
+ // SwapCached: 0 kB
+ // Active: 1176648 kB
+ // Inactive: 1427532 kB
+ // Active(anon): 529152 kB
+ // Inactive(anon): 15924 kB
+ // Active(file): 647496 kB
+ // Inactive(file): 1411608 kB
+ // Unevictable: 16 kB
+ // Mlocked: 16 kB
+ // HighTotal: 3266316 kB
+ // HighFree: 721308 kB
+ // LowTotal: 842108 kB
+ // LowFree: 522756 kB
+ // SwapTotal: 6384632 kB
+ // SwapFree: 6384632 kB
+ // Dirty: 28 kB
+ // Writeback: 0 kB
+ // AnonPages: 528820 kB
+ // Mapped: 89472 kB
+ // Shmem: 16324 kB
+ // Slab: 159624 kB
+ // SReclaimable: 145168 kB
+ // SUnreclaim: 14456 kB
+ // KernelStack: 2560 kB
+ // PageTables: 5560 kB
+ // NFS_Unstable: 0 kB
+ // Bounce: 0 kB
+ // WritebackTmp: 0 kB
+ // CommitLimit: 8438844 kB
+ // Committed_AS: 1271596 kB
+ // VmallocTotal: 122880 kB
+ // VmallocUsed: 65252 kB
+ // VmallocChunk: 52356 kB
+ // HardwareCorrupted: 0 kB
+ // HugePages_Total: 0
+ // HugePages_Free: 0
+ // HugePages_Rsvd: 0
+ // HugePages_Surp: 0
+ // Hugepagesize: 2048 kB
+ // DirectMap4k: 434168 kB
+ // DirectMap2M: 477184 kB
+ // (could also run 'free', but easier to read a file than run a program)
+ avail_physical_mem_kb = (U32Kilobytes)-1 ;
+ avail_virtual_mem_kb = (U32Kilobytes)-1 ;
+
+#else
+ //do not know how to collect available memory info for other systems.
+ //leave it blank here for now.
+
+ avail_physical_mem_kb = (U32Kilobytes)-1 ;
+ avail_virtual_mem_kb = (U32Kilobytes)-1 ;
+#endif
+}
+
+void LLMemoryInfo::stream(std::ostream& s) const
+{
+ // We want these memory stats to be easy to grep from the log, along with
+ // the timestamp. So preface each line with the timestamp and a
+ // distinctive marker. Without that, we'd have to search the log for the
+ // introducer line, then read subsequent lines, etc...
+ std::string pfx(LLError::utcTime() + " <mem> ");
+
+ // Max key length
+ size_t key_width(0);
+ for (const auto& [key, value] : inMap(mStatsMap))
+ {
+ size_t len(key.length());
+ if (len > key_width)
+ {
+ key_width = len;
+ }
+ }
+
+ // Now stream stats
+ for (const auto& [key, value] : inMap(mStatsMap))
+ {
+ s << pfx << std::setw(narrow<size_t>(key_width+1)) << (key + ':') << ' ';
+ if (value.isInteger())
+ s << std::setw(12) << value.asInteger();
+ else if (value.isReal())
+ s << std::fixed << std::setprecision(1) << value.asReal();
+ else if (value.isDate())
+ value.asDate().toStream(s);
+ else
+ s << value; // just use default LLSD formatting
+ s << std::endl;
+ }
+}
+
+LLSD LLMemoryInfo::getStatsMap() const
+{
+ return mStatsMap;
+}
+
+LLMemoryInfo& LLMemoryInfo::refresh()
+{
+ LL_PROFILE_ZONE_SCOPED
+ mStatsMap = loadStatsMap();
+
+ LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n";
+ LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT);
+ LL_ENDL;
+
+ return *this;
+}
+
+LLSD LLMemoryInfo::loadStatsMap()
+{
+ LL_PROFILE_ZONE_SCOPED;
+
+ // This implementation is derived from stream() code (as of 2011-06-29).
+ Stats stats;
+
+ // associate timestamp for analysis over time
+ stats.add("timestamp", LLDate::now());
+
+#if LL_WINDOWS
+ MEMORYSTATUSEX state;
+ state.dwLength = sizeof(state);
+ GlobalMemoryStatusEx(&state);
+
+ DWORDLONG div = 1024;
+
+ stats.add("Percent Memory use", state.dwMemoryLoad/div);
+ stats.add("Total Physical KB", state.ullTotalPhys/div);
+ stats.add("Avail Physical KB", state.ullAvailPhys/div);
+ stats.add("Total page KB", state.ullTotalPageFile/div);
+ stats.add("Avail page KB", state.ullAvailPageFile/div);
+ stats.add("Total Virtual KB", state.ullTotalVirtual/div);
+ stats.add("Avail Virtual KB", state.ullAvailVirtual/div);
+
+ // SL-12122 - Call to GetPerformanceInfo() was removed here. Took
+ // on order of 10 ms, causing unacceptable frame time spike every
+ // second, and results were never used. If this is needed in the
+ // future, must find a way to avoid frame time impact (e.g. move
+ // to another thread, call much less often).
+
+ PROCESS_MEMORY_COUNTERS_EX pmem;
+ pmem.cb = sizeof(pmem);
+ // GetProcessMemoryInfo() is documented to accept either
+ // PROCESS_MEMORY_COUNTERS* or PROCESS_MEMORY_COUNTERS_EX*, presumably
+ // using the redundant size info to distinguish. But its prototype
+ // specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a
+ // classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the
+ // pointer.
+ GetProcessMemoryInfo(GetCurrentProcess(), PPROCESS_MEMORY_COUNTERS(&pmem), sizeof(pmem));
+
+ stats.add("Page Fault Count", pmem.PageFaultCount);
+ stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div);
+ stats.add("WorkingSetSize KB", pmem.WorkingSetSize/div);
+ stats.add("QutaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage/div);
+ stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage/div);
+ stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/div);
+ stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage/div);
+ stats.add("PagefileUsage KB", pmem.PagefileUsage/div);
+ stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage/div);
+ stats.add("PrivateUsage KB", pmem.PrivateUsage/div);
+
+#elif LL_DARWIN
+
+ const vm_size_t pagekb(vm_page_size / 1024);
+
+ //
+ // Collect the vm_stat's
+ //
+
+ {
+ vm_statistics64_data_t vmstat;
+ mach_msg_type_number_t vmstatCount = HOST_VM_INFO64_COUNT;
+
+ if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) &vmstat, &vmstatCount) != KERN_SUCCESS)
+ {
+ LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL;
+ }
+ else
+ {
+ stats.add("Pages free KB", pagekb * vmstat.free_count);
+ stats.add("Pages active KB", pagekb * vmstat.active_count);
+ stats.add("Pages inactive KB", pagekb * vmstat.inactive_count);
+ stats.add("Pages wired KB", pagekb * vmstat.wire_count);
+
+ stats.add("Pages zero fill", vmstat.zero_fill_count);
+ stats.add("Page reactivations", vmstat.reactivations);
+ stats.add("Page-ins", vmstat.pageins);
+ stats.add("Page-outs", vmstat.pageouts);
+
+ stats.add("Faults", vmstat.faults);
+ stats.add("Faults copy-on-write", vmstat.cow_faults);
+
+ stats.add("Cache lookups", vmstat.lookups);
+ stats.add("Cache hits", vmstat.hits);
+
+ stats.add("Page purgeable count", vmstat.purgeable_count);
+ stats.add("Page purges", vmstat.purges);
+
+ stats.add("Page speculative reads", vmstat.speculative_count);
+ }
+ }
+
+ //
+ // Collect the misc task info
+ //
+
+ {
+ task_events_info_data_t taskinfo;
+ unsigned taskinfoSize = sizeof(taskinfo);
+
+ if (task_info(mach_task_self(), TASK_EVENTS_INFO, (task_info_t) &taskinfo, &taskinfoSize) != KERN_SUCCESS)
+ {
+ LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL;
+ }
+ else
+ {
+ stats.add("Task page-ins", taskinfo.pageins);
+ stats.add("Task copy-on-write faults", taskinfo.cow_faults);
+ stats.add("Task messages sent", taskinfo.messages_sent);
+ stats.add("Task messages received", taskinfo.messages_received);
+ stats.add("Task mach system call count", taskinfo.syscalls_mach);
+ stats.add("Task unix system call count", taskinfo.syscalls_unix);
+ stats.add("Task context switch count", taskinfo.csw);
+ }
+ }
+
+ //
+ // Collect the basic task info
+ //
+
+ {
+ mach_task_basic_info_data_t taskinfo;
+ mach_msg_type_number_t task_count = MACH_TASK_BASIC_INFO_COUNT;
+ if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t) &taskinfo, &task_count) != KERN_SUCCESS)
+ {
+ LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL;
+ }
+ else
+ {
+ stats.add("Basic virtual memory KB", taskinfo.virtual_size / 1024);
+ stats.add("Basic resident memory KB", taskinfo.resident_size / 1024);
+ stats.add("Basic max resident memory KB", taskinfo.resident_size_max / 1024);
+ stats.add("Basic new thread policy", taskinfo.policy);
+ stats.add("Basic suspend count", taskinfo.suspend_count);
+ }
+ }
+
+#elif LL_LINUX
+ std::ifstream meminfo(MEMINFO_FILE);
+ if (meminfo.is_open())
+ {
+ // MemTotal: 4108424 kB
+ // MemFree: 1244064 kB
+ // Buffers: 85164 kB
+ // Cached: 1990264 kB
+ // SwapCached: 0 kB
+ // Active: 1176648 kB
+ // Inactive: 1427532 kB
+ // ...
+ // VmallocTotal: 122880 kB
+ // VmallocUsed: 65252 kB
+ // VmallocChunk: 52356 kB
+ // HardwareCorrupted: 0 kB
+ // HugePages_Total: 0
+ // HugePages_Free: 0
+ // HugePages_Rsvd: 0
+ // HugePages_Surp: 0
+ // Hugepagesize: 2048 kB
+ // DirectMap4k: 434168 kB
+ // DirectMap2M: 477184 kB
+
+ // Intentionally don't pass the boost::no_except flag. This
+ // boost::regex object is constructed with a string literal, so it
+ // should be valid every time. If it becomes invalid, we WANT an
+ // exception, hopefully even before the dev checks in.
+ boost::regex stat_rx("(.+): +([0-9]+)( kB)?");
+ boost::smatch matched;
+
+ std::string line;
+ while (std::getline(meminfo, line))
+ {
+ LL_DEBUGS("LLMemoryInfo") << line << LL_ENDL;
+ if (ll_regex_match(line, matched, stat_rx))
+ {
+ // e.g. "MemTotal: 4108424 kB"
+ LLSD::String key(matched[1].first, matched[1].second);
+ LLSD::String value_str(matched[2].first, matched[2].second);
+ LLSD::Integer value(0);
+ try
+ {
+ value = boost::lexical_cast<LLSD::Integer>(value_str);
+ }
+ catch (const boost::bad_lexical_cast&)
+ {
+ LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str
+ << "' in " << MEMINFO_FILE << " line: "
+ << line << LL_ENDL;
+ continue;
+ }
+ // Store this statistic.
+ stats.add(key, value);
+ }
+ else
+ {
+ LL_WARNS("LLMemoryInfo") << "unrecognized " << MEMINFO_FILE << " line: "
+ << line << LL_ENDL;
+ }
+ }
+ }
+ else
+ {
+ LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL;
+ }
+
+#else
+ LL_WARNS("LLMemoryInfo") << "Unknown system; unable to collect memory information" << LL_ENDL;
+
+#endif
+
+ return stats.get();
+}
+
+std::ostream& operator<<(std::ostream& s, const LLOSInfo& info)
+{
+ info.stream(s);
+ return s;
+}
+
+std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info)
+{
+ info.stream(s);
+ return s;
+}
+
+std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info)
+{
+ info.stream(s);
+ return s;
+}
+
+class FrameWatcher
+{
+public:
+ FrameWatcher():
+ // Hooking onto the "mainloop" event pump gets us one call per frame.
+ mConnection(LLEventPumps::instance()
+ .obtain("mainloop")
+ .listen("FrameWatcher", boost::bind(&FrameWatcher::tick, this, _1))),
+ // Initializing mSampleStart to an invalid timestamp alerts us to skip
+ // trying to compute framerate on the first call.
+ mSampleStart(-1),
+ // Initializing mSampleEnd to 0 ensures that we treat the first call
+ // as the completion of a sample window.
+ mSampleEnd(0),
+ mFrames(0),
+ // Both MEM_INFO_WINDOW and MEM_INFO_THROTTLE are in seconds. We need
+ // the number of integer MEM_INFO_THROTTLE sample slots that will fit
+ // in MEM_INFO_WINDOW. Round up.
+ mSamples(int((MEM_INFO_WINDOW / MEM_INFO_THROTTLE) + 0.7)),
+ // Initializing to F32_MAX means that the first real frame will become
+ // the slowest ever, which sounds like a good idea.
+ mSlowest(F32_MAX)
+ {}
+
+ bool tick(const LLSD&)
+ {
+ F32 timestamp(mTimer.getElapsedTimeF32());
+
+ // Count this frame in the interval just completed.
+ ++mFrames;
+
+ // Have we finished a sample window yet?
+ if (timestamp < mSampleEnd)
+ {
+ // no, just keep waiting
+ return false;
+ }
+
+ // Set up for next sample window. Capture values for previous frame in
+ // local variables and reset data members.
+ U32 frames(mFrames);
+ F32 sampleStart(mSampleStart);
+ // No frames yet in next window
+ mFrames = 0;
+ // which starts right now
+ mSampleStart = timestamp;
+ // and ends MEM_INFO_THROTTLE seconds in the future
+ mSampleEnd = mSampleStart + MEM_INFO_THROTTLE;
+
+ // On the very first call, that's all we can do, no framerate
+ // computation is possible.
+ if (sampleStart < 0)
+ {
+ return false;
+ }
+
+ // How long did this actually take? As framerate slows, the duration
+ // of the frame we just finished could push us WELL beyond our desired
+ // sample window size.
+ F32 elapsed(timestamp - sampleStart);
+ F32 framerate(frames/elapsed);
+
+ // Remember previous slowest framerate because we're just about to
+ // update it.
+ F32 slowest(mSlowest);
+ // Remember previous number of samples.
+ boost::circular_buffer<F32>::size_type prevSize(mSamples.size());
+
+ // Capture new framerate in our samples buffer. Once the buffer is
+ // full (after MEM_INFO_WINDOW seconds), this will displace the oldest
+ // sample. ("So they all rolled over, and one fell out...")
+ mSamples.push_back(framerate);
+
+ // Calculate the new minimum framerate. I know of no way to update a
+ // rolling minimum without ever rescanning the buffer. But since there
+ // are only a few tens of items in this buffer, rescanning it is
+ // probably cheaper (and certainly easier to reason about) than
+ // attempting to optimize away some of the scans.
+ mSlowest = framerate; // pick an arbitrary entry to start
+ for (boost::circular_buffer<F32>::const_iterator si(mSamples.begin()), send(mSamples.end());
+ si != send; ++si)
+ {
+ if (*si < mSlowest)
+ {
+ mSlowest = *si;
+ }
+ }
+
+ // We're especially interested in memory as framerate drops. Only log
+ // when framerate drops below the slowest framerate we remember.
+ // (Should always be true for the end of the very first sample
+ // window.)
+ if (framerate >= slowest)
+ {
+ return false;
+ }
+ // Congratulations, we've hit a new low. :-P
+
+ LL_INFOS("FrameWatcher") << ' ';
+ if (! prevSize)
+ {
+ LL_CONT << "initial framerate ";
+ }
+ else
+ {
+ LL_CONT << "slowest framerate for last " << int(prevSize * MEM_INFO_THROTTLE)
+ << " seconds ";
+ }
+
+ auto precision = LL_CONT.precision();
+
+ LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n'
+ << LLMemoryInfo();
+
+ LL_CONT.precision(precision);
+ LL_CONT << LL_ENDL;
+ return false;
+ }
+
+private:
+ // Storing the connection in an LLTempBoundListener ensures it will be
+ // disconnected when we're destroyed.
+ LLTempBoundListener mConnection;
+ // Track elapsed time
+ LLTimer mTimer;
+ // Some of what you see here is in fact redundant with functionality you
+ // can get from LLTimer. Unfortunately the LLTimer API is missing the
+ // feature we need: has at least the stated interval elapsed, and if so,
+ // exactly how long has passed? So we have to do it by hand, sigh.
+ // Time at start, end of sample window
+ F32 mSampleStart, mSampleEnd;
+ // Frames this sample window
+ U32 mFrames;
+ // Sliding window of framerate samples
+ boost::circular_buffer<F32> mSamples;
+ // Slowest framerate in mSamples
+ F32 mSlowest;
+};
+
+// Need an instance of FrameWatcher before it does any good
+static FrameWatcher sFrameWatcher;
+
+bool gunzip_file(const std::string& srcfile, const std::string& dstfile)
+{
+ std::string tmpfile;
+ const S32 UNCOMPRESS_BUFFER_SIZE = 32768;
+ bool retval = false;
+ gzFile src = NULL;
+ U8 buffer[UNCOMPRESS_BUFFER_SIZE];
+ LLFILE *dst = NULL;
+ S32 bytes = 0;
+ tmpfile = dstfile + ".t";
+#ifdef LL_WINDOWS
+ llutf16string utf16filename = utf8str_to_utf16str(srcfile);
+ src = gzopen_w(utf16filename.c_str(), "rb");
+#else
+ src = gzopen(srcfile.c_str(), "rb");
+#endif
+ if (! src) goto err;
+ dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */
+ if (! dst) goto err;
+ do
+ {
+ bytes = gzread(src, buffer, UNCOMPRESS_BUFFER_SIZE);
+ size_t nwrit = fwrite(buffer, sizeof(U8), bytes, dst);
+ if (nwrit < (size_t) bytes)
+ {
+ LL_WARNS() << "Short write on " << tmpfile << ": Wrote " << nwrit << " of " << bytes << " bytes." << LL_ENDL;
+ goto err;
+ }
+ } while(gzeof(src) == 0);
+ fclose(dst);
+ dst = NULL;
+#if LL_WINDOWS
+ // Rename in windows needs the dstfile to not exist.
+ LLFile::remove(dstfile, ENOENT);
+#endif
+ if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */
+ retval = true;
+err:
+ if (src != NULL) gzclose(src);
+ if (dst != NULL) fclose(dst);
+ return retval;
+}
+
+bool gzip_file(const std::string& srcfile, const std::string& dstfile)
+{
+ const S32 COMPRESS_BUFFER_SIZE = 32768;
+ std::string tmpfile;
+ bool retval = false;
+ U8 buffer[COMPRESS_BUFFER_SIZE];
+ gzFile dst = NULL;
+ LLFILE *src = NULL;
+ S32 bytes = 0;
+ tmpfile = dstfile + ".t";
+
+#ifdef LL_WINDOWS
+ llutf16string utf16filename = utf8str_to_utf16str(tmpfile);
+ dst = gzopen_w(utf16filename.c_str(), "wb");
+#else
+ dst = gzopen(tmpfile.c_str(), "wb");
+#endif
+
+ if (! dst) goto err;
+ src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */
+ if (! src) goto err;
+
+ while ((bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE, src)) > 0)
+ {
+ if (gzwrite(dst, buffer, bytes) <= 0)
+ {
+ LL_WARNS() << "gzwrite failed: " << gzerror(dst, NULL) << LL_ENDL;
+ goto err;
+ }
+ }
+
+ if (ferror(src))
+ {
+ LL_WARNS() << "Error reading " << srcfile << LL_ENDL;
+ goto err;
+ }
+
+ gzclose(dst);
+ dst = NULL;
+#if LL_WINDOWS
+ // Rename in windows needs the dstfile to not exist.
+ LLFile::remove(dstfile);
+#endif
+ if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */
+ retval = true;
+ err:
+ if (src != NULL) fclose(src);
+ if (dst != NULL) gzclose(dst);
+ return retval;
+}
diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index 5c87ce6bf2..3ef1e2b528 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -1,174 +1,174 @@ -/** - * @file llsys.h - * @brief System information debugging classes. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_SYS_H -#define LL_SYS_H - -// -// The LLOSInfo, LLCPUInfo, and LLMemoryInfo classes are essentially -// the same, but query different machine subsystems. Here's how you -// use an LLCPUInfo object: -// -// LLCPUInfo info; -// LL_INFOS() << info << LL_ENDL; -// - -#include "llsd.h" -#include "llsingleton.h" -#include <iosfwd> -#include <string> - -class LL_COMMON_API LLOSInfo : public LLSingleton<LLOSInfo> -{ - LLSINGLETON(LLOSInfo); -public: - void stream(std::ostream& s) const; - - const std::string& getOSString() const; - const std::string& getOSStringSimple() const; - - const std::string& getOSVersionString() const; - - const S32 getOSBitness() const; - - S32 mMajorVer; - S32 mMinorVer; - S32 mBuild; - -#ifndef LL_WINDOWS - static long getMaxOpenFiles(); -#endif - static bool is64Bit(); - - static U32 getProcessVirtualSizeKB(); - static U32 getProcessResidentSizeKB(); -private: - std::string mOSString; - std::string mOSStringSimple; - std::string mOSVersionString; - S32 mOSBitness; -}; - - -class LL_COMMON_API LLCPUInfo -{ -public: - LLCPUInfo(); - void stream(std::ostream& s) const; - - std::string getCPUString() const; - const LLSD& getSSEVersions() const; - - bool hasAltivec() const; - bool hasSSE() const; - bool hasSSE2() const; - bool hasSSE3() const; - bool hasSSE3S() const; - bool hasSSE41() const; - bool hasSSE42() const; - bool hasSSE4a() const; - F64 getMHz() const; - - // Family is "AMD Duron" or "Intel Pentium Pro" - const std::string& getFamily() const { return mFamily; } - -private: - bool mHasSSE; - bool mHasSSE2; - bool mHasSSE3; - bool mHasSSE3S; - bool mHasSSE41; - bool mHasSSE42; - bool mHasSSE4a; - bool mHasAltivec; - F64 mCPUMHz; - std::string mFamily; - std::string mCPUString; - LLSD mSSEVersions; -}; - -//============================================================================= -// -// CLASS LLMemoryInfo - -class LL_COMMON_API LLMemoryInfo - -/*! @brief Class to query the memory subsystem - - @details - Here's how you use an LLMemoryInfo: - - LLMemoryInfo info; -<br> LL_INFOS() << info << LL_ENDL; -*/ -{ -public: - LLMemoryInfo(); ///< Default constructor - void stream(std::ostream& s) const; ///< output text info to s - - U32Kilobytes getPhysicalMemoryKB() const; -#if LL_DARWIN - static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib. -#endif - - //get the available memory infomation in KiloBytes. - static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb); - - // Retrieve a map of memory statistics. The keys of the map are platform- - // dependent. The values are in kilobytes to try to avoid integer overflow. - LLSD getStatsMap() const; - - // Re-fetch memory data (as reported by stream() and getStatsMap()) from the - // system. Normally this is fetched at construction time. Return (*this) - // to permit usage of the form: - // @code - // LLMemoryInfo info; - // ... - // info.refresh().getStatsMap(); - // @endcode - LLMemoryInfo& refresh(); - -private: - // set mStatsMap - static LLSD loadStatsMap(); - - // Memory stats for getStatsMap(). - LLSD mStatsMap; -}; - - -LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); -LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); -LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); - -// gunzip srcfile into dstfile. Returns FALSE on error. -BOOL LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile); -// gzip srcfile into dstfile. Returns FALSE on error. -BOOL LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); - -extern LL_COMMON_API LLCPUInfo gSysCPU; - -#endif // LL_LLSYS_H +/**
+ * @file llsys.h
+ * @brief System information debugging classes.
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_SYS_H
+#define LL_SYS_H
+
+//
+// The LLOSInfo, LLCPUInfo, and LLMemoryInfo classes are essentially
+// the same, but query different machine subsystems. Here's how you
+// use an LLCPUInfo object:
+//
+// LLCPUInfo info;
+// LL_INFOS() << info << LL_ENDL;
+//
+
+#include "llsd.h"
+#include "llsingleton.h"
+#include <iosfwd>
+#include <string>
+
+class LL_COMMON_API LLOSInfo : public LLSingleton<LLOSInfo>
+{
+ LLSINGLETON(LLOSInfo);
+public:
+ void stream(std::ostream& s) const;
+
+ const std::string& getOSString() const;
+ const std::string& getOSStringSimple() const;
+
+ const std::string& getOSVersionString() const;
+
+ const S32 getOSBitness() const;
+
+ S32 mMajorVer;
+ S32 mMinorVer;
+ S32 mBuild;
+
+#ifndef LL_WINDOWS
+ static long getMaxOpenFiles();
+#endif
+ static bool is64Bit();
+
+ static U32 getProcessVirtualSizeKB();
+ static U32 getProcessResidentSizeKB();
+private:
+ std::string mOSString;
+ std::string mOSStringSimple;
+ std::string mOSVersionString;
+ S32 mOSBitness;
+};
+
+
+class LL_COMMON_API LLCPUInfo
+{
+public:
+ LLCPUInfo();
+ void stream(std::ostream& s) const;
+
+ std::string getCPUString() const;
+ const LLSD& getSSEVersions() const;
+
+ bool hasAltivec() const;
+ bool hasSSE() const;
+ bool hasSSE2() const;
+ bool hasSSE3() const;
+ bool hasSSE3S() const;
+ bool hasSSE41() const;
+ bool hasSSE42() const;
+ bool hasSSE4a() const;
+ F64 getMHz() const;
+
+ // Family is "AMD Duron" or "Intel Pentium Pro"
+ const std::string& getFamily() const { return mFamily; }
+
+private:
+ bool mHasSSE;
+ bool mHasSSE2;
+ bool mHasSSE3;
+ bool mHasSSE3S;
+ bool mHasSSE41;
+ bool mHasSSE42;
+ bool mHasSSE4a;
+ bool mHasAltivec;
+ F64 mCPUMHz;
+ std::string mFamily;
+ std::string mCPUString;
+ LLSD mSSEVersions;
+};
+
+//=============================================================================
+//
+// CLASS LLMemoryInfo
+
+class LL_COMMON_API LLMemoryInfo
+
+/*! @brief Class to query the memory subsystem
+
+ @details
+ Here's how you use an LLMemoryInfo:
+
+ LLMemoryInfo info;
+<br> LL_INFOS() << info << LL_ENDL;
+*/
+{
+public:
+ LLMemoryInfo(); ///< Default constructor
+ void stream(std::ostream& s) const; ///< output text info to s
+
+ U32Kilobytes getPhysicalMemoryKB() const;
+#if LL_DARWIN
+ static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib.
+#endif
+
+ //get the available memory infomation in KiloBytes.
+ static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb);
+
+ // Retrieve a map of memory statistics. The keys of the map are platform-
+ // dependent. The values are in kilobytes to try to avoid integer overflow.
+ LLSD getStatsMap() const;
+
+ // Re-fetch memory data (as reported by stream() and getStatsMap()) from the
+ // system. Normally this is fetched at construction time. Return (*this)
+ // to permit usage of the form:
+ // @code
+ // LLMemoryInfo info;
+ // ...
+ // info.refresh().getStatsMap();
+ // @endcode
+ LLMemoryInfo& refresh();
+
+private:
+ // set mStatsMap
+ static LLSD loadStatsMap();
+
+ // Memory stats for getStatsMap().
+ LLSD mStatsMap;
+};
+
+
+LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLOSInfo& info);
+LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info);
+LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info);
+
+// gunzip srcfile into dstfile. Returns false on error.
+bool LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile);
+// gzip srcfile into dstfile. Returns false on error.
+bool LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile);
+
+extern LL_COMMON_API LLCPUInfo gSysCPU;
+
+#endif // LL_LLSYS_H
diff --git a/indra/llcommon/llsys_objc.mm b/indra/llcommon/llsys_objc.mm index 3fd85fb1c9..1393ccea50 100644 --- a/indra/llcommon/llsys_objc.mm +++ b/indra/llcommon/llsys_objc.mm @@ -27,38 +27,12 @@ #import "llsys_objc.h" #import <AppKit/AppKit.h> -static auto intAtStringIndex(NSArray *array, int index) -{ - return [(NSString *)[array objectAtIndex:index] integerValue]; -} - bool LLGetDarwinOSInfo(int64_t &major, int64_t &minor, int64_t &patch) { - if (NSAppKitVersionNumber > NSAppKitVersionNumber10_8) - { NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; major = osVersion.majorVersion; minor = osVersion.minorVersion; patch = osVersion.patchVersion; - } - else - { - NSString* versionString = [[NSDictionary dictionaryWithContentsOfFile: - @"/System/Library/CoreServices/SystemVersion.plist"] objectForKey:@"ProductVersion"]; - NSArray* versions = [versionString componentsSeparatedByString:@"."]; - NSUInteger count = [versions count]; - if (count > 0) - { - major = intAtStringIndex(versions, 0); - if (count > 1) - { - minor = intAtStringIndex(versions, 1); - if (count > 2) - { - patch = intAtStringIndex(versions, 2); - } - } - } - } + return true; } diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index e6b0c03371..deb1df640c 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -1,472 +1,472 @@ -/** - * @file llthread.cpp - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010-2013, 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 "linden_common.h" -#include "llapr.h" - -#include "apr_portable.h" - -#include "llthread.h" -#include "llmutex.h" - -#include "lltimer.h" -#include "lltrace.h" -#include "lltracethreadrecorder.h" -#include "llexception.h" - -#if LL_LINUX -#include <sched.h> -#endif - - -#ifdef LL_WINDOWS - -const DWORD MS_VC_EXCEPTION=0x406D1388; - -#pragma pack(push,8) -typedef struct tagTHREADNAME_INFO -{ - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. -} THREADNAME_INFO; -#pragma pack(pop) - -void set_thread_name( DWORD dwThreadID, const char* threadName) -{ - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = threadName; - info.dwThreadID = dwThreadID; - info.dwFlags = 0; - - __try - { - ::RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info ); - } - __except(EXCEPTION_CONTINUE_EXECUTION) - { - } -} -#endif - - -//---------------------------------------------------------------------------- -// Usage: -// void run_func(LLThread* thread) -// { -// } -// LLThread* thread = new LLThread(); -// thread->run(run_func); -// ... -// thread->setQuitting(); -// while(!timeout) -// { -// if (thread->isStopped()) -// { -// delete thread; -// break; -// } -// } -// -//---------------------------------------------------------------------------- -namespace -{ - - LLThread::id_t main_thread() - { - // Using a function-static variable to identify the main thread - // requires that control reach here from the main thread before it - // reaches here from any other thread. We simply trust that whichever - // thread gets here first is the main thread. - static LLThread::id_t s_thread_id = LLThread::currentID(); - return s_thread_id; - } - -} // anonymous namespace - -LL_COMMON_API bool on_main_thread() -{ - return (LLThread::currentID() == main_thread()); -} - -LL_COMMON_API bool assert_main_thread() -{ - auto curr = LLThread::currentID(); - auto main = main_thread(); - if (curr == main) - return true; - - LL_WARNS() << "Illegal execution from thread id " << curr - << " outside main thread " << main << LL_ENDL; - return false; -} - -// this function has become moot -void LLThread::registerThreadID() {} - -// -// Handed to the APR thread creation function -// -void LLThread::threadRun() -{ -#ifdef LL_WINDOWS - set_thread_name(-1, mName.c_str()); - -#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32) - HANDLE hThread = GetCurrentThread(); - if (hThread) - { - SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE); - } -#endif - -#endif - - LL_PROFILER_SET_THREAD_NAME( mName.c_str() ); - - // this is the first point at which we're actually running in the new thread - mID = currentID(); - - // for now, hard code all LLThreads to report to single master thread recorder, which is known to be running on main thread - mRecorder = new LLTrace::ThreadRecorder(*LLTrace::get_master_thread_recorder()); - - // Run the user supplied function - do - { - try - { - run(); - } - catch (const LLContinueError &e) - { - LL_WARNS("THREAD") << "ContinueException on thread '" << mName << - "' reentering run(). Error what is: '" << e.what() << "'" << LL_ENDL; - //output possible call stacks to log file. - LLError::LLCallStacks::print(); - - LOG_UNHANDLED_EXCEPTION("LLThread"); - continue; - } - break; - - } while (true); - - //LL_INFOS() << "LLThread::staticRun() Exiting: " << threadp->mName << LL_ENDL; - - - delete mRecorder; - mRecorder = NULL; - - // We're done with the run function, this thread is done executing now. - //NB: we are using this flag to sync across threads...we really need memory barriers here - // Todo: add LLMutex per thread instead of flag? - // We are using "while (mStatus != STOPPED) {ms_sleep();}" everywhere. - mStatus = STOPPED; -} - -LLThread::LLThread(const std::string& name, apr_pool_t *poolp) : - mPaused(FALSE), - mName(name), - mThreadp(NULL), - mStatus(STOPPED), - mRecorder(NULL) -{ - mRunCondition = new LLCondition(); - mDataLock = new LLMutex(); - mLocalAPRFilePoolp = NULL ; -} - - -LLThread::~LLThread() -{ - shutdown(); - - if (isCrashed()) - { - LL_WARNS("THREAD") << "Destroying crashed thread named '" << mName << "'" << LL_ENDL; - } - - if(mLocalAPRFilePoolp) - { - delete mLocalAPRFilePoolp ; - mLocalAPRFilePoolp = NULL ; - } -} - -void LLThread::shutdown() -{ - if (isCrashed()) - { - LL_WARNS("THREAD") << "Shutting down crashed thread named '" << mName << "'" << LL_ENDL; - } - - // Warning! If you somehow call the thread destructor from itself, - // the thread will die in an unclean fashion! - if (mThreadp) - { - if (!isStopped()) - { - // The thread isn't already stopped - // First, set the flag that indicates that we're ready to die - setQuitting(); - - //LL_INFOS() << "LLThread::~LLThread() Killing thread " << mName << " Status: " << mStatus << LL_ENDL; - // Now wait a bit for the thread to exit - // It's unclear whether I should even bother doing this - this destructor - // should never get called unless we're already stopped, really... - S32 counter = 0; - const S32 MAX_WAIT = 600; - while (counter < MAX_WAIT) - { - if (isStopped()) - { - break; - } - // Sleep for a tenth of a second - ms_sleep(100); - yield(); - counter++; - } - } - - if (!isStopped()) - { - // This thread just wouldn't stop, even though we gave it time - //LL_WARNS() << "LLThread::~LLThread() exiting thread before clean exit!" << LL_ENDL; - // Put a stake in its heart. (A very hostile method to force a thread to quit) -#if LL_WINDOWS - TerminateThread(mNativeHandle, 0); -#else - pthread_cancel(mNativeHandle); -#endif - - delete mRecorder; - mRecorder = NULL; - mStatus = STOPPED; - return; - } - mThreadp = NULL; - } - - delete mRunCondition; - mRunCondition = NULL; - - delete mDataLock; - mDataLock = NULL; - - if (mRecorder) - { - // missed chance to properly shut down recorder (needs to be done in thread context) - // probably due to abnormal thread termination - // so just leak it and remove it from parent - LLTrace::get_master_thread_recorder()->removeChildRecorder(mRecorder); - } -} - - -void LLThread::start() -{ - llassert(isStopped()); - - // Set thread state to running - mStatus = RUNNING; - - try - { - mThreadp = new std::thread(std::bind(&LLThread::threadRun, this)); - mNativeHandle = mThreadp->native_handle(); - } - catch (std::system_error& ex) - { - mStatus = STOPPED; - LL_WARNS() << "failed to start thread " << mName << " " << ex.what() << LL_ENDL; - } - -} - -//============================================================================ -// Called from MAIN THREAD. - -// Request that the thread pause/resume. -// The thread will pause when (and if) it calls checkPause() -void LLThread::pause() -{ - if (!mPaused) - { - // this will cause the thread to stop execution as soon as checkPause() is called - mPaused = 1; // Does not need to be atomic since this is only set/unset from the main thread - } -} - -void LLThread::unpause() -{ - if (mPaused) - { - mPaused = 0; - } - - wake(); // wake up the thread if necessary -} - -// virtual predicate function -- returns true if the thread should wake up, false if it should sleep. -bool LLThread::runCondition(void) -{ - // by default, always run. Handling of pause/unpause is done regardless of this function's result. - return true; -} - -//============================================================================ -// Called from run() (CHILD THREAD). -// Stop thread execution if requested until unpaused. -void LLThread::checkPause() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mDataLock->lock(); - - // This is in a while loop because the pthread API allows for spurious wakeups. - while(shouldSleep()) - { - mDataLock->unlock(); - mRunCondition->wait(); // unlocks mRunCondition - mDataLock->lock(); - // mRunCondition is locked when the thread wakes up - } - - mDataLock->unlock(); -} - -//============================================================================ - -void LLThread::setQuitting() -{ - mDataLock->lock(); - if (mStatus == RUNNING) - { - mStatus = QUITTING; - } - // It's only safe to remove mRunCondition if all locked threads were notified - mRunCondition->broadcast(); - mDataLock->unlock(); -} - -// static -LLThread::id_t LLThread::currentID() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - return std::this_thread::get_id(); -} - -// static -void LLThread::yield() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - std::this_thread::yield(); -} - -void LLThread::wake() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mDataLock->lock(); - if(!shouldSleep()) - { - mRunCondition->signal(); - } - mDataLock->unlock(); -} - -void LLThread::wakeLocked() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - if(!shouldSleep()) - { - mRunCondition->signal(); - } -} - -void LLThread::lockData() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mDataLock->lock(); -} - -void LLThread::unlockData() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD - mDataLock->unlock(); -} - -//============================================================================ - -//---------------------------------------------------------------------------- - -//static -LLMutex* LLThreadSafeRefCount::sMutex = 0; - -//static -void LLThreadSafeRefCount::initThreadSafeRefCount() -{ - if (!sMutex) - { - sMutex = new LLMutex(); - } -} - -//static -void LLThreadSafeRefCount::cleanupThreadSafeRefCount() -{ - delete sMutex; - sMutex = NULL; -} - - -//---------------------------------------------------------------------------- - -LLThreadSafeRefCount::LLThreadSafeRefCount() : - mRef(0) -{ -} - -LLThreadSafeRefCount::LLThreadSafeRefCount(const LLThreadSafeRefCount& src) -{ - mRef = 0; -} - -LLThreadSafeRefCount::~LLThreadSafeRefCount() -{ - if (mRef != 0) - { - LL_ERRS() << "deleting referenced object mRef = " << mRef << LL_ENDL; - } -} - -//============================================================================ - -LLResponder::~LLResponder() -{ -} - -//============================================================================ +/**
+ * @file llthread.cpp
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010-2013, 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 "linden_common.h"
+#include "llapr.h"
+
+#include "apr_portable.h"
+
+#include "llthread.h"
+#include "llmutex.h"
+
+#include "lltimer.h"
+#include "lltrace.h"
+#include "lltracethreadrecorder.h"
+#include "llexception.h"
+
+#if LL_LINUX
+#include <sched.h>
+#endif
+
+
+#ifdef LL_WINDOWS
+
+const DWORD MS_VC_EXCEPTION=0x406D1388;
+
+#pragma pack(push,8)
+typedef struct tagTHREADNAME_INFO
+{
+ DWORD dwType; // Must be 0x1000.
+ LPCSTR szName; // Pointer to name (in user addr space).
+ DWORD dwThreadID; // Thread ID (-1=caller thread).
+ DWORD dwFlags; // Reserved for future use, must be zero.
+} THREADNAME_INFO;
+#pragma pack(pop)
+
+void set_thread_name( DWORD dwThreadID, const char* threadName)
+{
+ THREADNAME_INFO info;
+ info.dwType = 0x1000;
+ info.szName = threadName;
+ info.dwThreadID = dwThreadID;
+ info.dwFlags = 0;
+
+ __try
+ {
+ ::RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info );
+ }
+ __except(EXCEPTION_CONTINUE_EXECUTION)
+ {
+ }
+}
+#endif
+
+
+//----------------------------------------------------------------------------
+// Usage:
+// void run_func(LLThread* thread)
+// {
+// }
+// LLThread* thread = new LLThread();
+// thread->run(run_func);
+// ...
+// thread->setQuitting();
+// while(!timeout)
+// {
+// if (thread->isStopped())
+// {
+// delete thread;
+// break;
+// }
+// }
+//
+//----------------------------------------------------------------------------
+namespace
+{
+
+ LLThread::id_t main_thread()
+ {
+ // Using a function-static variable to identify the main thread
+ // requires that control reach here from the main thread before it
+ // reaches here from any other thread. We simply trust that whichever
+ // thread gets here first is the main thread.
+ static LLThread::id_t s_thread_id = LLThread::currentID();
+ return s_thread_id;
+ }
+
+} // anonymous namespace
+
+LL_COMMON_API bool on_main_thread()
+{
+ return (LLThread::currentID() == main_thread());
+}
+
+LL_COMMON_API bool assert_main_thread()
+{
+ auto curr = LLThread::currentID();
+ auto main = main_thread();
+ if (curr == main)
+ return true;
+
+ LL_WARNS() << "Illegal execution from thread id " << curr
+ << " outside main thread " << main << LL_ENDL;
+ return false;
+}
+
+// this function has become moot
+void LLThread::registerThreadID() {}
+
+//
+// Handed to the APR thread creation function
+//
+void LLThread::threadRun()
+{
+#ifdef LL_WINDOWS
+ set_thread_name(-1, mName.c_str());
+
+#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32)
+ HANDLE hThread = GetCurrentThread();
+ if (hThread)
+ {
+ SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE);
+ }
+#endif
+
+#endif
+
+ LL_PROFILER_SET_THREAD_NAME( mName.c_str() );
+
+ // this is the first point at which we're actually running in the new thread
+ mID = currentID();
+
+ // for now, hard code all LLThreads to report to single master thread recorder, which is known to be running on main thread
+ mRecorder = new LLTrace::ThreadRecorder(*LLTrace::get_master_thread_recorder());
+
+ // Run the user supplied function
+ do
+ {
+ try
+ {
+ run();
+ }
+ catch (const LLContinueError &e)
+ {
+ LL_WARNS("THREAD") << "ContinueException on thread '" << mName <<
+ "' reentering run(). Error what is: '" << e.what() << "'" << LL_ENDL;
+ //output possible call stacks to log file.
+ LLError::LLCallStacks::print();
+
+ LOG_UNHANDLED_EXCEPTION("LLThread");
+ continue;
+ }
+ break;
+
+ } while (true);
+
+ //LL_INFOS() << "LLThread::staticRun() Exiting: " << threadp->mName << LL_ENDL;
+
+
+ delete mRecorder;
+ mRecorder = NULL;
+
+ // We're done with the run function, this thread is done executing now.
+ //NB: we are using this flag to sync across threads...we really need memory barriers here
+ // Todo: add LLMutex per thread instead of flag?
+ // We are using "while (mStatus != STOPPED) {ms_sleep();}" everywhere.
+ mStatus = STOPPED;
+}
+
+LLThread::LLThread(const std::string& name, apr_pool_t *poolp) :
+ mPaused(false),
+ mName(name),
+ mThreadp(NULL),
+ mStatus(STOPPED),
+ mRecorder(NULL)
+{
+ mRunCondition = new LLCondition();
+ mDataLock = new LLMutex();
+ mLocalAPRFilePoolp = NULL ;
+}
+
+
+LLThread::~LLThread()
+{
+ shutdown();
+
+ if (isCrashed())
+ {
+ LL_WARNS("THREAD") << "Destroying crashed thread named '" << mName << "'" << LL_ENDL;
+ }
+
+ if(mLocalAPRFilePoolp)
+ {
+ delete mLocalAPRFilePoolp ;
+ mLocalAPRFilePoolp = NULL ;
+ }
+}
+
+void LLThread::shutdown()
+{
+ if (isCrashed())
+ {
+ LL_WARNS("THREAD") << "Shutting down crashed thread named '" << mName << "'" << LL_ENDL;
+ }
+
+ // Warning! If you somehow call the thread destructor from itself,
+ // the thread will die in an unclean fashion!
+ if (mThreadp)
+ {
+ if (!isStopped())
+ {
+ // The thread isn't already stopped
+ // First, set the flag that indicates that we're ready to die
+ setQuitting();
+
+ //LL_INFOS() << "LLThread::~LLThread() Killing thread " << mName << " Status: " << mStatus << LL_ENDL;
+ // Now wait a bit for the thread to exit
+ // It's unclear whether I should even bother doing this - this destructor
+ // should never get called unless we're already stopped, really...
+ S32 counter = 0;
+ const S32 MAX_WAIT = 600;
+ while (counter < MAX_WAIT)
+ {
+ if (isStopped())
+ {
+ break;
+ }
+ // Sleep for a tenth of a second
+ ms_sleep(100);
+ yield();
+ counter++;
+ }
+ }
+
+ if (!isStopped())
+ {
+ // This thread just wouldn't stop, even though we gave it time
+ //LL_WARNS() << "LLThread::~LLThread() exiting thread before clean exit!" << LL_ENDL;
+ // Put a stake in its heart. (A very hostile method to force a thread to quit)
+#if LL_WINDOWS
+ TerminateThread(mNativeHandle, 0);
+#else
+ pthread_cancel(mNativeHandle);
+#endif
+
+ delete mRecorder;
+ mRecorder = NULL;
+ mStatus = STOPPED;
+ return;
+ }
+ mThreadp = NULL;
+ }
+
+ delete mRunCondition;
+ mRunCondition = NULL;
+
+ delete mDataLock;
+ mDataLock = NULL;
+
+ if (mRecorder)
+ {
+ // missed chance to properly shut down recorder (needs to be done in thread context)
+ // probably due to abnormal thread termination
+ // so just leak it and remove it from parent
+ LLTrace::get_master_thread_recorder()->removeChildRecorder(mRecorder);
+ }
+}
+
+
+void LLThread::start()
+{
+ llassert(isStopped());
+
+ // Set thread state to running
+ mStatus = RUNNING;
+
+ try
+ {
+ mThreadp = new std::thread(std::bind(&LLThread::threadRun, this));
+ mNativeHandle = mThreadp->native_handle();
+ }
+ catch (std::system_error& ex)
+ {
+ mStatus = STOPPED;
+ LL_WARNS() << "failed to start thread " << mName << " " << ex.what() << LL_ENDL;
+ }
+
+}
+
+//============================================================================
+// Called from MAIN THREAD.
+
+// Request that the thread pause/resume.
+// The thread will pause when (and if) it calls checkPause()
+void LLThread::pause()
+{
+ if (!mPaused)
+ {
+ // this will cause the thread to stop execution as soon as checkPause() is called
+ mPaused = 1; // Does not need to be atomic since this is only set/unset from the main thread
+ }
+}
+
+void LLThread::unpause()
+{
+ if (mPaused)
+ {
+ mPaused = 0;
+ }
+
+ wake(); // wake up the thread if necessary
+}
+
+// virtual predicate function -- returns true if the thread should wake up, false if it should sleep.
+bool LLThread::runCondition(void)
+{
+ // by default, always run. Handling of pause/unpause is done regardless of this function's result.
+ return true;
+}
+
+//============================================================================
+// Called from run() (CHILD THREAD).
+// Stop thread execution if requested until unpaused.
+void LLThread::checkPause()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ mDataLock->lock();
+
+ // This is in a while loop because the pthread API allows for spurious wakeups.
+ while(shouldSleep())
+ {
+ mDataLock->unlock();
+ mRunCondition->wait(); // unlocks mRunCondition
+ mDataLock->lock();
+ // mRunCondition is locked when the thread wakes up
+ }
+
+ mDataLock->unlock();
+}
+
+//============================================================================
+
+void LLThread::setQuitting()
+{
+ mDataLock->lock();
+ if (mStatus == RUNNING)
+ {
+ mStatus = QUITTING;
+ }
+ // It's only safe to remove mRunCondition if all locked threads were notified
+ mRunCondition->broadcast();
+ mDataLock->unlock();
+}
+
+// static
+LLThread::id_t LLThread::currentID()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ return std::this_thread::get_id();
+}
+
+// static
+void LLThread::yield()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ std::this_thread::yield();
+}
+
+void LLThread::wake()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ mDataLock->lock();
+ if(!shouldSleep())
+ {
+ mRunCondition->signal();
+ }
+ mDataLock->unlock();
+}
+
+void LLThread::wakeLocked()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ if(!shouldSleep())
+ {
+ mRunCondition->signal();
+ }
+}
+
+void LLThread::lockData()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ mDataLock->lock();
+}
+
+void LLThread::unlockData()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
+ mDataLock->unlock();
+}
+
+//============================================================================
+
+//----------------------------------------------------------------------------
+
+//static
+LLMutex* LLThreadSafeRefCount::sMutex = 0;
+
+//static
+void LLThreadSafeRefCount::initThreadSafeRefCount()
+{
+ if (!sMutex)
+ {
+ sMutex = new LLMutex();
+ }
+}
+
+//static
+void LLThreadSafeRefCount::cleanupThreadSafeRefCount()
+{
+ delete sMutex;
+ sMutex = NULL;
+}
+
+
+//----------------------------------------------------------------------------
+
+LLThreadSafeRefCount::LLThreadSafeRefCount() :
+ mRef(0)
+{
+}
+
+LLThreadSafeRefCount::LLThreadSafeRefCount(const LLThreadSafeRefCount& src)
+{
+ mRef = 0;
+}
+
+LLThreadSafeRefCount::~LLThreadSafeRefCount()
+{
+ if (mRef != 0)
+ {
+ LL_ERRS() << "deleting referenced object mRef = " << mRef << LL_ENDL;
+ }
+}
+
+//============================================================================
+
+LLResponder::~LLResponder()
+{
+}
+
+//============================================================================
diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index e21e6265ea..cf2ae00cd7 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -1,25 +1,25 @@ -/** +/** * @file llthread.h * @brief Base classes for thread, mutex and condition handling. * * $LicenseInfo:firstyear=2004&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010-2013, 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$ */ @@ -55,10 +55,10 @@ public: LLThread(const std::string& name, apr_pool_t *poolp = NULL); virtual ~LLThread(); // Warning! You almost NEVER want to destroy a thread unless it's in the STOPPED state. virtual void shutdown(); // stops the thread - + bool isQuitting() const { return (QUITTING == mStatus); } bool isStopped() const { return (STOPPED == mStatus) || (CRASHED == mStatus); } - bool isCrashed() const { return (CRASHED == mStatus); } + bool isCrashed() const { return (CRASHED == mStatus); } static id_t currentID(); // Return ID of current thread static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure. @@ -68,7 +68,7 @@ public: // Called from MAIN THREAD. void pause(); void unpause(); - bool isPaused() { return isStopped() || mPaused == TRUE; } + bool isPaused() { return isStopped() || mPaused; } // Cause the thread to wake up and check its condition void wake(); @@ -90,11 +90,11 @@ public: // internal state used by LLMutex. You must call this once early // in the running thread to prevent collisions with the main thread. static void registerThreadID(); - + private: bool mPaused; std::thread::native_handle_type mNativeHandle; // for termination in case of issues - + // static function passed to APR thread creation routine void threadRun(); @@ -111,21 +111,21 @@ protected: //a local apr_pool for APRFile operations in this thread. If it exists, LLAPRFile::sAPRFilePoolp should not be used. //Note: this pool is used by APRFile ONLY, do NOT use it for any other purposes. // otherwise it will cause severe memory leaking!!! --bao - LLVolatileAPRPool *mLocalAPRFilePoolp ; + LLVolatileAPRPool *mLocalAPRFilePoolp ; void setQuitting(); - + // virtual function overridden by subclass -- this will be called when the thread runs - virtual void run(void) = 0; - + virtual void run(void) = 0; + // virtual predicate function -- returns true if the thread should wake up, false if it should sleep. virtual bool runCondition(void); // Lock/Unlock Run Condition -- use around modification of any variable used in runCondition() void lockData(); void unlockData(); - - // This is the predicate that decides whether the thread should sleep. + + // This is the predicate that decides whether the thread should sleep. // It should only be called with mDataLock locked, since the virtual runCondition() function may need to access // data structures that are thread-unsafe. bool shouldSleep(void) { return (mStatus == RUNNING) && (isPaused() || (!runCondition())); } diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index 6f74bf3fc8..8a8451b927 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -1,609 +1,608 @@ -/** - * @file lltimer.cpp - * @brief Cross-platform objects for doing timing - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "lltimer.h" - -#include "u64.h" - -#include <chrono> -#include <thread> - -#if LL_WINDOWS -# include "llwin32headerslean.h" -#elif LL_LINUX || LL_DARWIN -# include <errno.h> -# include <sys/time.h> -#else -# error "architecture not supported" -#endif - -// -// Locally used constants -// -const U64 SEC_TO_MICROSEC_U64 = 1000000; - -//--------------------------------------------------------------------------- -// Globals and statics -//--------------------------------------------------------------------------- - -S32 gUTCOffset = 0; // viewer's offset from server UTC, in seconds -LLTimer* LLTimer::sTimer = NULL; - - -// -// Forward declarations -// - - -//--------------------------------------------------------------------------- -// Implementation -//--------------------------------------------------------------------------- - -#if LL_WINDOWS - - -#if 0 -void ms_sleep(U32 ms) -{ - LL_PROFILE_ZONE_SCOPED; - using TimePoint = std::chrono::steady_clock::time_point; - auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms); - while (TimePoint::clock::now() < resume_time) - { - std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long - } -} - -U32 micro_sleep(U64 us, U32 max_yields) -{ - // max_yields is unused; just fiddle with it to avoid warnings. - max_yields = 0; - ms_sleep((U32)(us / 1000)); - return 0; -} - -#else - -U32 micro_sleep(U64 us, U32 max_yields) -{ - LL_PROFILE_ZONE_SCOPED -#if 0 - LARGE_INTEGER ft; - ft.QuadPart = -static_cast<S64>(us * 10); // '-' using relative time - - HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL); - SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); - WaitForSingleObject(timer, INFINITE); - CloseHandle(timer); -#else - Sleep(us / 1000); -#endif - - return 0; -} - -void ms_sleep(U32 ms) -{ - LL_PROFILE_ZONE_SCOPED - micro_sleep(ms * 1000, 0); -} - -#endif - -#elif LL_LINUX || LL_DARWIN -static void _sleep_loop(struct timespec& thiswait) -{ - struct timespec nextwait; - bool sleep_more = false; - - do { - int result = nanosleep(&thiswait, &nextwait); - - // check if sleep was interrupted by a signal; unslept - // remainder was written back into 't' and we just nanosleep - // again. - sleep_more = (result == -1 && EINTR == errno); - - if (sleep_more) - { - if ( nextwait.tv_sec > thiswait.tv_sec || - (nextwait.tv_sec == thiswait.tv_sec && - nextwait.tv_nsec >= thiswait.tv_nsec) ) - { - // if the remaining time isn't actually going - // down then we're being shafted by low clock - // resolution - manually massage the sleep time - // downward. - if (nextwait.tv_nsec > 1000000) { - // lose 1ms - nextwait.tv_nsec -= 1000000; - } else { - if (nextwait.tv_sec == 0) { - // already so close to finished - sleep_more = false; - } else { - // lose up to 1ms - nextwait.tv_nsec = 0; - } - } - } - thiswait = nextwait; - } - } while (sleep_more); -} - -U32 micro_sleep(U64 us, U32 max_yields) -{ - U64 start = get_clock_count(); - // This is kernel dependent. Currently, our kernel generates software clock - // interrupts at 250 Hz (every 4,000 microseconds). - const S64 KERNEL_SLEEP_INTERVAL_US = 4000; - - // Use signed arithmetic to discover whether a sleep is even necessary. If - // either 'us' or KERNEL_SLEEP_INTERVAL_US is unsigned, the compiler - // promotes the difference to unsigned. If 'us' is less than half - // KERNEL_SLEEP_INTERVAL_US, the unsigned difference will be hugely - // positive, resulting in a crazy long wait. - auto num_sleep_intervals = (S64(us) - (KERNEL_SLEEP_INTERVAL_US >> 1)) / KERNEL_SLEEP_INTERVAL_US; - if (num_sleep_intervals > 0) - { - U64 sleep_time = (num_sleep_intervals * KERNEL_SLEEP_INTERVAL_US) - (KERNEL_SLEEP_INTERVAL_US >> 1); - struct timespec thiswait; - thiswait.tv_sec = sleep_time / 1000000; - thiswait.tv_nsec = (sleep_time % 1000000) * 1000l; - _sleep_loop(thiswait); - } - - U64 current_clock = get_clock_count(); - U32 yields = 0; - while ( (yields < max_yields) - && (current_clock - start < us) ) - { - sched_yield(); - ++yields; - current_clock = get_clock_count(); - } - return yields; -} - -void ms_sleep(U32 ms) -{ - long mslong = ms; // tv_nsec is a long - struct timespec thiswait; - thiswait.tv_sec = ms / 1000; - thiswait.tv_nsec = (mslong % 1000) * 1000000l; - _sleep_loop(thiswait); -} -#else -# error "architecture not supported" -#endif - -// -// CPU clock/other clock frequency and count functions -// - -#if LL_WINDOWS -U64 get_clock_count() -{ - static bool firstTime = true; - static U64 offset; - // ensures that callers to this function never have to deal with wrap - - // QueryPerformanceCounter implementation - LARGE_INTEGER clock_count; - QueryPerformanceCounter(&clock_count); - if (firstTime) { - offset = clock_count.QuadPart; - firstTime = false; - } - return clock_count.QuadPart - offset; -} - -F64 calc_clock_frequency() -{ - __int64 freq; - QueryPerformanceFrequency((LARGE_INTEGER *) &freq); - return (F64)freq; -} -#endif // LL_WINDOWS - - -#if LL_LINUX || LL_DARWIN -// Both Linux and Mac use gettimeofday for accurate time -F64 calc_clock_frequency() -{ - return 1000000.0; // microseconds, so 1 MHz. -} - -U64 get_clock_count() -{ - // Linux clocks are in microseconds - struct timeval tv; - gettimeofday(&tv, NULL); - return tv.tv_sec*SEC_TO_MICROSEC_U64 + tv.tv_usec; -} -#endif - - -TimerInfo::TimerInfo() -: mClockFrequency(0.0), - mTotalTimeClockCount(0), - mLastTotalTimeClockCount(0) -{} - -void TimerInfo::update() -{ - mClockFrequency = calc_clock_frequency(); - mClockFrequencyInv = 1.0/mClockFrequency; - mClocksToMicroseconds = mClockFrequencyInv; -} - -TimerInfo& get_timer_info() -{ - static TimerInfo sTimerInfo; - return sTimerInfo; -} - -/////////////////////////////////////////////////////////////////////////////// - -// returns a U64 number that represents the number of -// microseconds since the Unix epoch - Jan 1, 1970 -U64MicrosecondsImplicit totalTime() -{ - U64 current_clock_count = get_clock_count(); - if (!get_timer_info().mTotalTimeClockCount || get_timer_info().mClocksToMicroseconds.value() == 0) - { - get_timer_info().update(); - get_timer_info().mTotalTimeClockCount = current_clock_count; - -#if LL_WINDOWS - // Sync us up with local time (even though we PROBABLY don't need to, this is how it was implemented) - // Unix platforms use gettimeofday so they are synced, although this probably isn't a good assumption to - // make in the future. - - get_timer_info().mTotalTimeClockCount = (U64)(time(NULL) * get_timer_info().mClockFrequency); -#endif - - // Update the last clock count - get_timer_info().mLastTotalTimeClockCount = current_clock_count; - } - else - { - if (current_clock_count >= get_timer_info().mLastTotalTimeClockCount) - { - // No wrapping, we're all okay. - get_timer_info().mTotalTimeClockCount += current_clock_count - get_timer_info().mLastTotalTimeClockCount; - } - else - { - // We've wrapped. Compensate correctly - get_timer_info().mTotalTimeClockCount += (0xFFFFFFFFFFFFFFFFULL - get_timer_info().mLastTotalTimeClockCount) + current_clock_count; - } - - // Update the last clock count - get_timer_info().mLastTotalTimeClockCount = current_clock_count; - } - - // Return the total clock tick count in microseconds. - U64Microseconds time(get_timer_info().mTotalTimeClockCount*get_timer_info().mClocksToMicroseconds); - return time; -} - - -/////////////////////////////////////////////////////////////////////////////// - -LLTimer::LLTimer() -{ - if (!get_timer_info().mClockFrequency) - { - get_timer_info().update(); - } - - mStarted = TRUE; - reset(); -} - -LLTimer::~LLTimer() -{} - -// static -void LLTimer::initClass() -{ - if (!sTimer) sTimer = new LLTimer; -} - -// static -void LLTimer::cleanupClass() -{ - delete sTimer; sTimer = NULL; -} - -// static -U64MicrosecondsImplicit LLTimer::getTotalTime() -{ - // simply call into the implementation function. - U64MicrosecondsImplicit total_time = totalTime(); - return total_time; -} - -// static -F64SecondsImplicit LLTimer::getTotalSeconds() -{ - return F64Microseconds(U64_to_F64(getTotalTime())); -} - -void LLTimer::reset() -{ - mLastClockCount = get_clock_count(); - mExpirationTicks = 0; -} - -/////////////////////////////////////////////////////////////////////////////// - -U64 LLTimer::getCurrentClockCount() -{ - return get_clock_count(); -} - -/////////////////////////////////////////////////////////////////////////////// - -void LLTimer::setLastClockCount(U64 current_count) -{ - mLastClockCount = current_count; -} - -/////////////////////////////////////////////////////////////////////////////// - -static -U64 getElapsedTimeAndUpdate(U64& lastClockCount) -{ - U64 current_clock_count = get_clock_count(); - U64 result; - - if (current_clock_count >= lastClockCount) - { - result = current_clock_count - lastClockCount; - } - else - { - // time has gone backward - result = 0; - } - - lastClockCount = current_clock_count; - - return result; -} - - -F64SecondsImplicit LLTimer::getElapsedTimeF64() const -{ - U64 last = mLastClockCount; - return (F64)getElapsedTimeAndUpdate(last) * get_timer_info().mClockFrequencyInv; -} - -F32SecondsImplicit LLTimer::getElapsedTimeF32() const -{ - return (F32)getElapsedTimeF64(); -} - -F64SecondsImplicit LLTimer::getElapsedTimeAndResetF64() -{ - return (F64)getElapsedTimeAndUpdate(mLastClockCount) * get_timer_info().mClockFrequencyInv; -} - -F32SecondsImplicit LLTimer::getElapsedTimeAndResetF32() -{ - return (F32)getElapsedTimeAndResetF64(); -} - -/////////////////////////////////////////////////////////////////////////////// - -void LLTimer::setTimerExpirySec(F32SecondsImplicit expiration) -{ - mExpirationTicks = get_clock_count() - + (U64)((F32)(expiration * get_timer_info().mClockFrequency.value())); -} - -F32SecondsImplicit LLTimer::getRemainingTimeF32() const -{ - U64 cur_ticks = get_clock_count(); - if (cur_ticks > mExpirationTicks) - { - return 0.0f; - } - return F32((mExpirationTicks - cur_ticks) * get_timer_info().mClockFrequencyInv); -} - - -BOOL LLTimer::checkExpirationAndReset(F32 expiration) -{ - U64 cur_ticks = get_clock_count(); - if (cur_ticks < mExpirationTicks) - { - return FALSE; - } - - mExpirationTicks = cur_ticks - + (U64)((F32)(expiration * get_timer_info().mClockFrequency)); - return TRUE; -} - - -BOOL LLTimer::hasExpired() const -{ - return (get_clock_count() >= mExpirationTicks) - ? TRUE : FALSE; -} - -/////////////////////////////////////////////////////////////////////////////// - -BOOL LLTimer::knownBadTimer() -{ - BOOL failed = FALSE; - -#if LL_WINDOWS - WCHAR bad_pci_list[][10] = {L"1039:0530", - L"1039:0620", - L"10B9:0533", - L"10B9:1533", - L"1106:0596", - L"1106:0686", - L"1166:004F", - L"1166:0050", - L"8086:7110", - L"\0" - }; - - HKEY hKey = NULL; - LONG nResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SYSTEM\\CurrentControlSet\\Enum\\PCI", 0, - KEY_EXECUTE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey); - - WCHAR name[1024]; - DWORD name_len = 1024; - FILETIME scrap; - - S32 key_num = 0; - WCHAR pci_id[10]; - - wcscpy(pci_id, L"0000:0000"); /*Flawfinder: ignore*/ - - while (nResult == ERROR_SUCCESS) - { - nResult = ::RegEnumKeyEx(hKey, key_num++, name, &name_len, NULL, NULL, NULL, &scrap); - - if (nResult == ERROR_SUCCESS) - { - memcpy(&pci_id[0],&name[4],4); /* Flawfinder: ignore */ - memcpy(&pci_id[5],&name[13],4); /* Flawfinder: ignore */ - - for (S32 check = 0; bad_pci_list[check][0]; check++) - { - if (!wcscmp(pci_id, bad_pci_list[check])) - { -// LL_WARNS() << "unreliable PCI chipset found!! " << pci_id << endl; - failed = TRUE; - break; - } - } -// llinfo << "PCI chipset found: " << pci_id << endl; - name_len = 1024; - } - } -#endif - return(failed); -} - -/////////////////////////////////////////////////////////////////////////////// -// -// NON-MEMBER FUNCTIONS -// -/////////////////////////////////////////////////////////////////////////////// - -time_t time_corrected() -{ - return time(NULL) + gUTCOffset; -} - - -// Is the current computer (in its current time zone) -// observing daylight savings time? -BOOL is_daylight_savings() -{ - time_t now = time(NULL); - - // Internal buffer to local server time - struct tm* internal_time = localtime(&now); - - // tm_isdst > 0 => daylight savings - // tm_isdst = 0 => not daylight savings - // tm_isdst < 0 => can't tell - return (internal_time->tm_isdst > 0); -} - - -struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time) -{ - S32Hours pacific_offset_hours; - if (pacific_daylight_time) - { - pacific_offset_hours = S32Hours(7); - } - else - { - pacific_offset_hours = S32Hours(8); - } - - // We subtract off the PST/PDT offset _before_ getting - // "UTC" time, because this will handle wrapping around - // for 5 AM UTC -> 10 PM PDT of the previous day. - utc_time -= S32SecondsImplicit(pacific_offset_hours); - - // Internal buffer to PST/PDT (see above) - struct tm* internal_time = gmtime(&utc_time); - - /* - // Don't do this, this won't correctly tell you if daylight savings is active in CA or not. - if (pacific_daylight_time) - { - internal_time->tm_isdst = 1; - } - */ - - return internal_time; -} - - -void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring) -{ - U64 hours; - U64 minutes; - U64 seconds; - U64 frames; - U64 subframes; - - hours = current_time / (U64)3600000000ul; - minutes = current_time / (U64)60000000; - minutes %= 60; - seconds = current_time / (U64)1000000; - seconds %= 60; - frames = current_time / (U64)41667; - frames %= 24; - subframes = current_time / (U64)42; - subframes %= 100; - - tcstring = llformat("%3.3d:%2.2d:%2.2d:%2.2d.%2.2d",(int)hours,(int)minutes,(int)seconds,(int)frames,(int)subframes); -} - - -void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring) -{ - microsecondsToTimecodeString(current_time, tcstring); -} - - +/**
+ * @file lltimer.cpp
+ * @brief Cross-platform objects for doing timing
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "lltimer.h"
+
+#include "u64.h"
+
+#include <chrono>
+#include <thread>
+
+#if LL_WINDOWS
+# include "llwin32headerslean.h"
+#elif LL_LINUX || LL_DARWIN
+# include <errno.h>
+# include <sys/time.h>
+#else
+# error "architecture not supported"
+#endif
+
+//
+// Locally used constants
+//
+const U64 SEC_TO_MICROSEC_U64 = 1000000;
+
+//---------------------------------------------------------------------------
+// Globals and statics
+//---------------------------------------------------------------------------
+
+S32 gUTCOffset = 0; // viewer's offset from server UTC, in seconds
+LLTimer* LLTimer::sTimer = NULL;
+
+
+//
+// Forward declarations
+//
+
+
+//---------------------------------------------------------------------------
+// Implementation
+//---------------------------------------------------------------------------
+
+#if LL_WINDOWS
+
+
+#if 0
+void ms_sleep(U32 ms)
+{
+ LL_PROFILE_ZONE_SCOPED;
+ using TimePoint = std::chrono::steady_clock::time_point;
+ auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms);
+ while (TimePoint::clock::now() < resume_time)
+ {
+ std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long
+ }
+}
+
+U32 micro_sleep(U64 us, U32 max_yields)
+{
+ // max_yields is unused; just fiddle with it to avoid warnings.
+ max_yields = 0;
+ ms_sleep((U32)(us / 1000));
+ return 0;
+}
+
+#else
+
+U32 micro_sleep(U64 us, U32 max_yields)
+{
+ LL_PROFILE_ZONE_SCOPED
+#if 0
+ LARGE_INTEGER ft;
+ ft.QuadPart = -static_cast<S64>(us * 10); // '-' using relative time
+
+ HANDLE timer = CreateWaitableTimer(NULL, true, NULL);
+ SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
+ WaitForSingleObject(timer, INFINITE);
+ CloseHandle(timer);
+#else
+ Sleep(us / 1000);
+#endif
+
+ return 0;
+}
+
+void ms_sleep(U32 ms)
+{
+ LL_PROFILE_ZONE_SCOPED
+ micro_sleep(ms * 1000, 0);
+}
+
+#endif
+
+#elif LL_LINUX || LL_DARWIN
+static void _sleep_loop(struct timespec& thiswait)
+{
+ struct timespec nextwait;
+ bool sleep_more = false;
+
+ do {
+ int result = nanosleep(&thiswait, &nextwait);
+
+ // check if sleep was interrupted by a signal; unslept
+ // remainder was written back into 't' and we just nanosleep
+ // again.
+ sleep_more = (result == -1 && EINTR == errno);
+
+ if (sleep_more)
+ {
+ if ( nextwait.tv_sec > thiswait.tv_sec ||
+ (nextwait.tv_sec == thiswait.tv_sec &&
+ nextwait.tv_nsec >= thiswait.tv_nsec) )
+ {
+ // if the remaining time isn't actually going
+ // down then we're being shafted by low clock
+ // resolution - manually massage the sleep time
+ // downward.
+ if (nextwait.tv_nsec > 1000000) {
+ // lose 1ms
+ nextwait.tv_nsec -= 1000000;
+ } else {
+ if (nextwait.tv_sec == 0) {
+ // already so close to finished
+ sleep_more = false;
+ } else {
+ // lose up to 1ms
+ nextwait.tv_nsec = 0;
+ }
+ }
+ }
+ thiswait = nextwait;
+ }
+ } while (sleep_more);
+}
+
+U32 micro_sleep(U64 us, U32 max_yields)
+{
+ U64 start = get_clock_count();
+ // This is kernel dependent. Currently, our kernel generates software clock
+ // interrupts at 250 Hz (every 4,000 microseconds).
+ const S64 KERNEL_SLEEP_INTERVAL_US = 4000;
+
+ // Use signed arithmetic to discover whether a sleep is even necessary. If
+ // either 'us' or KERNEL_SLEEP_INTERVAL_US is unsigned, the compiler
+ // promotes the difference to unsigned. If 'us' is less than half
+ // KERNEL_SLEEP_INTERVAL_US, the unsigned difference will be hugely
+ // positive, resulting in a crazy long wait.
+ auto num_sleep_intervals = (S64(us) - (KERNEL_SLEEP_INTERVAL_US >> 1)) / KERNEL_SLEEP_INTERVAL_US;
+ if (num_sleep_intervals > 0)
+ {
+ U64 sleep_time = (num_sleep_intervals * KERNEL_SLEEP_INTERVAL_US) - (KERNEL_SLEEP_INTERVAL_US >> 1);
+ struct timespec thiswait;
+ thiswait.tv_sec = sleep_time / 1000000;
+ thiswait.tv_nsec = (sleep_time % 1000000) * 1000l;
+ _sleep_loop(thiswait);
+ }
+
+ U64 current_clock = get_clock_count();
+ U32 yields = 0;
+ while ( (yields < max_yields)
+ && (current_clock - start < us) )
+ {
+ sched_yield();
+ ++yields;
+ current_clock = get_clock_count();
+ }
+ return yields;
+}
+
+void ms_sleep(U32 ms)
+{
+ long mslong = ms; // tv_nsec is a long
+ struct timespec thiswait;
+ thiswait.tv_sec = ms / 1000;
+ thiswait.tv_nsec = (mslong % 1000) * 1000000l;
+ _sleep_loop(thiswait);
+}
+#else
+# error "architecture not supported"
+#endif
+
+//
+// CPU clock/other clock frequency and count functions
+//
+
+#if LL_WINDOWS
+U64 get_clock_count()
+{
+ static bool firstTime = true;
+ static U64 offset;
+ // ensures that callers to this function never have to deal with wrap
+
+ // QueryPerformanceCounter implementation
+ LARGE_INTEGER clock_count;
+ QueryPerformanceCounter(&clock_count);
+ if (firstTime) {
+ offset = clock_count.QuadPart;
+ firstTime = false;
+ }
+ return clock_count.QuadPart - offset;
+}
+
+F64 calc_clock_frequency()
+{
+ __int64 freq;
+ QueryPerformanceFrequency((LARGE_INTEGER *) &freq);
+ return (F64)freq;
+}
+#endif // LL_WINDOWS
+
+
+#if LL_LINUX || LL_DARWIN
+// Both Linux and Mac use gettimeofday for accurate time
+F64 calc_clock_frequency()
+{
+ return 1000000.0; // microseconds, so 1 MHz.
+}
+
+U64 get_clock_count()
+{
+ // Linux clocks are in microseconds
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return tv.tv_sec*SEC_TO_MICROSEC_U64 + tv.tv_usec;
+}
+#endif
+
+
+TimerInfo::TimerInfo()
+: mClockFrequency(0.0),
+ mTotalTimeClockCount(0),
+ mLastTotalTimeClockCount(0)
+{}
+
+void TimerInfo::update()
+{
+ mClockFrequency = calc_clock_frequency();
+ mClockFrequencyInv = 1.0/mClockFrequency;
+ mClocksToMicroseconds = mClockFrequencyInv;
+}
+
+TimerInfo& get_timer_info()
+{
+ static TimerInfo sTimerInfo;
+ return sTimerInfo;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// returns a U64 number that represents the number of
+// microseconds since the Unix epoch - Jan 1, 1970
+U64MicrosecondsImplicit totalTime()
+{
+ U64 current_clock_count = get_clock_count();
+ if (!get_timer_info().mTotalTimeClockCount || get_timer_info().mClocksToMicroseconds.value() == 0)
+ {
+ get_timer_info().update();
+ get_timer_info().mTotalTimeClockCount = current_clock_count;
+
+#if LL_WINDOWS
+ // Sync us up with local time (even though we PROBABLY don't need to, this is how it was implemented)
+ // Unix platforms use gettimeofday so they are synced, although this probably isn't a good assumption to
+ // make in the future.
+
+ get_timer_info().mTotalTimeClockCount = (U64)(time(NULL) * get_timer_info().mClockFrequency);
+#endif
+
+ // Update the last clock count
+ get_timer_info().mLastTotalTimeClockCount = current_clock_count;
+ }
+ else
+ {
+ if (current_clock_count >= get_timer_info().mLastTotalTimeClockCount)
+ {
+ // No wrapping, we're all okay.
+ get_timer_info().mTotalTimeClockCount += current_clock_count - get_timer_info().mLastTotalTimeClockCount;
+ }
+ else
+ {
+ // We've wrapped. Compensate correctly
+ get_timer_info().mTotalTimeClockCount += (0xFFFFFFFFFFFFFFFFULL - get_timer_info().mLastTotalTimeClockCount) + current_clock_count;
+ }
+
+ // Update the last clock count
+ get_timer_info().mLastTotalTimeClockCount = current_clock_count;
+ }
+
+ // Return the total clock tick count in microseconds.
+ U64Microseconds time(get_timer_info().mTotalTimeClockCount*get_timer_info().mClocksToMicroseconds);
+ return time;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+LLTimer::LLTimer()
+{
+ if (!get_timer_info().mClockFrequency)
+ {
+ get_timer_info().update();
+ }
+
+ mStarted = true;
+ reset();
+}
+
+LLTimer::~LLTimer()
+{}
+
+// static
+void LLTimer::initClass()
+{
+ if (!sTimer) sTimer = new LLTimer;
+}
+
+// static
+void LLTimer::cleanupClass()
+{
+ delete sTimer; sTimer = NULL;
+}
+
+// static
+U64MicrosecondsImplicit LLTimer::getTotalTime()
+{
+ // simply call into the implementation function.
+ U64MicrosecondsImplicit total_time = totalTime();
+ return total_time;
+}
+
+// static
+F64SecondsImplicit LLTimer::getTotalSeconds()
+{
+ return F64Microseconds(U64_to_F64(getTotalTime()));
+}
+
+void LLTimer::reset()
+{
+ mLastClockCount = get_clock_count();
+ mExpirationTicks = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+U64 LLTimer::getCurrentClockCount()
+{
+ return get_clock_count();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void LLTimer::setLastClockCount(U64 current_count)
+{
+ mLastClockCount = current_count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static
+U64 getElapsedTimeAndUpdate(U64& lastClockCount)
+{
+ U64 current_clock_count = get_clock_count();
+ U64 result;
+
+ if (current_clock_count >= lastClockCount)
+ {
+ result = current_clock_count - lastClockCount;
+ }
+ else
+ {
+ // time has gone backward
+ result = 0;
+ }
+
+ lastClockCount = current_clock_count;
+
+ return result;
+}
+
+
+F64SecondsImplicit LLTimer::getElapsedTimeF64() const
+{
+ U64 last = mLastClockCount;
+ return (F64)getElapsedTimeAndUpdate(last) * get_timer_info().mClockFrequencyInv;
+}
+
+F32SecondsImplicit LLTimer::getElapsedTimeF32() const
+{
+ return (F32)getElapsedTimeF64();
+}
+
+F64SecondsImplicit LLTimer::getElapsedTimeAndResetF64()
+{
+ return (F64)getElapsedTimeAndUpdate(mLastClockCount) * get_timer_info().mClockFrequencyInv;
+}
+
+F32SecondsImplicit LLTimer::getElapsedTimeAndResetF32()
+{
+ return (F32)getElapsedTimeAndResetF64();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void LLTimer::setTimerExpirySec(F32SecondsImplicit expiration)
+{
+ mExpirationTicks = get_clock_count()
+ + (U64)((F32)(expiration * get_timer_info().mClockFrequency.value()));
+}
+
+F32SecondsImplicit LLTimer::getRemainingTimeF32() const
+{
+ U64 cur_ticks = get_clock_count();
+ if (cur_ticks > mExpirationTicks)
+ {
+ return 0.0f;
+ }
+ return F32((mExpirationTicks - cur_ticks) * get_timer_info().mClockFrequencyInv);
+}
+
+
+bool LLTimer::checkExpirationAndReset(F32 expiration)
+{
+ U64 cur_ticks = get_clock_count();
+ if (cur_ticks < mExpirationTicks)
+ {
+ return false;
+ }
+
+ mExpirationTicks = cur_ticks
+ + (U64)((F32)(expiration * get_timer_info().mClockFrequency));
+ return true;
+}
+
+
+bool LLTimer::hasExpired() const
+{
+ return get_clock_count() >= mExpirationTicks;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool LLTimer::knownBadTimer()
+{
+ bool failed = false;
+
+#if LL_WINDOWS
+ WCHAR bad_pci_list[][10] = {L"1039:0530",
+ L"1039:0620",
+ L"10B9:0533",
+ L"10B9:1533",
+ L"1106:0596",
+ L"1106:0686",
+ L"1166:004F",
+ L"1166:0050",
+ L"8086:7110",
+ L"\0"
+ };
+
+ HKEY hKey = NULL;
+ LONG nResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SYSTEM\\CurrentControlSet\\Enum\\PCI", 0,
+ KEY_EXECUTE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey);
+
+ WCHAR name[1024];
+ DWORD name_len = 1024;
+ FILETIME scrap;
+
+ S32 key_num = 0;
+ WCHAR pci_id[10];
+
+ wcscpy(pci_id, L"0000:0000"); /*Flawfinder: ignore*/
+
+ while (nResult == ERROR_SUCCESS)
+ {
+ nResult = ::RegEnumKeyEx(hKey, key_num++, name, &name_len, NULL, NULL, NULL, &scrap);
+
+ if (nResult == ERROR_SUCCESS)
+ {
+ memcpy(&pci_id[0],&name[4],4); /* Flawfinder: ignore */
+ memcpy(&pci_id[5],&name[13],4); /* Flawfinder: ignore */
+
+ for (S32 check = 0; bad_pci_list[check][0]; check++)
+ {
+ if (!wcscmp(pci_id, bad_pci_list[check]))
+ {
+// LL_WARNS() << "unreliable PCI chipset found!! " << pci_id << endl;
+ failed = true;
+ break;
+ }
+ }
+// llinfo << "PCI chipset found: " << pci_id << endl;
+ name_len = 1024;
+ }
+ }
+#endif
+ return(failed);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// NON-MEMBER FUNCTIONS
+//
+///////////////////////////////////////////////////////////////////////////////
+
+time_t time_corrected()
+{
+ return time(NULL) + gUTCOffset;
+}
+
+
+// Is the current computer (in its current time zone)
+// observing daylight savings time?
+bool is_daylight_savings()
+{
+ time_t now = time(NULL);
+
+ // Internal buffer to local server time
+ struct tm* internal_time = localtime(&now);
+
+ // tm_isdst > 0 => daylight savings
+ // tm_isdst = 0 => not daylight savings
+ // tm_isdst < 0 => can't tell
+ return (internal_time->tm_isdst > 0);
+}
+
+
+struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time)
+{
+ S32Hours pacific_offset_hours;
+ if (pacific_daylight_time)
+ {
+ pacific_offset_hours = S32Hours(7);
+ }
+ else
+ {
+ pacific_offset_hours = S32Hours(8);
+ }
+
+ // We subtract off the PST/PDT offset _before_ getting
+ // "UTC" time, because this will handle wrapping around
+ // for 5 AM UTC -> 10 PM PDT of the previous day.
+ utc_time -= S32SecondsImplicit(pacific_offset_hours);
+
+ // Internal buffer to PST/PDT (see above)
+ struct tm* internal_time = gmtime(&utc_time);
+
+ /*
+ // Don't do this, this won't correctly tell you if daylight savings is active in CA or not.
+ if (pacific_daylight_time)
+ {
+ internal_time->tm_isdst = 1;
+ }
+ */
+
+ return internal_time;
+}
+
+
+void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring)
+{
+ U64 hours;
+ U64 minutes;
+ U64 seconds;
+ U64 frames;
+ U64 subframes;
+
+ hours = current_time / (U64)3600000000ul;
+ minutes = current_time / (U64)60000000;
+ minutes %= 60;
+ seconds = current_time / (U64)1000000;
+ seconds %= 60;
+ frames = current_time / (U64)41667;
+ frames %= 24;
+ subframes = current_time / (U64)42;
+ subframes %= 100;
+
+ tcstring = llformat("%3.3d:%2.2d:%2.2d:%2.2d.%2.2d",(int)hours,(int)minutes,(int)seconds,(int)frames,(int)subframes);
+}
+
+
+void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring)
+{
+ microsecondsToTimecodeString(current_time, tcstring);
+}
+
+
diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index fe4a3afc7a..60eb007595 100644 --- a/indra/llcommon/lltimer.h +++ b/indra/llcommon/lltimer.h @@ -1,188 +1,188 @@ -/** - * @file lltimer.h - * @brief Cross-platform objects for doing timing - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_TIMER_H -#define LL_TIMER_H - -#if LL_LINUX || LL_DARWIN -#include <sys/time.h> -#endif -#include <limits.h> - -#include "stdtypes.h" - -#include <string> -#include <list> -// units conversions -#include "llunits.h" -#ifndef USEC_PER_SEC - const U32 USEC_PER_SEC = 1000000; -#endif -const U32 SEC_PER_MIN = 60; -const U32 MIN_PER_HOUR = 60; -const U32 USEC_PER_MIN = USEC_PER_SEC * SEC_PER_MIN; -const U32 USEC_PER_HOUR = USEC_PER_MIN * MIN_PER_HOUR; -const U32 SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR; -const F64 SEC_PER_USEC = 1.0 / (F64) USEC_PER_SEC; - -class LL_COMMON_API LLTimer -{ -public: - static LLTimer *sTimer; // global timer - -protected: - U64 mLastClockCount; - U64 mExpirationTicks; - bool mStarted; - -public: - LLTimer(); - ~LLTimer(); - - static void initClass(); - static void cleanupClass(); - - // Return a high precision number of seconds since the start of - // this application instance. - static F64SecondsImplicit getElapsedSeconds() - { - if (sTimer) - { - return sTimer->getElapsedTimeF64(); - } - else - { - return 0; - } - } - - // Return a high precision usec since epoch - static U64MicrosecondsImplicit getTotalTime(); - - // Return a high precision seconds since epoch - static F64SecondsImplicit getTotalSeconds(); - - - // MANIPULATORS - void start() { reset(); mStarted = TRUE; } - void stop() { mStarted = FALSE; } - void reset(); // Resets the timer - void setLastClockCount(U64 current_count); // Sets the timer so that the next elapsed call will be relative to this time - void setTimerExpirySec(F32SecondsImplicit expiration); - BOOL checkExpirationAndReset(F32 expiration); - BOOL hasExpired() const; - F32SecondsImplicit getElapsedTimeAndResetF32(); // Returns elapsed time in seconds with reset - F64SecondsImplicit getElapsedTimeAndResetF64(); - - F32SecondsImplicit getRemainingTimeF32() const; - - static BOOL knownBadTimer(); - - // ACCESSORS - F32SecondsImplicit getElapsedTimeF32() const; // Returns elapsed time in seconds - F64SecondsImplicit getElapsedTimeF64() const; // Returns elapsed time in seconds - - bool getStarted() const { return mStarted; } - - - static U64 getCurrentClockCount(); // Returns the raw clockticks -}; - -// -// Various functions for initializing/accessing clock and timing stuff. Don't use these without REALLY knowing how they work. -// -struct TimerInfo -{ - TimerInfo(); - void update(); - - F64HertzImplicit mClockFrequency; - F64SecondsImplicit mClockFrequencyInv; - F64MicrosecondsImplicit mClocksToMicroseconds; - U64 mTotalTimeClockCount; - U64 mLastTotalTimeClockCount; -}; - -TimerInfo& get_timer_info(); - -LL_COMMON_API U64 get_clock_count(); - -// Sleep for milliseconds -LL_COMMON_API void ms_sleep(U32 ms); -LL_COMMON_API U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF); - -// Returns the correct UTC time in seconds, like time(NULL). -// Useful on the viewer, which may have its local clock set wrong. -LL_COMMON_API time_t time_corrected(); - -static inline time_t time_min() -{ - if (sizeof(time_t) == 4) - { - return (time_t) INT_MIN; - } else { -#ifdef LLONG_MIN - return (time_t) LLONG_MIN; -#else - return (time_t) LONG_MIN; -#endif - } -} - -static inline time_t time_max() -{ - if (sizeof(time_t) == 4) - { - return (time_t) INT_MAX; - } else { -#ifdef LLONG_MAX - return (time_t) LLONG_MAX; -#else - return (time_t) LONG_MAX; -#endif - } -} - -// Correction factor used by time_corrected() above. -extern LL_COMMON_API S32 gUTCOffset; - -// Is the current computer (in its current time zone) -// observing daylight savings time? -LL_COMMON_API BOOL is_daylight_savings(); - -// Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time -// Usage: -// S32 utc_time; -// utc_time = time_corrected(); -// struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight); -LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, BOOL pacific_daylight_time); - -LL_COMMON_API void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring); -LL_COMMON_API void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring); - -U64MicrosecondsImplicit LL_COMMON_API totalTime(); // Returns current system time in microseconds - -#endif +/**
+ * @file lltimer.h
+ * @brief Cross-platform objects for doing timing
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_TIMER_H
+#define LL_TIMER_H
+
+#if LL_LINUX || LL_DARWIN
+#include <sys/time.h>
+#endif
+#include <limits.h>
+
+#include "stdtypes.h"
+
+#include <string>
+#include <list>
+// units conversions
+#include "llunits.h"
+#ifndef USEC_PER_SEC
+ const U32 USEC_PER_SEC = 1000000;
+#endif
+const U32 SEC_PER_MIN = 60;
+const U32 MIN_PER_HOUR = 60;
+const U32 USEC_PER_MIN = USEC_PER_SEC * SEC_PER_MIN;
+const U32 USEC_PER_HOUR = USEC_PER_MIN * MIN_PER_HOUR;
+const U32 SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR;
+const F64 SEC_PER_USEC = 1.0 / (F64) USEC_PER_SEC;
+
+class LL_COMMON_API LLTimer
+{
+public:
+ static LLTimer *sTimer; // global timer
+
+protected:
+ U64 mLastClockCount;
+ U64 mExpirationTicks;
+ bool mStarted;
+
+public:
+ LLTimer();
+ ~LLTimer();
+
+ static void initClass();
+ static void cleanupClass();
+
+ // Return a high precision number of seconds since the start of
+ // this application instance.
+ static F64SecondsImplicit getElapsedSeconds()
+ {
+ if (sTimer)
+ {
+ return sTimer->getElapsedTimeF64();
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ // Return a high precision usec since epoch
+ static U64MicrosecondsImplicit getTotalTime();
+
+ // Return a high precision seconds since epoch
+ static F64SecondsImplicit getTotalSeconds();
+
+
+ // MANIPULATORS
+ void start() { reset(); mStarted = true; }
+ void stop() { mStarted = false; }
+ void reset(); // Resets the timer
+ void setLastClockCount(U64 current_count); // Sets the timer so that the next elapsed call will be relative to this time
+ void setTimerExpirySec(F32SecondsImplicit expiration);
+ bool checkExpirationAndReset(F32 expiration);
+ bool hasExpired() const;
+ F32SecondsImplicit getElapsedTimeAndResetF32(); // Returns elapsed time in seconds with reset
+ F64SecondsImplicit getElapsedTimeAndResetF64();
+
+ F32SecondsImplicit getRemainingTimeF32() const;
+
+ static bool knownBadTimer();
+
+ // ACCESSORS
+ F32SecondsImplicit getElapsedTimeF32() const; // Returns elapsed time in seconds
+ F64SecondsImplicit getElapsedTimeF64() const; // Returns elapsed time in seconds
+
+ bool getStarted() const { return mStarted; }
+
+
+ static U64 getCurrentClockCount(); // Returns the raw clockticks
+};
+
+//
+// Various functions for initializing/accessing clock and timing stuff. Don't use these without REALLY knowing how they work.
+//
+struct TimerInfo
+{
+ TimerInfo();
+ void update();
+
+ F64HertzImplicit mClockFrequency;
+ F64SecondsImplicit mClockFrequencyInv;
+ F64MicrosecondsImplicit mClocksToMicroseconds;
+ U64 mTotalTimeClockCount;
+ U64 mLastTotalTimeClockCount;
+};
+
+TimerInfo& get_timer_info();
+
+LL_COMMON_API U64 get_clock_count();
+
+// Sleep for milliseconds
+LL_COMMON_API void ms_sleep(U32 ms);
+LL_COMMON_API U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF);
+
+// Returns the correct UTC time in seconds, like time(NULL).
+// Useful on the viewer, which may have its local clock set wrong.
+LL_COMMON_API time_t time_corrected();
+
+static inline time_t time_min()
+{
+ if (sizeof(time_t) == 4)
+ {
+ return (time_t) INT_MIN;
+ } else {
+#ifdef LLONG_MIN
+ return (time_t) LLONG_MIN;
+#else
+ return (time_t) LONG_MIN;
+#endif
+ }
+}
+
+static inline time_t time_max()
+{
+ if (sizeof(time_t) == 4)
+ {
+ return (time_t) INT_MAX;
+ } else {
+#ifdef LLONG_MAX
+ return (time_t) LLONG_MAX;
+#else
+ return (time_t) LONG_MAX;
+#endif
+ }
+}
+
+// Correction factor used by time_corrected() above.
+extern LL_COMMON_API S32 gUTCOffset;
+
+// Is the current computer (in its current time zone)
+// observing daylight savings time?
+LL_COMMON_API bool is_daylight_savings();
+
+// Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time
+// Usage:
+// S32 utc_time;
+// utc_time = time_corrected();
+// struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight);
+LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time);
+
+LL_COMMON_API void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring);
+LL_COMMON_API void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring);
+
+U64MicrosecondsImplicit LL_COMMON_API totalTime(); // Returns current system time in microseconds
+
+#endif
diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp index e93238f0e9..b4664318cc 100644 --- a/indra/llcommon/lluri.cpp +++ b/indra/llcommon/lluri.cpp @@ -1,758 +1,758 @@ -/** - * @file lluri.cpp - * @author Phoenix - * @date 2006-02-08 - * @brief Implementation of the LLURI class. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include "llapp.h" -#include "lluri.h" -#include "llsd.h" -#include <iomanip> - -#include "lluuid.h" - -// system includes -#include <boost/tokenizer.hpp> -#include <boost/algorithm/string/find_iterator.hpp> -#include <boost/algorithm/string/finder.hpp> - -// static -void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val) -{ - ostr << "%" - - << std::uppercase - << std::hex - << std::setw(2) - << std::setfill('0') - - // VWR-4010 Cannot cast to U32 because sign-extension on - // chars > 128 will result in FFFFFFC3 instead of F3. - << static_cast<S32>(static_cast<U8>(val)) - - // reset stream state - << std::nouppercase - << std::dec - << std::setfill(' '); -} - -// static -std::string LLURI::escape( - const std::string& str, - const std::string& allowed, - bool is_allowed_sorted) -{ - // *NOTE: This size determination feels like a good value to - // me. If someone wante to come up with a more precise heuristic - // with some data to back up the assertion that 'sort is good' - // then feel free to change this test a bit. - if(!is_allowed_sorted && (str.size() > 2 * allowed.size())) - { - // if it's already sorted, or if the url is quite long, we - // want to optimize this process. - std::string sorted_allowed(allowed); - std::sort(sorted_allowed.begin(), sorted_allowed.end()); - return escape(str, sorted_allowed, true); - } - - std::ostringstream ostr; - std::string::const_iterator it = str.begin(); - std::string::const_iterator end = str.end(); - std::string::value_type c; - if(is_allowed_sorted) - { - std::string::const_iterator allowed_begin(allowed.begin()); - std::string::const_iterator allowed_end(allowed.end()); - for(; it != end; ++it) - { - c = *it; - if(std::binary_search(allowed_begin, allowed_end, c)) - { - ostr << c; - } - else - { - encodeCharacter(ostr, c); - } - } - } - else - { - for(; it != end; ++it) - { - c = *it; - if(allowed.find(c) == std::string::npos) - { - encodeCharacter(ostr, c); - } - else - { - ostr << c; - } - } - } - return ostr.str(); -} - -// static -std::string LLURI::unescape(const std::string& str) -{ - std::ostringstream ostr; - std::string::const_iterator it = str.begin(); - std::string::const_iterator end = str.end(); - for(; it != end; ++it) - { - if((*it) == '%') - { - ++it; - if(it == end) break; - - if(is_char_hex(*it)) - { - U8 c = hex_as_nybble(*it++); - - c = c << 4; - if (it == end) break; - - if(is_char_hex(*it)) - { - c |= hex_as_nybble(*it); - ostr.put((char)c); - } - else - { - ostr.put((char)c); - ostr.put(*it); - } - } - else - { - ostr.put('%'); - ostr.put(*it); - } - } - else - { - ostr.put(*it); - } - } - return ostr.str(); -} - -namespace -{ - const std::string unreserved() - { - static const std::string s = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~"; - return s; - } - const std::string path() - { - static const std::string s = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "$-_.+" - "!*'()," - "{}|\\^~[]`" - "<>#%" - ";/?:@&="; - return s; - } - const std::string sub_delims() - { - static const std::string s = "!$&'()*+,;="; - return s; - } - - std::string escapeHostAndPort(const std::string& s) - { return LLURI::escape(s, unreserved() + sub_delims() +":"); } - std::string escapePathComponent(const std::string& s) - { return LLURI::escape(s, unreserved() + sub_delims() + ":@"); } - std::string escapeQueryVariable(const std::string& s) - { return LLURI::escape(s, unreserved() + ":@!$'()*+,"); } // sub_delims - "&;=" + ":@" - std::string escapeQueryValue(const std::string& s) - { return LLURI::escape(s, unreserved() + ":@!$'()*+,="); } // sub_delims - "&;" + ":@" - std::string escapeUriQuery(const std::string& s) - { return LLURI::escape(s, unreserved() + ":@?&$;*+=%/"); } - std::string escapeUriData(const std::string& s) - { return LLURI::escape(s, unreserved() + "%"); } - std::string escapeUriPath(const std::string& s) - { return LLURI::escape(s, path()); } -} - -//static -std::string LLURI::escape(const std::string& str) -{ - static std::string default_allowed = unreserved(); - static bool initialized = false; - if(!initialized) - { - std::sort(default_allowed.begin(), default_allowed.end()); - initialized = true; - } - return escape(str, default_allowed, true); -} - -//static -std::string LLURI::escapePathAndData(const std::string &str) -{ - std::string result; - - const std::string data_marker = "data:"; - if (str.compare(0, data_marker.length(), data_marker) == 0) - { - // This is not url, but data, data part needs to be properly escaped - // data part is separated by ',' from header. Minimal data uri is "data:," - // See "data URI scheme" - size_t separator = str.find(','); - if (separator != std::string::npos) - { - size_t header_size = separator + 1; - std::string header = str.substr(0, header_size); - // base64 is url-safe - if (header.find("base64") != std::string::npos) - { - // assume url-safe data - result = str; - } - else - { - std::string data = str.substr(header_size, str.length() - header_size); - - // Notes: File can be partially pre-escaped, that's why escaping ignores '%' - // It somewhat limits user from displaying strings like "%20" in text - // but that's how viewer worked for a while and user can double-escape it - - - // Header doesn't need escaping - result = header + escapeUriData(data); - } - } - } - else - { - // try processing it as path with query separator - // The query component is indicated by the first question - // mark("?") character and terminated by a number sign("#") - size_t delim_pos = str.find('?'); - if (delim_pos == std::string::npos) - { - // alternate separator - delim_pos = str.find(';'); - } - - if (delim_pos != std::string::npos) - { - size_t path_size = delim_pos + 1; - std::string query; - std::string fragment; - - size_t fragment_pos = str.find('#'); - if ((fragment_pos != std::string::npos) && (fragment_pos > delim_pos)) - { - query = str.substr(path_size, fragment_pos - path_size); - fragment = str.substr(fragment_pos); - } - else - { - query = str.substr(path_size); - } - - std::string path = str.substr(0, path_size); - - result = escapeUriPath(path) + escapeUriQuery(query) + escapeUriPath(fragment); - } - } - - if (result.empty()) - { - // Not a known scheme or no data part, try just escaping as Uri path - result = escapeUriPath(str); - } - return result; -} - -LLURI::LLURI() -{ -} - -LLURI::LLURI(const std::string& escaped_str) -{ - std::string::size_type delim_pos; - delim_pos = escaped_str.find(':'); - std::string temp; - if (delim_pos == std::string::npos) - { - mScheme = ""; - mEscapedOpaque = escaped_str; - } - else - { - mScheme = escaped_str.substr(0, delim_pos); - mEscapedOpaque = escaped_str.substr(delim_pos+1); - } - - parseAuthorityAndPathUsingOpaque(); - - delim_pos = mEscapedPath.find('?'); - if (delim_pos != std::string::npos) - { - mEscapedQuery = mEscapedPath.substr(delim_pos+1); - mEscapedPath = mEscapedPath.substr(0,delim_pos); - } -} - -static BOOL isDefault(const std::string& scheme, U16 port) -{ - if (scheme == "http") - return port == 80; - if (scheme == "https") - return port == 443; - if (scheme == "ftp") - return port == 21; - - return FALSE; -} - -void LLURI::parseAuthorityAndPathUsingOpaque() -{ - if (mScheme == "http" || mScheme == "https" || - mScheme == "ftp" || mScheme == "secondlife" || - mScheme == "x-grid-location-info") - { - if (mEscapedOpaque.substr(0,2) != "//") - { - return; - } - - std::string::size_type delim_pos, delim_pos2; - delim_pos = mEscapedOpaque.find('/', 2); - delim_pos2 = mEscapedOpaque.find('?', 2); - // no path, no query - if (delim_pos == std::string::npos && - delim_pos2 == std::string::npos) - { - mEscapedAuthority = mEscapedOpaque.substr(2); - mEscapedPath = ""; - } - // path exist, no query - else if (delim_pos2 == std::string::npos) - { - mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); - mEscapedPath = mEscapedOpaque.substr(delim_pos); - } - // no path, only query - else if (delim_pos == std::string::npos || - delim_pos2 < delim_pos) - { - mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2); - // query part will be broken out later - mEscapedPath = mEscapedOpaque.substr(delim_pos2); - } - // path and query - else - { - mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); - // query part will be broken out later - mEscapedPath = mEscapedOpaque.substr(delim_pos); - } - } - else if (mScheme == "about") - { - mEscapedPath = mEscapedOpaque; - } -} - -LLURI::LLURI(const std::string& scheme, - const std::string& userName, - const std::string& password, - const std::string& hostName, - U16 port, - const std::string& escapedPath, - const std::string& escapedQuery) - : mScheme(scheme), - mEscapedPath(escapedPath), - mEscapedQuery(escapedQuery) -{ - std::ostringstream auth; - std::ostringstream opaque; - - opaque << "//"; - - if (!userName.empty()) - { - auth << escape(userName); - if (!password.empty()) - { - auth << ':' << escape(password); - } - auth << '@'; - } - auth << hostName; - if (!isDefault(scheme, port)) - { - auth << ':' << port; - } - mEscapedAuthority = auth.str(); - - opaque << mEscapedAuthority << escapedPath << escapedQuery; - - mEscapedOpaque = opaque.str(); -} - -LLURI::~LLURI() -{ -} - -// static -LLURI LLURI::buildHTTP(const std::string& prefix, - const LLSD& path) -{ - LLURI result; - - // TODO: deal with '/' '?' '#' in host_port - if (prefix.find("://") != prefix.npos) - { - // it is a prefix - result = LLURI(prefix); - } - else - { - // it is just a host and optional port - result.mScheme = "http"; - result.mEscapedAuthority = escapeHostAndPort(prefix); - } - - if (path.isArray()) - { - // break out and escape each path component - for (LLSD::array_const_iterator it = path.beginArray(); - it != path.endArray(); - ++it) - { - LL_DEBUGS() << "PATH: inserting " << it->asString() << LL_ENDL; - result.mEscapedPath += "/" + escapePathComponent(it->asString()); - } - } - else if (path.isString()) - { - std::string pathstr(path); - // Trailing slash is significant in HTTP land. If caller specified, - // make a point of preserving. - std::string last_slash; - std::string::size_type len(pathstr.length()); - if (len && pathstr[len-1] == '/') - { - last_slash = "/"; - } - - // Escape every individual path component, recombining with slashes. - for (boost::split_iterator<std::string::const_iterator> - ti(pathstr, boost::first_finder("/")), tend; - ti != tend; ++ti) - { - // Eliminate a leading slash or duplicate slashes anywhere. (Extra - // slashes show up here as empty components.) This test also - // eliminates a trailing slash, hence last_slash above. - if (! ti->empty()) - { - result.mEscapedPath - += "/" + escapePathComponent(std::string(ti->begin(), ti->end())); - } - } - - // Reinstate trailing slash, if any. - result.mEscapedPath += last_slash; - } - else if(path.isUndefined()) - { - // do nothing - } - else - { - LL_WARNS() << "Valid path arguments to buildHTTP are array, string, or undef, you passed type" - << path.type() << LL_ENDL; - } - result.mEscapedOpaque = "//" + result.mEscapedAuthority + - result.mEscapedPath; - return result; -} - -// static -LLURI LLURI::buildHTTP(const std::string& prefix, - const LLSD& path, - const LLSD& query) -{ - LLURI uri = buildHTTP(prefix, path); - // break out and escape each query component - uri.mEscapedQuery = mapToQueryString(query); - uri.mEscapedOpaque += uri.mEscapedQuery ; - uri.mEscapedQuery.erase(0,1); // trim the leading '?' - return uri; -} - -// static -LLURI LLURI::buildHTTP(const std::string& host, - const U32& port, - const LLSD& path) -{ - return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path); -} - -// static -LLURI LLURI::buildHTTP(const std::string& host, - const U32& port, - const LLSD& path, - const LLSD& query) -{ - return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path, query); -} - -std::string LLURI::asString() const -{ - if (mScheme.empty()) - { - return mEscapedOpaque; - } - else - { - return mScheme + ":" + mEscapedOpaque; - } -} - -std::string LLURI::scheme() const -{ - return mScheme; -} - -std::string LLURI::opaque() const -{ - return unescape(mEscapedOpaque); -} - -std::string LLURI::authority() const -{ - return unescape(mEscapedAuthority); -} - - -namespace { - void findAuthorityParts(const std::string& authority, - std::string& user, - std::string& host, - std::string& port) - { - std::string::size_type start_pos = authority.find('@'); - if (start_pos == std::string::npos) - { - user = ""; - start_pos = 0; - } - else - { - user = authority.substr(0, start_pos); - start_pos += 1; - } - - std::string::size_type end_pos = authority.find(':', start_pos); - if (end_pos == std::string::npos) - { - host = authority.substr(start_pos); - port = ""; - } - else - { - host = authority.substr(start_pos, end_pos - start_pos); - port = authority.substr(end_pos + 1); - } - } -} - -std::string LLURI::hostName() const -{ - std::string user, host, port; - findAuthorityParts(mEscapedAuthority, user, host, port); - return unescape(host); -} - -std::string LLURI::userName() const -{ - std::string user, userPass, host, port; - findAuthorityParts(mEscapedAuthority, userPass, host, port); - std::string::size_type pos = userPass.find(':'); - if (pos != std::string::npos) - { - user = userPass.substr(0, pos); - } - return unescape(user); -} - -std::string LLURI::password() const -{ - std::string pass, userPass, host, port; - findAuthorityParts(mEscapedAuthority, userPass, host, port); - std::string::size_type pos = userPass.find(':'); - if (pos != std::string::npos) - { - pass = userPass.substr(pos + 1); - } - return unescape(pass); -} - -BOOL LLURI::defaultPort() const -{ - return isDefault(mScheme, hostPort()); -} - -U16 LLURI::hostPort() const -{ - std::string user, host, port; - findAuthorityParts(mEscapedAuthority, user, host, port); - if (port.empty()) - { - if (mScheme == "http") - return 80; - if (mScheme == "https") - return 443; - if (mScheme == "ftp") - return 21; - return 0; - } - return atoi(port.c_str()); -} - -std::string LLURI::path() const -{ - return unescape(mEscapedPath); -} - -LLSD LLURI::pathArray() const -{ - typedef boost::tokenizer<boost::char_separator<char> > tokenizer; - boost::char_separator<char> sep("/", "", boost::drop_empty_tokens); - tokenizer tokens(mEscapedPath, sep); - tokenizer::iterator it = tokens.begin(); - tokenizer::iterator end = tokens.end(); - - LLSD params; - for (const std::string& str : tokens) - { - params.append(str); - } - return params; -} - -std::string LLURI::query() const -{ - return unescape(mEscapedQuery); -} - -LLSD LLURI::queryMap() const -{ - return queryMap(mEscapedQuery); -} - -// static -LLSD LLURI::queryMap(std::string escaped_query_string) -{ - LL_DEBUGS() << "LLURI::queryMap query params: " << escaped_query_string << LL_ENDL; - - LLSD result = LLSD::emptyArray(); - while(!escaped_query_string.empty()) - { - // get tuple first - std::string tuple; - std::string::size_type tuple_begin = escaped_query_string.find('&'); - if (tuple_begin != std::string::npos) - { - tuple = escaped_query_string.substr(0, tuple_begin); - escaped_query_string = escaped_query_string.substr(tuple_begin+1); - } - else - { - tuple = escaped_query_string; - escaped_query_string = ""; - } - if (tuple.empty()) continue; - - // parse tuple - std::string::size_type key_end = tuple.find('='); - if (key_end != std::string::npos) - { - std::string key = unescape(tuple.substr(0,key_end)); - std::string value = unescape(tuple.substr(key_end+1)); - LL_DEBUGS() << "inserting key " << key << " value " << value << LL_ENDL; - result[key] = value; - } - else - { - LL_DEBUGS() << "inserting key " << unescape(tuple) << " value true" << LL_ENDL; - result[unescape(tuple)] = true; - } - } - return result; -} - -std::string LLURI::mapToQueryString(const LLSD& queryMap) -{ - std::string query_string; - if (queryMap.isMap()) - { - bool first_element = true; - LLSD::map_const_iterator iter = queryMap.beginMap(); - LLSD::map_const_iterator end = queryMap.endMap(); - std::ostringstream ostr; - for (; iter != end; ++iter) - { - if(first_element) - { - ostr << "?"; - first_element = false; - } - else - { - ostr << "&"; - } - ostr << escapeQueryVariable(iter->first); - if(iter->second.isDefined()) - { - ostr << "=" << escapeQueryValue(iter->second.asString()); - } - } - query_string = ostr.str(); - } - return query_string; -} - -bool operator!=(const LLURI& first, const LLURI& second) -{ - return (first.asString() != second.asString()); -} +/**
+ * @file lluri.cpp
+ * @author Phoenix
+ * @date 2006-02-08
+ * @brief Implementation of the LLURI class.
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include "llapp.h"
+#include "lluri.h"
+#include "llsd.h"
+#include <iomanip>
+
+#include "lluuid.h"
+
+// system includes
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/find_iterator.hpp>
+#include <boost/algorithm/string/finder.hpp>
+
+// static
+void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val)
+{
+ ostr << "%"
+
+ << std::uppercase
+ << std::hex
+ << std::setw(2)
+ << std::setfill('0')
+
+ // VWR-4010 Cannot cast to U32 because sign-extension on
+ // chars > 128 will result in FFFFFFC3 instead of F3.
+ << static_cast<S32>(static_cast<U8>(val))
+
+ // reset stream state
+ << std::nouppercase
+ << std::dec
+ << std::setfill(' ');
+}
+
+// static
+std::string LLURI::escape(
+ const std::string& str,
+ const std::string& allowed,
+ bool is_allowed_sorted)
+{
+ // *NOTE: This size determination feels like a good value to
+ // me. If someone wante to come up with a more precise heuristic
+ // with some data to back up the assertion that 'sort is good'
+ // then feel free to change this test a bit.
+ if(!is_allowed_sorted && (str.size() > 2 * allowed.size()))
+ {
+ // if it's already sorted, or if the url is quite long, we
+ // want to optimize this process.
+ std::string sorted_allowed(allowed);
+ std::sort(sorted_allowed.begin(), sorted_allowed.end());
+ return escape(str, sorted_allowed, true);
+ }
+
+ std::ostringstream ostr;
+ std::string::const_iterator it = str.begin();
+ std::string::const_iterator end = str.end();
+ std::string::value_type c;
+ if(is_allowed_sorted)
+ {
+ std::string::const_iterator allowed_begin(allowed.begin());
+ std::string::const_iterator allowed_end(allowed.end());
+ for(; it != end; ++it)
+ {
+ c = *it;
+ if(std::binary_search(allowed_begin, allowed_end, c))
+ {
+ ostr << c;
+ }
+ else
+ {
+ encodeCharacter(ostr, c);
+ }
+ }
+ }
+ else
+ {
+ for(; it != end; ++it)
+ {
+ c = *it;
+ if(allowed.find(c) == std::string::npos)
+ {
+ encodeCharacter(ostr, c);
+ }
+ else
+ {
+ ostr << c;
+ }
+ }
+ }
+ return ostr.str();
+}
+
+// static
+std::string LLURI::unescape(const std::string& str)
+{
+ std::ostringstream ostr;
+ std::string::const_iterator it = str.begin();
+ std::string::const_iterator end = str.end();
+ for(; it != end; ++it)
+ {
+ if((*it) == '%')
+ {
+ ++it;
+ if(it == end) break;
+
+ if(is_char_hex(*it))
+ {
+ U8 c = hex_as_nybble(*it++);
+
+ c = c << 4;
+ if (it == end) break;
+
+ if(is_char_hex(*it))
+ {
+ c |= hex_as_nybble(*it);
+ ostr.put((char)c);
+ }
+ else
+ {
+ ostr.put((char)c);
+ ostr.put(*it);
+ }
+ }
+ else
+ {
+ ostr.put('%');
+ ostr.put(*it);
+ }
+ }
+ else
+ {
+ ostr.put(*it);
+ }
+ }
+ return ostr.str();
+}
+
+namespace
+{
+ const std::string unreserved()
+ {
+ static const std::string s =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-._~";
+ return s;
+ }
+ const std::string path()
+ {
+ static const std::string s =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "$-_.+"
+ "!*'(),"
+ "{}|\\^~[]`"
+ "<>#%"
+ ";/?:@&=";
+ return s;
+ }
+ const std::string sub_delims()
+ {
+ static const std::string s = "!$&'()*+,;=";
+ return s;
+ }
+
+ std::string escapeHostAndPort(const std::string& s)
+ { return LLURI::escape(s, unreserved() + sub_delims() +":"); }
+ std::string escapePathComponent(const std::string& s)
+ { return LLURI::escape(s, unreserved() + sub_delims() + ":@"); }
+ std::string escapeQueryVariable(const std::string& s)
+ { return LLURI::escape(s, unreserved() + ":@!$'()*+,"); } // sub_delims - "&;=" + ":@"
+ std::string escapeQueryValue(const std::string& s)
+ { return LLURI::escape(s, unreserved() + ":@!$'()*+,="); } // sub_delims - "&;" + ":@"
+ std::string escapeUriQuery(const std::string& s)
+ { return LLURI::escape(s, unreserved() + ":@?&$;*+=%/"); }
+ std::string escapeUriData(const std::string& s)
+ { return LLURI::escape(s, unreserved() + "%"); }
+ std::string escapeUriPath(const std::string& s)
+ { return LLURI::escape(s, path()); }
+}
+
+//static
+std::string LLURI::escape(const std::string& str)
+{
+ static std::string default_allowed = unreserved();
+ static bool initialized = false;
+ if(!initialized)
+ {
+ std::sort(default_allowed.begin(), default_allowed.end());
+ initialized = true;
+ }
+ return escape(str, default_allowed, true);
+}
+
+//static
+std::string LLURI::escapePathAndData(const std::string &str)
+{
+ std::string result;
+
+ const std::string data_marker = "data:";
+ if (str.compare(0, data_marker.length(), data_marker) == 0)
+ {
+ // This is not url, but data, data part needs to be properly escaped
+ // data part is separated by ',' from header. Minimal data uri is "data:,"
+ // See "data URI scheme"
+ size_t separator = str.find(',');
+ if (separator != std::string::npos)
+ {
+ size_t header_size = separator + 1;
+ std::string header = str.substr(0, header_size);
+ // base64 is url-safe
+ if (header.find("base64") != std::string::npos)
+ {
+ // assume url-safe data
+ result = str;
+ }
+ else
+ {
+ std::string data = str.substr(header_size, str.length() - header_size);
+
+ // Notes: File can be partially pre-escaped, that's why escaping ignores '%'
+ // It somewhat limits user from displaying strings like "%20" in text
+ // but that's how viewer worked for a while and user can double-escape it
+
+
+ // Header doesn't need escaping
+ result = header + escapeUriData(data);
+ }
+ }
+ }
+ else
+ {
+ // try processing it as path with query separator
+ // The query component is indicated by the first question
+ // mark("?") character and terminated by a number sign("#")
+ size_t delim_pos = str.find('?');
+ if (delim_pos == std::string::npos)
+ {
+ // alternate separator
+ delim_pos = str.find(';');
+ }
+
+ if (delim_pos != std::string::npos)
+ {
+ size_t path_size = delim_pos + 1;
+ std::string query;
+ std::string fragment;
+
+ size_t fragment_pos = str.find('#');
+ if ((fragment_pos != std::string::npos) && (fragment_pos > delim_pos))
+ {
+ query = str.substr(path_size, fragment_pos - path_size);
+ fragment = str.substr(fragment_pos);
+ }
+ else
+ {
+ query = str.substr(path_size);
+ }
+
+ std::string path = str.substr(0, path_size);
+
+ result = escapeUriPath(path) + escapeUriQuery(query) + escapeUriPath(fragment);
+ }
+ }
+
+ if (result.empty())
+ {
+ // Not a known scheme or no data part, try just escaping as Uri path
+ result = escapeUriPath(str);
+ }
+ return result;
+}
+
+LLURI::LLURI()
+{
+}
+
+LLURI::LLURI(const std::string& escaped_str)
+{
+ std::string::size_type delim_pos;
+ delim_pos = escaped_str.find(':');
+ std::string temp;
+ if (delim_pos == std::string::npos)
+ {
+ mScheme = "";
+ mEscapedOpaque = escaped_str;
+ }
+ else
+ {
+ mScheme = escaped_str.substr(0, delim_pos);
+ mEscapedOpaque = escaped_str.substr(delim_pos+1);
+ }
+
+ parseAuthorityAndPathUsingOpaque();
+
+ delim_pos = mEscapedPath.find('?');
+ if (delim_pos != std::string::npos)
+ {
+ mEscapedQuery = mEscapedPath.substr(delim_pos+1);
+ mEscapedPath = mEscapedPath.substr(0,delim_pos);
+ }
+}
+
+static bool isDefault(const std::string& scheme, U16 port)
+{
+ if (scheme == "http")
+ return port == 80;
+ if (scheme == "https")
+ return port == 443;
+ if (scheme == "ftp")
+ return port == 21;
+
+ return false;
+}
+
+void LLURI::parseAuthorityAndPathUsingOpaque()
+{
+ if (mScheme == "http" || mScheme == "https" ||
+ mScheme == "ftp" || mScheme == "secondlife" ||
+ mScheme == "x-grid-location-info")
+ {
+ if (mEscapedOpaque.substr(0,2) != "//")
+ {
+ return;
+ }
+
+ std::string::size_type delim_pos, delim_pos2;
+ delim_pos = mEscapedOpaque.find('/', 2);
+ delim_pos2 = mEscapedOpaque.find('?', 2);
+ // no path, no query
+ if (delim_pos == std::string::npos &&
+ delim_pos2 == std::string::npos)
+ {
+ mEscapedAuthority = mEscapedOpaque.substr(2);
+ mEscapedPath = "";
+ }
+ // path exist, no query
+ else if (delim_pos2 == std::string::npos)
+ {
+ mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2);
+ mEscapedPath = mEscapedOpaque.substr(delim_pos);
+ }
+ // no path, only query
+ else if (delim_pos == std::string::npos ||
+ delim_pos2 < delim_pos)
+ {
+ mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2);
+ // query part will be broken out later
+ mEscapedPath = mEscapedOpaque.substr(delim_pos2);
+ }
+ // path and query
+ else
+ {
+ mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2);
+ // query part will be broken out later
+ mEscapedPath = mEscapedOpaque.substr(delim_pos);
+ }
+ }
+ else if (mScheme == "about")
+ {
+ mEscapedPath = mEscapedOpaque;
+ }
+}
+
+LLURI::LLURI(const std::string& scheme,
+ const std::string& userName,
+ const std::string& password,
+ const std::string& hostName,
+ U16 port,
+ const std::string& escapedPath,
+ const std::string& escapedQuery)
+ : mScheme(scheme),
+ mEscapedPath(escapedPath),
+ mEscapedQuery(escapedQuery)
+{
+ std::ostringstream auth;
+ std::ostringstream opaque;
+
+ opaque << "//";
+
+ if (!userName.empty())
+ {
+ auth << escape(userName);
+ if (!password.empty())
+ {
+ auth << ':' << escape(password);
+ }
+ auth << '@';
+ }
+ auth << hostName;
+ if (!isDefault(scheme, port))
+ {
+ auth << ':' << port;
+ }
+ mEscapedAuthority = auth.str();
+
+ opaque << mEscapedAuthority << escapedPath << escapedQuery;
+
+ mEscapedOpaque = opaque.str();
+}
+
+LLURI::~LLURI()
+{
+}
+
+// static
+LLURI LLURI::buildHTTP(const std::string& prefix,
+ const LLSD& path)
+{
+ LLURI result;
+
+ // TODO: deal with '/' '?' '#' in host_port
+ if (prefix.find("://") != prefix.npos)
+ {
+ // it is a prefix
+ result = LLURI(prefix);
+ }
+ else
+ {
+ // it is just a host and optional port
+ result.mScheme = "http";
+ result.mEscapedAuthority = escapeHostAndPort(prefix);
+ }
+
+ if (path.isArray())
+ {
+ // break out and escape each path component
+ for (LLSD::array_const_iterator it = path.beginArray();
+ it != path.endArray();
+ ++it)
+ {
+ LL_DEBUGS() << "PATH: inserting " << it->asString() << LL_ENDL;
+ result.mEscapedPath += "/" + escapePathComponent(it->asString());
+ }
+ }
+ else if (path.isString())
+ {
+ std::string pathstr(path);
+ // Trailing slash is significant in HTTP land. If caller specified,
+ // make a point of preserving.
+ std::string last_slash;
+ std::string::size_type len(pathstr.length());
+ if (len && pathstr[len-1] == '/')
+ {
+ last_slash = "/";
+ }
+
+ // Escape every individual path component, recombining with slashes.
+ for (boost::split_iterator<std::string::const_iterator>
+ ti(pathstr, boost::first_finder("/")), tend;
+ ti != tend; ++ti)
+ {
+ // Eliminate a leading slash or duplicate slashes anywhere. (Extra
+ // slashes show up here as empty components.) This test also
+ // eliminates a trailing slash, hence last_slash above.
+ if (! ti->empty())
+ {
+ result.mEscapedPath
+ += "/" + escapePathComponent(std::string(ti->begin(), ti->end()));
+ }
+ }
+
+ // Reinstate trailing slash, if any.
+ result.mEscapedPath += last_slash;
+ }
+ else if(path.isUndefined())
+ {
+ // do nothing
+ }
+ else
+ {
+ LL_WARNS() << "Valid path arguments to buildHTTP are array, string, or undef, you passed type"
+ << path.type() << LL_ENDL;
+ }
+ result.mEscapedOpaque = "//" + result.mEscapedAuthority +
+ result.mEscapedPath;
+ return result;
+}
+
+// static
+LLURI LLURI::buildHTTP(const std::string& prefix,
+ const LLSD& path,
+ const LLSD& query)
+{
+ LLURI uri = buildHTTP(prefix, path);
+ // break out and escape each query component
+ uri.mEscapedQuery = mapToQueryString(query);
+ uri.mEscapedOpaque += uri.mEscapedQuery ;
+ uri.mEscapedQuery.erase(0,1); // trim the leading '?'
+ return uri;
+}
+
+// static
+LLURI LLURI::buildHTTP(const std::string& host,
+ const U32& port,
+ const LLSD& path)
+{
+ return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path);
+}
+
+// static
+LLURI LLURI::buildHTTP(const std::string& host,
+ const U32& port,
+ const LLSD& path,
+ const LLSD& query)
+{
+ return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path, query);
+}
+
+std::string LLURI::asString() const
+{
+ if (mScheme.empty())
+ {
+ return mEscapedOpaque;
+ }
+ else
+ {
+ return mScheme + ":" + mEscapedOpaque;
+ }
+}
+
+std::string LLURI::scheme() const
+{
+ return mScheme;
+}
+
+std::string LLURI::opaque() const
+{
+ return unescape(mEscapedOpaque);
+}
+
+std::string LLURI::authority() const
+{
+ return unescape(mEscapedAuthority);
+}
+
+
+namespace {
+ void findAuthorityParts(const std::string& authority,
+ std::string& user,
+ std::string& host,
+ std::string& port)
+ {
+ std::string::size_type start_pos = authority.find('@');
+ if (start_pos == std::string::npos)
+ {
+ user = "";
+ start_pos = 0;
+ }
+ else
+ {
+ user = authority.substr(0, start_pos);
+ start_pos += 1;
+ }
+
+ std::string::size_type end_pos = authority.find(':', start_pos);
+ if (end_pos == std::string::npos)
+ {
+ host = authority.substr(start_pos);
+ port = "";
+ }
+ else
+ {
+ host = authority.substr(start_pos, end_pos - start_pos);
+ port = authority.substr(end_pos + 1);
+ }
+ }
+}
+
+std::string LLURI::hostName() const
+{
+ std::string user, host, port;
+ findAuthorityParts(mEscapedAuthority, user, host, port);
+ return unescape(host);
+}
+
+std::string LLURI::userName() const
+{
+ std::string user, userPass, host, port;
+ findAuthorityParts(mEscapedAuthority, userPass, host, port);
+ std::string::size_type pos = userPass.find(':');
+ if (pos != std::string::npos)
+ {
+ user = userPass.substr(0, pos);
+ }
+ return unescape(user);
+}
+
+std::string LLURI::password() const
+{
+ std::string pass, userPass, host, port;
+ findAuthorityParts(mEscapedAuthority, userPass, host, port);
+ std::string::size_type pos = userPass.find(':');
+ if (pos != std::string::npos)
+ {
+ pass = userPass.substr(pos + 1);
+ }
+ return unescape(pass);
+}
+
+bool LLURI::defaultPort() const
+{
+ return isDefault(mScheme, hostPort());
+}
+
+U16 LLURI::hostPort() const
+{
+ std::string user, host, port;
+ findAuthorityParts(mEscapedAuthority, user, host, port);
+ if (port.empty())
+ {
+ if (mScheme == "http")
+ return 80;
+ if (mScheme == "https")
+ return 443;
+ if (mScheme == "ftp")
+ return 21;
+ return 0;
+ }
+ return atoi(port.c_str());
+}
+
+std::string LLURI::path() const
+{
+ return unescape(mEscapedPath);
+}
+
+LLSD LLURI::pathArray() const
+{
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep("/", "", boost::drop_empty_tokens);
+ tokenizer tokens(mEscapedPath, sep);
+ tokenizer::iterator it = tokens.begin();
+ tokenizer::iterator end = tokens.end();
+
+ LLSD params;
+ for (const std::string& str : tokens)
+ {
+ params.append(str);
+ }
+ return params;
+}
+
+std::string LLURI::query() const
+{
+ return unescape(mEscapedQuery);
+}
+
+LLSD LLURI::queryMap() const
+{
+ return queryMap(mEscapedQuery);
+}
+
+// static
+LLSD LLURI::queryMap(std::string escaped_query_string)
+{
+ LL_DEBUGS() << "LLURI::queryMap query params: " << escaped_query_string << LL_ENDL;
+
+ LLSD result = LLSD::emptyArray();
+ while(!escaped_query_string.empty())
+ {
+ // get tuple first
+ std::string tuple;
+ std::string::size_type tuple_begin = escaped_query_string.find('&');
+ if (tuple_begin != std::string::npos)
+ {
+ tuple = escaped_query_string.substr(0, tuple_begin);
+ escaped_query_string = escaped_query_string.substr(tuple_begin+1);
+ }
+ else
+ {
+ tuple = escaped_query_string;
+ escaped_query_string = "";
+ }
+ if (tuple.empty()) continue;
+
+ // parse tuple
+ std::string::size_type key_end = tuple.find('=');
+ if (key_end != std::string::npos)
+ {
+ std::string key = unescape(tuple.substr(0,key_end));
+ std::string value = unescape(tuple.substr(key_end+1));
+ LL_DEBUGS() << "inserting key " << key << " value " << value << LL_ENDL;
+ result[key] = value;
+ }
+ else
+ {
+ LL_DEBUGS() << "inserting key " << unescape(tuple) << " value true" << LL_ENDL;
+ result[unescape(tuple)] = true;
+ }
+ }
+ return result;
+}
+
+std::string LLURI::mapToQueryString(const LLSD& queryMap)
+{
+ std::string query_string;
+ if (queryMap.isMap())
+ {
+ bool first_element = true;
+ LLSD::map_const_iterator iter = queryMap.beginMap();
+ LLSD::map_const_iterator end = queryMap.endMap();
+ std::ostringstream ostr;
+ for (; iter != end; ++iter)
+ {
+ if(first_element)
+ {
+ ostr << "?";
+ first_element = false;
+ }
+ else
+ {
+ ostr << "&";
+ }
+ ostr << escapeQueryVariable(iter->first);
+ if(iter->second.isDefined())
+ {
+ ostr << "=" << escapeQueryValue(iter->second.asString());
+ }
+ }
+ query_string = ostr.str();
+ }
+ return query_string;
+}
+
+bool operator!=(const LLURI& first, const LLURI& second)
+{
+ return (first.asString() != second.asString());
+}
diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index 95c119dee9..cc59c6b370 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -1,193 +1,193 @@ -/** - * @file lluri.h - * @author Phoenix - * @date 2006-02-05 - * @brief Declaration of the URI class. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLURI_H -#define LL_LLURI_H - -#include <string> - -class LLSD; -class LLUUID; -class LLApp; - -/** - * - * LLURI instances are immutable - * See: http://www.ietf.org/rfc/rfc3986.txt - * - */ -class LL_COMMON_API LLURI -{ -public: - LLURI(); - LLURI(const std::string& escaped_str); - LLURI(const std::string& scheme, - const std::string& userName, - const std::string& password, - const std::string& hostName, - U16 hostPort, - const std::string& escapedPath, - const std::string& escapedQuery); - - // construct from escaped string, as would be transmitted on the net - - ~LLURI(); - - static LLURI buildHTTP( - const std::string& prefix, - const LLSD& path); - - static LLURI buildHTTP( - const std::string& prefix, - const LLSD& path, - const LLSD& query); - ///< prefix is either a full URL prefix of the form - /// "http://example.com:8080", or it can be simply a host and - /// optional port like "example.com" or "example.com:8080", in - /// these cases, the "http://" will be added - - static LLURI buildHTTP( - const std::string& host, - const U32& port, - const LLSD& path); - static LLURI buildHTTP( - const std::string& host, - const U32& port, - const LLSD& path, - const LLSD& query); - - std::string asString() const; - ///< the whole URI, escaped as needed - - /** @name Parts of a URI */ - //@{ - // These functions return parts of the decoded URI. The returned - // strings are un-escaped as needed - - // for all schemes - std::string scheme() const; ///< ex.: "http", note lack of colon - std::string opaque() const; ///< everything after the colon - - // for schemes that follow path like syntax (http, https, ftp) - std::string authority() const; // ex.: "host.com:80" - std::string hostName() const; // ex.: "host.com" - std::string userName() const; - std::string password() const; - U16 hostPort() const; // ex.: 80, will include implicit port - BOOL defaultPort() const; // true if port is default for scheme - const std::string& escapedPath() const { return mEscapedPath; } - std::string path() const; // ex.: "/abc/def", includes leading slash - LLSD pathArray() const; // above decoded into an array of strings - std::string query() const; // ex.: "x=34", section after "?" - const std::string& escapedQuery() const { return mEscapedQuery; } - LLSD queryMap() const; // above decoded into a map - static LLSD queryMap(std::string escaped_query_string); - - /** - * @brief given a name value map, return a serialized query string. - * - - * @param query_map a map of name value. every value must be - * representable as a string. - * @return Returns an url query string of '?n1=v1&n2=v2&...' - */ - static std::string mapToQueryString(const LLSD& query_map); - - /** @name Escaping Utilities */ - //@{ - /** - * @brief 'Escape' symbol into stream - * - * @param ostr Output stream. - * @param val Symbol to encode. - */ - static void encodeCharacter(std::ostream& ostr, std::string::value_type val); - - /** - * @brief Escape the string passed except for unreserved - * - * ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz - * 0123456789 - * -._~ - * - * @see http://www.ietf.org/rfc/rfc1738.txt - * - * @param str The raw URI to escape. - * @return Returns the rfc 1738 escaped uri or an empty string. - */ - static std::string escape(const std::string& str); - - /** - * @brief Escape a string with a specified set of allowed characters. - * - * Escape a string by urlencoding all the characters that aren't - * in the allowed string. - * @param str The raw URI to escape. - * @param allowed Character array of allowed characters - * @param is_allowed_sorted Optimization hint if allowed array is sorted. - * @return Returns the escaped uri or an empty string. - */ - static std::string escape( - const std::string& str, - const std::string& allowed, - bool is_allowed_sorted = false); - - /** - * @brief Break string into data part and path or sheme - * and escape path (if present) and data. - * Data part is not allowed to have path related symbols - * @param str The raw URI to escape. - */ - static std::string escapePathAndData(const std::string &str); - - /** - * @brief unescape an escaped URI string. - * - * @param str The escped URI to unescape. - * @return Returns the unescaped uri or an empty string. - */ - static std::string unescape(const std::string& str); - //@} - -private: - // only "http", "https", "ftp", and "secondlife" schemes are parsed - // secondlife scheme parses authority as "" and includes it as part of - // the path. See lluri_tut.cpp - // i.e. secondlife://app/login has mAuthority = "" and mPath = "/app/login" - void parseAuthorityAndPathUsingOpaque(); - std::string mScheme; - std::string mEscapedOpaque; - std::string mEscapedAuthority; - std::string mEscapedPath; - std::string mEscapedQuery; -}; - -// this operator required for tut -LL_COMMON_API bool operator!=(const LLURI& first, const LLURI& second); - -#endif // LL_LLURI_H +/**
+ * @file lluri.h
+ * @author Phoenix
+ * @date 2006-02-05
+ * @brief Declaration of the URI class.
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLURI_H
+#define LL_LLURI_H
+
+#include <string>
+
+class LLSD;
+class LLUUID;
+class LLApp;
+
+/**
+ *
+ * LLURI instances are immutable
+ * See: http://www.ietf.org/rfc/rfc3986.txt
+ *
+ */
+class LL_COMMON_API LLURI
+{
+public:
+ LLURI();
+ LLURI(const std::string& escaped_str);
+ LLURI(const std::string& scheme,
+ const std::string& userName,
+ const std::string& password,
+ const std::string& hostName,
+ U16 hostPort,
+ const std::string& escapedPath,
+ const std::string& escapedQuery);
+
+ // construct from escaped string, as would be transmitted on the net
+
+ ~LLURI();
+
+ static LLURI buildHTTP(
+ const std::string& prefix,
+ const LLSD& path);
+
+ static LLURI buildHTTP(
+ const std::string& prefix,
+ const LLSD& path,
+ const LLSD& query);
+ ///< prefix is either a full URL prefix of the form
+ /// "http://example.com:8080", or it can be simply a host and
+ /// optional port like "example.com" or "example.com:8080", in
+ /// these cases, the "http://" will be added
+
+ static LLURI buildHTTP(
+ const std::string& host,
+ const U32& port,
+ const LLSD& path);
+ static LLURI buildHTTP(
+ const std::string& host,
+ const U32& port,
+ const LLSD& path,
+ const LLSD& query);
+
+ std::string asString() const;
+ ///< the whole URI, escaped as needed
+
+ /** @name Parts of a URI */
+ //@{
+ // These functions return parts of the decoded URI. The returned
+ // strings are un-escaped as needed
+
+ // for all schemes
+ std::string scheme() const; ///< ex.: "http", note lack of colon
+ std::string opaque() const; ///< everything after the colon
+
+ // for schemes that follow path like syntax (http, https, ftp)
+ std::string authority() const; // ex.: "host.com:80"
+ std::string hostName() const; // ex.: "host.com"
+ std::string userName() const;
+ std::string password() const;
+ U16 hostPort() const; // ex.: 80, will include implicit port
+ bool defaultPort() const; // true if port is default for scheme
+ const std::string& escapedPath() const { return mEscapedPath; }
+ std::string path() const; // ex.: "/abc/def", includes leading slash
+ LLSD pathArray() const; // above decoded into an array of strings
+ std::string query() const; // ex.: "x=34", section after "?"
+ const std::string& escapedQuery() const { return mEscapedQuery; }
+ LLSD queryMap() const; // above decoded into a map
+ static LLSD queryMap(std::string escaped_query_string);
+
+ /**
+ * @brief given a name value map, return a serialized query string.
+ *
+
+ * @param query_map a map of name value. every value must be
+ * representable as a string.
+ * @return Returns an url query string of '?n1=v1&n2=v2&...'
+ */
+ static std::string mapToQueryString(const LLSD& query_map);
+
+ /** @name Escaping Utilities */
+ //@{
+ /**
+ * @brief 'Escape' symbol into stream
+ *
+ * @param ostr Output stream.
+ * @param val Symbol to encode.
+ */
+ static void encodeCharacter(std::ostream& ostr, std::string::value_type val);
+
+ /**
+ * @brief Escape the string passed except for unreserved
+ *
+ * ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
+ * 0123456789
+ * -._~
+ *
+ * @see http://www.ietf.org/rfc/rfc1738.txt
+ *
+ * @param str The raw URI to escape.
+ * @return Returns the rfc 1738 escaped uri or an empty string.
+ */
+ static std::string escape(const std::string& str);
+
+ /**
+ * @brief Escape a string with a specified set of allowed characters.
+ *
+ * Escape a string by urlencoding all the characters that aren't
+ * in the allowed string.
+ * @param str The raw URI to escape.
+ * @param allowed Character array of allowed characters
+ * @param is_allowed_sorted Optimization hint if allowed array is sorted.
+ * @return Returns the escaped uri or an empty string.
+ */
+ static std::string escape(
+ const std::string& str,
+ const std::string& allowed,
+ bool is_allowed_sorted = false);
+
+ /**
+ * @brief Break string into data part and path or sheme
+ * and escape path (if present) and data.
+ * Data part is not allowed to have path related symbols
+ * @param str The raw URI to escape.
+ */
+ static std::string escapePathAndData(const std::string &str);
+
+ /**
+ * @brief unescape an escaped URI string.
+ *
+ * @param str The escped URI to unescape.
+ * @return Returns the unescaped uri or an empty string.
+ */
+ static std::string unescape(const std::string& str);
+ //@}
+
+private:
+ // only "http", "https", "ftp", and "secondlife" schemes are parsed
+ // secondlife scheme parses authority as "" and includes it as part of
+ // the path. See lluri_tut.cpp
+ // i.e. secondlife://app/login has mAuthority = "" and mPath = "/app/login"
+ void parseAuthorityAndPathUsingOpaque();
+ std::string mScheme;
+ std::string mEscapedOpaque;
+ std::string mEscapedAuthority;
+ std::string mEscapedPath;
+ std::string mEscapedQuery;
+};
+
+// this operator required for tut
+LL_COMMON_API bool operator!=(const LLURI& first, const LLURI& second);
+
+#endif // LL_LLURI_H
diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index ab66fa6ddc..f3821de71b 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -1,1077 +1,1077 @@ -/** - * @file lluuid.cpp - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - - // We can't use WIN32_LEAN_AND_MEAN here, needs lots of includes. -#if LL_WINDOWS -#include "llwin32headers.h" -// ugh, this is ugly. We need to straighten out our linking for this library -#pragma comment(lib, "IPHLPAPI.lib") -#include <iphlpapi.h> -#endif - -#include "llapp.h" -#include "lldefs.h" -#include "llerror.h" - -#include "lluuid.h" -#include "llerror.h" -#include "llrand.h" -#include "llstring.h" -#include "lltimer.h" -#include "llthread.h" -#include "llmutex.h" -#include "llmd5.h" -#include "hbxxh.h" - -const LLUUID LLUUID::null; -const LLTransactionID LLTransactionID::tnull; - -// static -LLMutex* LLUUID::mMutex = NULL; - - - -/* - -NOT DONE YET!!! - -static char BASE85_TABLE[] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', - 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', - 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', - 'y', 'z', '!', '#', '$', '%', '&', '(', ')', '*', - '+', '-', ';', '[', '=', '>', '?', '@', '^', '_', - '`', '{', '|', '}', '~', '\0' -}; - - -void encode( char * fiveChars, unsigned int word ) throw( ) -{ -for( int ix = 0; ix < 5; ++ix ) { -fiveChars[4-ix] = encodeTable[ word % 85]; -word /= 85; -} -} - -To decode: -unsigned int decode( char const * fiveChars ) throw( bad_input_data ) -{ -unsigned int ret = 0; -for( int ix = 0; ix < 5; ++ix ) { -char * s = strchr( encodeTable, fiveChars[ ix ] ); -if( s == 0 ) LLTHROW(bad_input_data()); -ret = ret * 85 + (s-encodeTable); -} -return ret; -} - -void LLUUID::toBase85(char* out) -{ - U32* me = (U32*)&(mData[0]); - for(S32 i = 0; i < 4; ++i) - { - char* o = &out[i*i]; - for(S32 j = 0; j < 5; ++j) - { - o[4-j] = BASE85_TABLE[ me[i] % 85]; - word /= 85; - } - } -} - -unsigned int decode( char const * fiveChars ) throw( bad_input_data ) -{ - unsigned int ret = 0; - for( S32 ix = 0; ix < 5; ++ix ) - { - char * s = strchr( encodeTable, fiveChars[ ix ] ); - ret = ret * 85 + (s-encodeTable); - } - return ret; -} -*/ - -#define LL_USE_JANKY_RANDOM_NUMBER_GENERATOR 0 -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR -/** - * @brief a global for - */ -static U64 sJankyRandomSeed(LLUUID::getRandomSeed()); - -/** - * @brief generate a random U32. - */ -U32 janky_fast_random_bytes() -{ - sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); - return (U32)sJankyRandomSeed; -} - -/** - * @brief generate a random U32 from [0, val) - */ -U32 janky_fast_random_byes_range(U32 val) -{ - sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); - return (U32)(sJankyRandomSeed) % val; -} - -/** - * @brief generate a random U32 from [0, val) - */ -U32 janky_fast_random_seeded_bytes(U32 seed, U32 val) -{ - seed = U64L(1664525) * (U64)(seed)+U64L(1013904223); - return (U32)(seed) % val; -} -#endif - -// Common to all UUID implementations -void LLUUID::toString(std::string& out) const -{ - out = llformat( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - (U8)(mData[0]), - (U8)(mData[1]), - (U8)(mData[2]), - (U8)(mData[3]), - (U8)(mData[4]), - (U8)(mData[5]), - (U8)(mData[6]), - (U8)(mData[7]), - (U8)(mData[8]), - (U8)(mData[9]), - (U8)(mData[10]), - (U8)(mData[11]), - (U8)(mData[12]), - (U8)(mData[13]), - (U8)(mData[14]), - (U8)(mData[15])); -} - -// *TODO: deprecate -void LLUUID::toString(char* out) const -{ - std::string buffer; - toString(buffer); - strcpy(out, buffer.c_str()); /* Flawfinder: ignore */ -} - -void LLUUID::toCompressedString(std::string& out) const -{ - char bytes[UUID_BYTES + 1]; - memcpy(bytes, mData, UUID_BYTES); /* Flawfinder: ignore */ - bytes[UUID_BYTES] = '\0'; - out.assign(bytes, UUID_BYTES); -} - -// *TODO: deprecate -void LLUUID::toCompressedString(char* out) const -{ - memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */ - out[UUID_BYTES] = '\0'; -} - -std::string LLUUID::getString() const -{ - return asString(); -} - -std::string LLUUID::asString() const -{ - std::string str; - toString(str); - return str; -} - -BOOL LLUUID::set(const char* in_string, BOOL emit) -{ - return set(ll_safe_string(in_string), emit); -} - -BOOL LLUUID::set(const std::string& in_string, BOOL emit) -{ - BOOL broken_format = FALSE; - - // empty strings should make NULL uuid - if (in_string.empty()) - { - setNull(); - return TRUE; - } - - if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ - { - // I'm a moron. First implementation didn't have the right UUID format. - // Shouldn't see any of these any more - if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ - { - if (emit) - { - LL_WARNS() << "Warning! Using broken UUID string format" << LL_ENDL; - } - broken_format = TRUE; - } - else - { - // Bad UUID string. Spam as INFO, as most cases we don't care. - if (emit) - { - //don't spam the logs because a resident can't spell. - LL_WARNS() << "Bad UUID string: " << in_string << LL_ENDL; - } - setNull(); - return FALSE; - } - } - - U8 cur_pos = 0; - S32 i; - for (i = 0; i < UUID_BYTES; i++) - { - if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) - { - cur_pos++; - if (broken_format && (i == 10)) - { - // Missing - in the broken format - cur_pos--; - } - } - - mData[i] = 0; - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - mData[i] += (U8)(in_string[cur_pos] - '0'); - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); - } - else - { - if (emit) - { - LL_WARNS() << "Invalid UUID string character" << LL_ENDL; - } - setNull(); - return FALSE; - } - - mData[i] = mData[i] << 4; - cur_pos++; - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - mData[i] += (U8)(in_string[cur_pos] - '0'); - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); - } - else - { - if (emit) - { - LL_WARNS() << "Invalid UUID string character" << LL_ENDL; - } - setNull(); - return FALSE; - } - cur_pos++; - } - - return TRUE; -} - -BOOL LLUUID::validate(const std::string& in_string) -{ - BOOL broken_format = FALSE; - if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ - { - // I'm a moron. First implementation didn't have the right UUID format. - if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ - { - broken_format = TRUE; - } - else - { - return FALSE; - } - } - - U8 cur_pos = 0; - for (U32 i = 0; i < 16; i++) - { - if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) - { - cur_pos++; - if (broken_format && (i == 10)) - { - // Missing - in the broken format - cur_pos--; - } - } - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) - { - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) - { - } - else - { - return FALSE; - } - - cur_pos++; - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) - { - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) - { - } - else - { - return FALSE; - } - cur_pos++; - } - return TRUE; -} - -const LLUUID& LLUUID::operator^=(const LLUUID& rhs) -{ - U32* me = (U32*)&(mData[0]); - const U32* other = (U32*)&(rhs.mData[0]); - for (S32 i = 0; i < 4; ++i) - { - me[i] = me[i] ^ other[i]; - } - return *this; -} - -LLUUID LLUUID::operator^(const LLUUID& rhs) const -{ - LLUUID id(*this); - id ^= rhs; - return id; -} - -// WARNING: this algorithm SHALL NOT be changed. It is also used by the server -// and plays a role in some assets validation (e.g. clothing items). Changing -// it would cause invalid assets. -void LLUUID::combine(const LLUUID& other, LLUUID& result) const -{ - LLMD5 md5_uuid; - md5_uuid.update((unsigned char*)mData, 16); - md5_uuid.update((unsigned char*)other.mData, 16); - md5_uuid.finalize(); - md5_uuid.raw_digest(result.mData); -} - -LLUUID LLUUID::combine(const LLUUID& other) const -{ - LLUUID combination; - combine(other, combination); - return combination; -} - -std::ostream& operator<<(std::ostream& s, const LLUUID& uuid) -{ - std::string uuid_str; - uuid.toString(uuid_str); - s << uuid_str; - return s; -} - -std::istream& operator>>(std::istream& s, LLUUID& uuid) -{ - U32 i; - char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */ - for (i = 0; i < UUID_STR_LENGTH - 1; i++) - { - s >> uuid_str[i]; - } - uuid_str[i] = '\0'; - uuid.set(std::string(uuid_str)); - return s; -} - -static void get_random_bytes(void* buf, int nbytes) -{ - int i; - char* cp = (char*)buf; - - // *NOTE: If we are not using the janky generator ll_rand() - // generates at least 3 good bytes of data since it is 0 to - // RAND_MAX. This could be made more efficient by copying all the - // bytes. - for (i = 0; i < nbytes; i++) -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - * cp++ = janky_fast_random_bytes() & 0xFF; -#else - * cp++ = ll_rand() & 0xFF; -#endif - return; -} - -#if LL_WINDOWS - -typedef struct _ASTAT_ -{ - ADAPTER_STATUS adapt; - NAME_BUFFER NameBuff[30]; -}ASTAT, * PASTAT; - -// static -S32 LLUUID::getNodeID(unsigned char* node_id) -{ - ASTAT Adapter; - NCB Ncb; - UCHAR uRetCode; - LANA_ENUM lenum; - int i; - int retval = 0; - - memset(&Ncb, 0, sizeof(Ncb)); - Ncb.ncb_command = NCBENUM; - Ncb.ncb_buffer = (UCHAR*)&lenum; - Ncb.ncb_length = sizeof(lenum); - uRetCode = Netbios(&Ncb); - - for (i = 0; i < lenum.length; i++) - { - memset(&Ncb, 0, sizeof(Ncb)); - Ncb.ncb_command = NCBRESET; - Ncb.ncb_lana_num = lenum.lana[i]; - - uRetCode = Netbios(&Ncb); - - memset(&Ncb, 0, sizeof(Ncb)); - Ncb.ncb_command = NCBASTAT; - Ncb.ncb_lana_num = lenum.lana[i]; - - strcpy((char*)Ncb.ncb_callname, "* "); /* Flawfinder: ignore */ - Ncb.ncb_buffer = (unsigned char*)&Adapter; - Ncb.ncb_length = sizeof(Adapter); - - uRetCode = Netbios(&Ncb); - if (uRetCode == 0) - { - memcpy(node_id, Adapter.adapt.adapter_address, 6); /* Flawfinder: ignore */ - retval = 1; - } - } - return retval; -} - -#elif LL_DARWIN -// Mac OS X version of the UUID generation code... -/* - * Get an ethernet hardware address, if we can find it... - */ -#include <unistd.h> -#include <sys/types.h> -#include <sys/time.h> -#include <sys/socket.h> -#include <sys/ioctl.h> -#include <net/if.h> -#include <net/if_types.h> -#include <net/if_dl.h> -#include <net/route.h> -#include <ifaddrs.h> - - // static -S32 LLUUID::getNodeID(unsigned char* node_id) -{ - int i; - unsigned char* a = NULL; - struct ifaddrs* ifap, * ifa; - int rv; - S32 result = 0; - - if ((rv = getifaddrs(&ifap)) == -1) - { - return -1; - } - if (ifap == NULL) - { - return -1; - } - - for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) - { - // printf("Interface %s, address family %d, ", ifa->ifa_name, ifa->ifa_addr->sa_family); - for (i = 0; i < ifa->ifa_addr->sa_len; i++) - { - // printf("%02X ", (unsigned char)ifa->ifa_addr->sa_data[i]); - } - // printf("\n"); - - if (ifa->ifa_addr->sa_family == AF_LINK) - { - // This is a link-level address - struct sockaddr_dl* lla = (struct sockaddr_dl*)ifa->ifa_addr; - - // printf("\tLink level address, type %02X\n", lla->sdl_type); - - if (lla->sdl_type == IFT_ETHER) - { - // Use the first ethernet MAC in the list. - // For some reason, the macro LLADDR() defined in net/if_dl.h doesn't expand correctly. This is what it would do. - a = (unsigned char*)&((lla)->sdl_data); - a += (lla)->sdl_nlen; - - if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) - { - continue; - } - - if (node_id) - { - memcpy(node_id, a, 6); - result = 1; - } - - // We found one. - break; - } - } - } - freeifaddrs(ifap); - - return result; -} - -#else - -// Linux version of the UUID generation code... -/* - * Get the ethernet hardware address, if we can find it... - */ -#include <unistd.h> -#include <fcntl.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/time.h> -#include <sys/stat.h> -#include <sys/file.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <net/if.h> -#define HAVE_NETINET_IN_H -#ifdef HAVE_NETINET_IN_H -#include <netinet/in.h> -#if !LL_DARWIN -#include <linux/sockios.h> -#endif -#endif - - // static -S32 LLUUID::getNodeID(unsigned char* node_id) -{ - int sd; - struct ifreq ifr, * ifrp; - struct ifconf ifc; - char buf[1024]; - int n, i; - unsigned char* a; - - /* - * BSD 4.4 defines the size of an ifreq to be - * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len - * However, under earlier systems, sa_len isn't present, so the size is - * just sizeof(struct ifreq) - */ -#ifdef HAVE_SA_LEN -#ifndef max -#define max(a,b) ((a) > (b) ? (a) : (b)) -#endif -#define ifreq_size(i) max(sizeof(struct ifreq),\ - sizeof((i).ifr_name)+(i).ifr_addr.sa_len) -#else -#define ifreq_size(i) sizeof(struct ifreq) -#endif /* HAVE_SA_LEN*/ - - sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (sd < 0) { - return -1; - } - memset(buf, 0, sizeof(buf)); - ifc.ifc_len = sizeof(buf); - ifc.ifc_buf = buf; - if (ioctl(sd, SIOCGIFCONF, (char*)&ifc) < 0) { - close(sd); - return -1; - } - n = ifc.ifc_len; - for (i = 0; i < n; i += ifreq_size(*ifr)) { - ifrp = (struct ifreq*)((char*)ifc.ifc_buf + i); - strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); /* Flawfinder: ignore */ -#ifdef SIOCGIFHWADDR - if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) - continue; - a = (unsigned char*)&ifr.ifr_hwaddr.sa_data; -#else -#ifdef SIOCGENADDR - if (ioctl(sd, SIOCGENADDR, &ifr) < 0) - continue; - a = (unsigned char*)ifr.ifr_enaddr; -#else - /* - * XXX we don't have a way of getting the hardware - * address - */ - close(sd); - return 0; -#endif /* SIOCGENADDR */ -#endif /* SIOCGIFHWADDR */ - if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) - continue; - if (node_id) { - memcpy(node_id, a, 6); /* Flawfinder: ignore */ - close(sd); - return 1; - } - } - close(sd); - return 0; -} - -#endif - -S32 LLUUID::cmpTime(uuid_time_t* t1, uuid_time_t* t2) -{ - // Compare two time values. - - if (t1->high < t2->high) return -1; - if (t1->high > t2->high) return 1; - if (t1->low < t2->low) return -1; - if (t1->low > t2->low) return 1; - return 0; -} - -void LLUUID::getSystemTime(uuid_time_t* timestamp) -{ - // Get system time with 100ns precision. Time is since Oct 15, 1582. -#if LL_WINDOWS - ULARGE_INTEGER time; - GetSystemTimeAsFileTime((FILETIME*)&time); - // NT keeps time in FILETIME format which is 100ns ticks since - // Jan 1, 1601. UUIDs use time in 100ns ticks since Oct 15, 1582. - // The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec) - // + 18 years and 5 leap days. - time.QuadPart += - (unsigned __int64)(1000 * 1000 * 10) // seconds - * (unsigned __int64)(60 * 60 * 24) // days - * (unsigned __int64)(17 + 30 + 31 + 365 * 18 + 5); // # of days - - timestamp->high = time.HighPart; - timestamp->low = time.LowPart; -#else - struct timeval tp; - gettimeofday(&tp, 0); - - // Offset between UUID formatted times and Unix formatted times. - // UUID UTC base time is October 15, 1582. - // Unix base time is January 1, 1970. - U64 uuid_time = ((U64)tp.tv_sec * 10000000) + (tp.tv_usec * 10) + - U64L(0x01B21DD213814000); - timestamp->high = (U32)(uuid_time >> 32); - timestamp->low = (U32)(uuid_time & 0xFFFFFFFF); -#endif -} - -void LLUUID::getCurrentTime(uuid_time_t* timestamp) -{ - // Get current time as 60 bit 100ns ticks since whenever. - // Compensate for the fact that real clock resolution is less - // than 100ns. - - const U32 uuids_per_tick = 1024; - - static uuid_time_t time_last; - static U32 uuids_this_tick; - static BOOL init = FALSE; - - if (!init) { - getSystemTime(&time_last); - uuids_this_tick = uuids_per_tick; - init = TRUE; - mMutex = new LLMutex(); - } - - uuid_time_t time_now = { 0,0 }; - - while (1) { - getSystemTime(&time_now); - - // if clock reading changed since last UUID generated - if (cmpTime(&time_last, &time_now)) { - // reset count of uuid's generated with this clock reading - uuids_this_tick = 0; - break; - } - if (uuids_this_tick < uuids_per_tick) { - uuids_this_tick++; - break; - } - // going too fast for our clock; spin - } - - time_last = time_now; - - if (uuids_this_tick != 0) { - if (time_now.low & 0x80000000) { - time_now.low += uuids_this_tick; - if (!(time_now.low & 0x80000000)) - time_now.high++; - } - else - time_now.low += uuids_this_tick; - } - - timestamp->high = time_now.high; - timestamp->low = time_now.low; -} - -void LLUUID::generate() -{ - // Create a UUID. - uuid_time_t timestamp; - - static unsigned char node_id[6]; /* Flawfinder: ignore */ - static int has_init = 0; - - // Create a UUID. - static uuid_time_t time_last = { 0,0 }; - static U16 clock_seq = 0; -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - static U32 seed = 0L; // dummy seed. reset it below -#endif - if (!has_init) - { - has_init = 1; - if (getNodeID(node_id) <= 0) - { - get_random_bytes(node_id, 6); - /* - * Set multicast bit, to prevent conflicts - * with IEEE 802 addresses obtained from - * network cards - */ - node_id[0] |= 0x80; - } - - getCurrentTime(&time_last); -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - seed = time_last.low; -#endif - -#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - clock_seq = (U16)janky_fast_random_seeded_bytes(seed, 65536); -#else - clock_seq = (U16)ll_rand(65536); -#endif - } - - // get current time - getCurrentTime(×tamp); - U16 our_clock_seq = clock_seq; - - // if clock hasn't changed or went backward, change clockseq - if (cmpTime(×tamp, &time_last) != 1) - { - LLMutexLock lock(mMutex); - clock_seq = (clock_seq + 1) & 0x3FFF; - if (clock_seq == 0) - clock_seq++; - our_clock_seq = clock_seq; // Ensure we're using a different clock_seq value from previous time - } - - time_last = timestamp; - - memcpy(mData + 10, node_id, 6); /* Flawfinder: ignore */ - U32 tmp; - tmp = timestamp.low; - mData[3] = (unsigned char)tmp; - tmp >>= 8; - mData[2] = (unsigned char)tmp; - tmp >>= 8; - mData[1] = (unsigned char)tmp; - tmp >>= 8; - mData[0] = (unsigned char)tmp; - - tmp = (U16)timestamp.high; - mData[5] = (unsigned char)tmp; - tmp >>= 8; - mData[4] = (unsigned char)tmp; - - tmp = (timestamp.high >> 16) | 0x1000; - mData[7] = (unsigned char)tmp; - tmp >>= 8; - mData[6] = (unsigned char)tmp; - - tmp = our_clock_seq; - - mData[9] = (unsigned char)tmp; - tmp >>= 8; - mData[8] = (unsigned char)tmp; - - HBXXH128::digest(*this, (const void*)mData, 16); -} - -void LLUUID::generate(const std::string& hash_string) -{ - HBXXH128::digest(*this, hash_string); -} - -U32 LLUUID::getRandomSeed() -{ - static unsigned char seed[16]; /* Flawfinder: ignore */ - - getNodeID(&seed[0]); - - // Incorporate the pid into the seed to prevent - // processes that start on the same host at the same - // time from generating the same seed. - pid_t pid = LLApp::getPid(); - - seed[6] = (unsigned char)(pid >> 8); - seed[7] = (unsigned char)(pid); - getSystemTime((uuid_time_t*)(&seed[8])); - - U64 seed64 = HBXXH64::digest((const void*)seed, 16); - return U32(seed64) ^ U32(seed64 >> 32); -} - -BOOL LLUUID::parseUUID(const std::string& buf, LLUUID* value) -{ - if (buf.empty() || value == NULL) - { - return FALSE; - } - - std::string temp(buf); - LLStringUtil::trim(temp); - if (LLUUID::validate(temp)) - { - value->set(temp); - return TRUE; - } - return FALSE; -} - -//static -LLUUID LLUUID::generateNewID(std::string hash_string) -{ - LLUUID new_id; - if (hash_string.empty()) - { - new_id.generate(); - } - else - { - new_id.generate(hash_string); - } - return new_id; -} - -LLAssetID LLTransactionID::makeAssetID(const LLUUID& session) const -{ - LLAssetID result; - if (isNull()) - { - result.setNull(); - } - else - { - combine(session, result); - } - return result; -} - -// Construct -LLUUID::LLUUID() -{ - setNull(); -} - - -// Faster than copying from memory -void LLUUID::setNull() -{ - U32* word = (U32*)mData; - word[0] = 0; - word[1] = 0; - word[2] = 0; - word[3] = 0; -} - - -// Compare -bool LLUUID::operator==(const LLUUID& rhs) const -{ - U32* tmp = (U32*)mData; - U32* rhstmp = (U32*)rhs.mData; - // Note: binary & to avoid branching - return - (tmp[0] == rhstmp[0]) & - (tmp[1] == rhstmp[1]) & - (tmp[2] == rhstmp[2]) & - (tmp[3] == rhstmp[3]); -} - - -bool LLUUID::operator!=(const LLUUID& rhs) const -{ - U32* tmp = (U32*)mData; - U32* rhstmp = (U32*)rhs.mData; - // Note: binary | to avoid branching - return - (tmp[0] != rhstmp[0]) | - (tmp[1] != rhstmp[1]) | - (tmp[2] != rhstmp[2]) | - (tmp[3] != rhstmp[3]); -} - -/* -// JC: This is dangerous. It allows UUIDs to be cast automatically -// to integers, among other things. Use isNull() or notNull(). - LLUUID::operator bool() const -{ - U32 *word = (U32 *)mData; - return (word[0] | word[1] | word[2] | word[3]) > 0; -} -*/ - -BOOL LLUUID::notNull() const -{ - U32* word = (U32*)mData; - return (word[0] | word[1] | word[2] | word[3]) > 0; -} - -// Faster than == LLUUID::null because doesn't require -// as much memory access. -BOOL LLUUID::isNull() const -{ - U32* word = (U32*)mData; - // If all bits are zero, return !0 == TRUE - return !(word[0] | word[1] | word[2] | word[3]); -} - -LLUUID::LLUUID(const char* in_string) -{ - if (!in_string || in_string[0] == 0) - { - setNull(); - return; - } - - set(in_string); -} - -LLUUID::LLUUID(const std::string& in_string) -{ - if (in_string.empty()) - { - setNull(); - return; - } - - set(in_string); -} - -// IW: DON'T "optimize" these w/ U32s or you'll scoogie the sort order -// IW: this will make me very sad -bool LLUUID::operator<(const LLUUID& rhs) const -{ - U32 i; - for (i = 0; i < (UUID_BYTES - 1); i++) - { - if (mData[i] != rhs.mData[i]) - { - return (mData[i] < rhs.mData[i]); - } - } - return (mData[UUID_BYTES - 1] < rhs.mData[UUID_BYTES - 1]); -} - -bool LLUUID::operator>(const LLUUID& rhs) const -{ - U32 i; - for (i = 0; i < (UUID_BYTES - 1); i++) - { - if (mData[i] != rhs.mData[i]) - { - return (mData[i] > rhs.mData[i]); - } - } - return (mData[UUID_BYTES - 1] > rhs.mData[UUID_BYTES - 1]); -} - -U16 LLUUID::getCRC16() const -{ - // A UUID is 16 bytes, or 8 shorts. - U16* short_data = (U16*)mData; - U16 out = 0; - out += short_data[0]; - out += short_data[1]; - out += short_data[2]; - out += short_data[3]; - out += short_data[4]; - out += short_data[5]; - out += short_data[6]; - out += short_data[7]; - return out; -} - -U32 LLUUID::getCRC32() const -{ - U32* tmp = (U32*)mData; - return tmp[0] + tmp[1] + tmp[2] + tmp[3]; -} +/**
+ * @file lluuid.cpp
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+ // We can't use WIN32_LEAN_AND_MEAN here, needs lots of includes.
+#if LL_WINDOWS
+#include "llwin32headers.h"
+// ugh, this is ugly. We need to straighten out our linking for this library
+#pragma comment(lib, "IPHLPAPI.lib")
+#include <iphlpapi.h>
+#endif
+
+#include "llapp.h"
+#include "lldefs.h"
+#include "llerror.h"
+
+#include "lluuid.h"
+#include "llerror.h"
+#include "llrand.h"
+#include "llstring.h"
+#include "lltimer.h"
+#include "llthread.h"
+#include "llmutex.h"
+#include "llmd5.h"
+#include "hbxxh.h"
+
+const LLUUID LLUUID::null;
+const LLTransactionID LLTransactionID::tnull;
+
+// static
+LLMutex* LLUUID::mMutex = NULL;
+
+
+
+/*
+
+NOT DONE YET!!!
+
+static char BASE85_TABLE[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '!', '#', '$', '%', '&', '(', ')', '*',
+ '+', '-', ';', '[', '=', '>', '?', '@', '^', '_',
+ '`', '{', '|', '}', '~', '\0'
+};
+
+
+void encode( char * fiveChars, unsigned int word ) throw( )
+{
+for( int ix = 0; ix < 5; ++ix ) {
+fiveChars[4-ix] = encodeTable[ word % 85];
+word /= 85;
+}
+}
+
+To decode:
+unsigned int decode( char const * fiveChars ) throw( bad_input_data )
+{
+unsigned int ret = 0;
+for( int ix = 0; ix < 5; ++ix ) {
+char * s = strchr( encodeTable, fiveChars[ ix ] );
+if( s == 0 ) LLTHROW(bad_input_data());
+ret = ret * 85 + (s-encodeTable);
+}
+return ret;
+}
+
+void LLUUID::toBase85(char* out)
+{
+ U32* me = (U32*)&(mData[0]);
+ for(S32 i = 0; i < 4; ++i)
+ {
+ char* o = &out[i*i];
+ for(S32 j = 0; j < 5; ++j)
+ {
+ o[4-j] = BASE85_TABLE[ me[i] % 85];
+ word /= 85;
+ }
+ }
+}
+
+unsigned int decode( char const * fiveChars ) throw( bad_input_data )
+{
+ unsigned int ret = 0;
+ for( S32 ix = 0; ix < 5; ++ix )
+ {
+ char * s = strchr( encodeTable, fiveChars[ ix ] );
+ ret = ret * 85 + (s-encodeTable);
+ }
+ return ret;
+}
+*/
+
+#define LL_USE_JANKY_RANDOM_NUMBER_GENERATOR 0
+#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
+/**
+ * @brief a global for
+ */
+static U64 sJankyRandomSeed(LLUUID::getRandomSeed());
+
+/**
+ * @brief generate a random U32.
+ */
+U32 janky_fast_random_bytes()
+{
+ sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223);
+ return (U32)sJankyRandomSeed;
+}
+
+/**
+ * @brief generate a random U32 from [0, val)
+ */
+U32 janky_fast_random_byes_range(U32 val)
+{
+ sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223);
+ return (U32)(sJankyRandomSeed) % val;
+}
+
+/**
+ * @brief generate a random U32 from [0, val)
+ */
+U32 janky_fast_random_seeded_bytes(U32 seed, U32 val)
+{
+ seed = U64L(1664525) * (U64)(seed)+U64L(1013904223);
+ return (U32)(seed) % val;
+}
+#endif
+
+// Common to all UUID implementations
+void LLUUID::toString(std::string& out) const
+{
+ out = llformat(
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ (U8)(mData[0]),
+ (U8)(mData[1]),
+ (U8)(mData[2]),
+ (U8)(mData[3]),
+ (U8)(mData[4]),
+ (U8)(mData[5]),
+ (U8)(mData[6]),
+ (U8)(mData[7]),
+ (U8)(mData[8]),
+ (U8)(mData[9]),
+ (U8)(mData[10]),
+ (U8)(mData[11]),
+ (U8)(mData[12]),
+ (U8)(mData[13]),
+ (U8)(mData[14]),
+ (U8)(mData[15]));
+}
+
+// *TODO: deprecate
+void LLUUID::toString(char* out) const
+{
+ std::string buffer;
+ toString(buffer);
+ strcpy(out, buffer.c_str()); /* Flawfinder: ignore */
+}
+
+void LLUUID::toCompressedString(std::string& out) const
+{
+ char bytes[UUID_BYTES + 1];
+ memcpy(bytes, mData, UUID_BYTES); /* Flawfinder: ignore */
+ bytes[UUID_BYTES] = '\0';
+ out.assign(bytes, UUID_BYTES);
+}
+
+// *TODO: deprecate
+void LLUUID::toCompressedString(char* out) const
+{
+ memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */
+ out[UUID_BYTES] = '\0';
+}
+
+std::string LLUUID::getString() const
+{
+ return asString();
+}
+
+std::string LLUUID::asString() const
+{
+ std::string str;
+ toString(str);
+ return str;
+}
+
+bool LLUUID::set(const char* in_string, bool emit)
+{
+ return set(ll_safe_string(in_string), emit);
+}
+
+bool LLUUID::set(const std::string& in_string, bool emit)
+{
+ bool broken_format = false;
+
+ // empty strings should make NULL uuid
+ if (in_string.empty())
+ {
+ setNull();
+ return true;
+ }
+
+ if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */
+ {
+ // I'm a moron. First implementation didn't have the right UUID format.
+ // Shouldn't see any of these any more
+ if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */
+ {
+ if (emit)
+ {
+ LL_WARNS() << "Warning! Using broken UUID string format" << LL_ENDL;
+ }
+ broken_format = true;
+ }
+ else
+ {
+ // Bad UUID string. Spam as INFO, as most cases we don't care.
+ if (emit)
+ {
+ //don't spam the logs because a resident can't spell.
+ LL_WARNS() << "Bad UUID string: " << in_string << LL_ENDL;
+ }
+ setNull();
+ return false;
+ }
+ }
+
+ U8 cur_pos = 0;
+ S32 i;
+ for (i = 0; i < UUID_BYTES; i++)
+ {
+ if ((i == 4) || (i == 6) || (i == 8) || (i == 10))
+ {
+ cur_pos++;
+ if (broken_format && (i == 10))
+ {
+ // Missing - in the broken format
+ cur_pos--;
+ }
+ }
+
+ mData[i] = 0;
+
+ if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9'))
+ {
+ mData[i] += (U8)(in_string[cur_pos] - '0');
+ }
+ else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f'))
+ {
+ mData[i] += (U8)(10 + in_string[cur_pos] - 'a');
+ }
+ else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F'))
+ {
+ mData[i] += (U8)(10 + in_string[cur_pos] - 'A');
+ }
+ else
+ {
+ if (emit)
+ {
+ LL_WARNS() << "Invalid UUID string character" << LL_ENDL;
+ }
+ setNull();
+ return false;
+ }
+
+ mData[i] = mData[i] << 4;
+ cur_pos++;
+
+ if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9'))
+ {
+ mData[i] += (U8)(in_string[cur_pos] - '0');
+ }
+ else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f'))
+ {
+ mData[i] += (U8)(10 + in_string[cur_pos] - 'a');
+ }
+ else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F'))
+ {
+ mData[i] += (U8)(10 + in_string[cur_pos] - 'A');
+ }
+ else
+ {
+ if (emit)
+ {
+ LL_WARNS() << "Invalid UUID string character" << LL_ENDL;
+ }
+ setNull();
+ return false;
+ }
+ cur_pos++;
+ }
+
+ return true;
+}
+
+bool LLUUID::validate(const std::string& in_string)
+{
+ bool broken_format = false;
+ if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */
+ {
+ // I'm a moron. First implementation didn't have the right UUID format.
+ if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */
+ {
+ broken_format = true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ U8 cur_pos = 0;
+ for (U32 i = 0; i < 16; i++)
+ {
+ if ((i == 4) || (i == 6) || (i == 8) || (i == 10))
+ {
+ cur_pos++;
+ if (broken_format && (i == 10))
+ {
+ // Missing - in the broken format
+ cur_pos--;
+ }
+ }
+
+ if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9'))
+ {
+ }
+ else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f'))
+ {
+ }
+ else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F'))
+ {
+ }
+ else
+ {
+ return false;
+ }
+
+ cur_pos++;
+
+ if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9'))
+ {
+ }
+ else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f'))
+ {
+ }
+ else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F'))
+ {
+ }
+ else
+ {
+ return false;
+ }
+ cur_pos++;
+ }
+ return true;
+}
+
+const LLUUID& LLUUID::operator^=(const LLUUID& rhs)
+{
+ U32* me = (U32*)&(mData[0]);
+ const U32* other = (U32*)&(rhs.mData[0]);
+ for (S32 i = 0; i < 4; ++i)
+ {
+ me[i] = me[i] ^ other[i];
+ }
+ return *this;
+}
+
+LLUUID LLUUID::operator^(const LLUUID& rhs) const
+{
+ LLUUID id(*this);
+ id ^= rhs;
+ return id;
+}
+
+// WARNING: this algorithm SHALL NOT be changed. It is also used by the server
+// and plays a role in some assets validation (e.g. clothing items). Changing
+// it would cause invalid assets.
+void LLUUID::combine(const LLUUID& other, LLUUID& result) const
+{
+ LLMD5 md5_uuid;
+ md5_uuid.update((unsigned char*)mData, 16);
+ md5_uuid.update((unsigned char*)other.mData, 16);
+ md5_uuid.finalize();
+ md5_uuid.raw_digest(result.mData);
+}
+
+LLUUID LLUUID::combine(const LLUUID& other) const
+{
+ LLUUID combination;
+ combine(other, combination);
+ return combination;
+}
+
+std::ostream& operator<<(std::ostream& s, const LLUUID& uuid)
+{
+ std::string uuid_str;
+ uuid.toString(uuid_str);
+ s << uuid_str;
+ return s;
+}
+
+std::istream& operator>>(std::istream& s, LLUUID& uuid)
+{
+ U32 i;
+ char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */
+ for (i = 0; i < UUID_STR_LENGTH - 1; i++)
+ {
+ s >> uuid_str[i];
+ }
+ uuid_str[i] = '\0';
+ uuid.set(std::string(uuid_str));
+ return s;
+}
+
+static void get_random_bytes(void* buf, int nbytes)
+{
+ int i;
+ char* cp = (char*)buf;
+
+ // *NOTE: If we are not using the janky generator ll_rand()
+ // generates at least 3 good bytes of data since it is 0 to
+ // RAND_MAX. This could be made more efficient by copying all the
+ // bytes.
+ for (i = 0; i < nbytes; i++)
+#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
+ * cp++ = janky_fast_random_bytes() & 0xFF;
+#else
+ * cp++ = ll_rand() & 0xFF;
+#endif
+ return;
+}
+
+#if LL_WINDOWS
+
+typedef struct _ASTAT_
+{
+ ADAPTER_STATUS adapt;
+ NAME_BUFFER NameBuff[30];
+}ASTAT, * PASTAT;
+
+// static
+S32 LLUUID::getNodeID(unsigned char* node_id)
+{
+ ASTAT Adapter;
+ NCB Ncb;
+ UCHAR uRetCode;
+ LANA_ENUM lenum;
+ int i;
+ int retval = 0;
+
+ memset(&Ncb, 0, sizeof(Ncb));
+ Ncb.ncb_command = NCBENUM;
+ Ncb.ncb_buffer = (UCHAR*)&lenum;
+ Ncb.ncb_length = sizeof(lenum);
+ uRetCode = Netbios(&Ncb);
+
+ for (i = 0; i < lenum.length; i++)
+ {
+ memset(&Ncb, 0, sizeof(Ncb));
+ Ncb.ncb_command = NCBRESET;
+ Ncb.ncb_lana_num = lenum.lana[i];
+
+ uRetCode = Netbios(&Ncb);
+
+ memset(&Ncb, 0, sizeof(Ncb));
+ Ncb.ncb_command = NCBASTAT;
+ Ncb.ncb_lana_num = lenum.lana[i];
+
+ strcpy((char*)Ncb.ncb_callname, "* "); /* Flawfinder: ignore */
+ Ncb.ncb_buffer = (unsigned char*)&Adapter;
+ Ncb.ncb_length = sizeof(Adapter);
+
+ uRetCode = Netbios(&Ncb);
+ if (uRetCode == 0)
+ {
+ memcpy(node_id, Adapter.adapt.adapter_address, 6); /* Flawfinder: ignore */
+ retval = 1;
+ }
+ }
+ return retval;
+}
+
+#elif LL_DARWIN
+// macOS version of the UUID generation code...
+/*
+ * Get an ethernet hardware address, if we can find it...
+ */
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <ifaddrs.h>
+
+ // static
+S32 LLUUID::getNodeID(unsigned char* node_id)
+{
+ int i;
+ unsigned char* a = NULL;
+ struct ifaddrs* ifap, * ifa;
+ int rv;
+ S32 result = 0;
+
+ if ((rv = getifaddrs(&ifap)) == -1)
+ {
+ return -1;
+ }
+ if (ifap == NULL)
+ {
+ return -1;
+ }
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next)
+ {
+ // printf("Interface %s, address family %d, ", ifa->ifa_name, ifa->ifa_addr->sa_family);
+ for (i = 0; i < ifa->ifa_addr->sa_len; i++)
+ {
+ // printf("%02X ", (unsigned char)ifa->ifa_addr->sa_data[i]);
+ }
+ // printf("\n");
+
+ if (ifa->ifa_addr->sa_family == AF_LINK)
+ {
+ // This is a link-level address
+ struct sockaddr_dl* lla = (struct sockaddr_dl*)ifa->ifa_addr;
+
+ // printf("\tLink level address, type %02X\n", lla->sdl_type);
+
+ if (lla->sdl_type == IFT_ETHER)
+ {
+ // Use the first ethernet MAC in the list.
+ // For some reason, the macro LLADDR() defined in net/if_dl.h doesn't expand correctly. This is what it would do.
+ a = (unsigned char*)&((lla)->sdl_data);
+ a += (lla)->sdl_nlen;
+
+ if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5])
+ {
+ continue;
+ }
+
+ if (node_id)
+ {
+ memcpy(node_id, a, 6);
+ result = 1;
+ }
+
+ // We found one.
+ break;
+ }
+ }
+ }
+ freeifaddrs(ifap);
+
+ return result;
+}
+
+#else
+
+// Linux version of the UUID generation code...
+/*
+ * Get the ethernet hardware address, if we can find it...
+ */
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#define HAVE_NETINET_IN_H
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#if !LL_DARWIN
+#include <linux/sockios.h>
+#endif
+#endif
+
+ // static
+S32 LLUUID::getNodeID(unsigned char* node_id)
+{
+ int sd;
+ struct ifreq ifr, * ifrp;
+ struct ifconf ifc;
+ char buf[1024];
+ int n, i;
+ unsigned char* a;
+
+ /*
+ * BSD 4.4 defines the size of an ifreq to be
+ * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len
+ * However, under earlier systems, sa_len isn't present, so the size is
+ * just sizeof(struct ifreq)
+ */
+#ifdef HAVE_SA_LEN
+#ifndef max
+#define max(a,b) ((a) > (b) ? (a) : (b))
+#endif
+#define ifreq_size(i) max(sizeof(struct ifreq),\
+ sizeof((i).ifr_name)+(i).ifr_addr.sa_len)
+#else
+#define ifreq_size(i) sizeof(struct ifreq)
+#endif /* HAVE_SA_LEN*/
+
+ sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sd < 0) {
+ return -1;
+ }
+ memset(buf, 0, sizeof(buf));
+ ifc.ifc_len = sizeof(buf);
+ ifc.ifc_buf = buf;
+ if (ioctl(sd, SIOCGIFCONF, (char*)&ifc) < 0) {
+ close(sd);
+ return -1;
+ }
+ n = ifc.ifc_len;
+ for (i = 0; i < n; i += ifreq_size(*ifr)) {
+ ifrp = (struct ifreq*)((char*)ifc.ifc_buf + i);
+ strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); /* Flawfinder: ignore */
+#ifdef SIOCGIFHWADDR
+ if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0)
+ continue;
+ a = (unsigned char*)&ifr.ifr_hwaddr.sa_data;
+#else
+#ifdef SIOCGENADDR
+ if (ioctl(sd, SIOCGENADDR, &ifr) < 0)
+ continue;
+ a = (unsigned char*)ifr.ifr_enaddr;
+#else
+ /*
+ * XXX we don't have a way of getting the hardware
+ * address
+ */
+ close(sd);
+ return 0;
+#endif /* SIOCGENADDR */
+#endif /* SIOCGIFHWADDR */
+ if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5])
+ continue;
+ if (node_id) {
+ memcpy(node_id, a, 6); /* Flawfinder: ignore */
+ close(sd);
+ return 1;
+ }
+ }
+ close(sd);
+ return 0;
+}
+
+#endif
+
+S32 LLUUID::cmpTime(uuid_time_t* t1, uuid_time_t* t2)
+{
+ // Compare two time values.
+
+ if (t1->high < t2->high) return -1;
+ if (t1->high > t2->high) return 1;
+ if (t1->low < t2->low) return -1;
+ if (t1->low > t2->low) return 1;
+ return 0;
+}
+
+void LLUUID::getSystemTime(uuid_time_t* timestamp)
+{
+ // Get system time with 100ns precision. Time is since Oct 15, 1582.
+#if LL_WINDOWS
+ ULARGE_INTEGER time;
+ GetSystemTimeAsFileTime((FILETIME*)&time);
+ // NT keeps time in FILETIME format which is 100ns ticks since
+ // Jan 1, 1601. UUIDs use time in 100ns ticks since Oct 15, 1582.
+ // The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec)
+ // + 18 years and 5 leap days.
+ time.QuadPart +=
+ (unsigned __int64)(1000 * 1000 * 10) // seconds
+ * (unsigned __int64)(60 * 60 * 24) // days
+ * (unsigned __int64)(17 + 30 + 31 + 365 * 18 + 5); // # of days
+
+ timestamp->high = time.HighPart;
+ timestamp->low = time.LowPart;
+#else
+ struct timeval tp;
+ gettimeofday(&tp, 0);
+
+ // Offset between UUID formatted times and Unix formatted times.
+ // UUID UTC base time is October 15, 1582.
+ // Unix base time is January 1, 1970.
+ U64 uuid_time = ((U64)tp.tv_sec * 10000000) + (tp.tv_usec * 10) +
+ U64L(0x01B21DD213814000);
+ timestamp->high = (U32)(uuid_time >> 32);
+ timestamp->low = (U32)(uuid_time & 0xFFFFFFFF);
+#endif
+}
+
+void LLUUID::getCurrentTime(uuid_time_t* timestamp)
+{
+ // Get current time as 60 bit 100ns ticks since whenever.
+ // Compensate for the fact that real clock resolution is less
+ // than 100ns.
+
+ const U32 uuids_per_tick = 1024;
+
+ static uuid_time_t time_last;
+ static U32 uuids_this_tick;
+ static bool init = false;
+
+ if (!init) {
+ getSystemTime(&time_last);
+ uuids_this_tick = uuids_per_tick;
+ init = true;
+ mMutex = new LLMutex();
+ }
+
+ uuid_time_t time_now = { 0,0 };
+
+ while (1) {
+ getSystemTime(&time_now);
+
+ // if clock reading changed since last UUID generated
+ if (cmpTime(&time_last, &time_now)) {
+ // reset count of uuid's generated with this clock reading
+ uuids_this_tick = 0;
+ break;
+ }
+ if (uuids_this_tick < uuids_per_tick) {
+ uuids_this_tick++;
+ break;
+ }
+ // going too fast for our clock; spin
+ }
+
+ time_last = time_now;
+
+ if (uuids_this_tick != 0) {
+ if (time_now.low & 0x80000000) {
+ time_now.low += uuids_this_tick;
+ if (!(time_now.low & 0x80000000))
+ time_now.high++;
+ }
+ else
+ time_now.low += uuids_this_tick;
+ }
+
+ timestamp->high = time_now.high;
+ timestamp->low = time_now.low;
+}
+
+void LLUUID::generate()
+{
+ // Create a UUID.
+ uuid_time_t timestamp;
+
+ static unsigned char node_id[6]; /* Flawfinder: ignore */
+ static int has_init = 0;
+
+ // Create a UUID.
+ static uuid_time_t time_last = { 0,0 };
+ static U16 clock_seq = 0;
+#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
+ static U32 seed = 0L; // dummy seed. reset it below
+#endif
+ if (!has_init)
+ {
+ has_init = 1;
+ if (getNodeID(node_id) <= 0)
+ {
+ get_random_bytes(node_id, 6);
+ /*
+ * Set multicast bit, to prevent conflicts
+ * with IEEE 802 addresses obtained from
+ * network cards
+ */
+ node_id[0] |= 0x80;
+ }
+
+ getCurrentTime(&time_last);
+#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
+ seed = time_last.low;
+#endif
+
+#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
+ clock_seq = (U16)janky_fast_random_seeded_bytes(seed, 65536);
+#else
+ clock_seq = (U16)ll_rand(65536);
+#endif
+ }
+
+ // get current time
+ getCurrentTime(×tamp);
+ U16 our_clock_seq = clock_seq;
+
+ // if clock hasn't changed or went backward, change clockseq
+ if (cmpTime(×tamp, &time_last) != 1)
+ {
+ LLMutexLock lock(mMutex);
+ clock_seq = (clock_seq + 1) & 0x3FFF;
+ if (clock_seq == 0)
+ clock_seq++;
+ our_clock_seq = clock_seq; // Ensure we're using a different clock_seq value from previous time
+ }
+
+ time_last = timestamp;
+
+ memcpy(mData + 10, node_id, 6); /* Flawfinder: ignore */
+ U32 tmp;
+ tmp = timestamp.low;
+ mData[3] = (unsigned char)tmp;
+ tmp >>= 8;
+ mData[2] = (unsigned char)tmp;
+ tmp >>= 8;
+ mData[1] = (unsigned char)tmp;
+ tmp >>= 8;
+ mData[0] = (unsigned char)tmp;
+
+ tmp = (U16)timestamp.high;
+ mData[5] = (unsigned char)tmp;
+ tmp >>= 8;
+ mData[4] = (unsigned char)tmp;
+
+ tmp = (timestamp.high >> 16) | 0x1000;
+ mData[7] = (unsigned char)tmp;
+ tmp >>= 8;
+ mData[6] = (unsigned char)tmp;
+
+ tmp = our_clock_seq;
+
+ mData[9] = (unsigned char)tmp;
+ tmp >>= 8;
+ mData[8] = (unsigned char)tmp;
+
+ HBXXH128::digest(*this, (const void*)mData, 16);
+}
+
+void LLUUID::generate(const std::string& hash_string)
+{
+ HBXXH128::digest(*this, hash_string);
+}
+
+U32 LLUUID::getRandomSeed()
+{
+ static unsigned char seed[16]; /* Flawfinder: ignore */
+
+ getNodeID(&seed[0]);
+
+ // Incorporate the pid into the seed to prevent
+ // processes that start on the same host at the same
+ // time from generating the same seed.
+ pid_t pid = LLApp::getPid();
+
+ seed[6] = (unsigned char)(pid >> 8);
+ seed[7] = (unsigned char)(pid);
+ getSystemTime((uuid_time_t*)(&seed[8]));
+
+ U64 seed64 = HBXXH64::digest((const void*)seed, 16);
+ return U32(seed64) ^ U32(seed64 >> 32);
+}
+
+bool LLUUID::parseUUID(const std::string& buf, LLUUID* value)
+{
+ if (buf.empty() || value == NULL)
+ {
+ return false;
+ }
+
+ std::string temp(buf);
+ LLStringUtil::trim(temp);
+ if (LLUUID::validate(temp))
+ {
+ value->set(temp);
+ return true;
+ }
+ return false;
+}
+
+//static
+LLUUID LLUUID::generateNewID(std::string hash_string)
+{
+ LLUUID new_id;
+ if (hash_string.empty())
+ {
+ new_id.generate();
+ }
+ else
+ {
+ new_id.generate(hash_string);
+ }
+ return new_id;
+}
+
+LLAssetID LLTransactionID::makeAssetID(const LLUUID& session) const
+{
+ LLAssetID result;
+ if (isNull())
+ {
+ result.setNull();
+ }
+ else
+ {
+ combine(session, result);
+ }
+ return result;
+}
+
+// Construct
+LLUUID::LLUUID()
+{
+ setNull();
+}
+
+
+// Faster than copying from memory
+void LLUUID::setNull()
+{
+ U32* word = (U32*)mData;
+ word[0] = 0;
+ word[1] = 0;
+ word[2] = 0;
+ word[3] = 0;
+}
+
+
+// Compare
+bool LLUUID::operator==(const LLUUID& rhs) const
+{
+ U32* tmp = (U32*)mData;
+ U32* rhstmp = (U32*)rhs.mData;
+ // Note: binary & to avoid branching
+ return
+ (tmp[0] == rhstmp[0]) &
+ (tmp[1] == rhstmp[1]) &
+ (tmp[2] == rhstmp[2]) &
+ (tmp[3] == rhstmp[3]);
+}
+
+
+bool LLUUID::operator!=(const LLUUID& rhs) const
+{
+ U32* tmp = (U32*)mData;
+ U32* rhstmp = (U32*)rhs.mData;
+ // Note: binary | to avoid branching
+ return
+ (tmp[0] != rhstmp[0]) |
+ (tmp[1] != rhstmp[1]) |
+ (tmp[2] != rhstmp[2]) |
+ (tmp[3] != rhstmp[3]);
+}
+
+/*
+// JC: This is dangerous. It allows UUIDs to be cast automatically
+// to integers, among other things. Use isNull() or notNull().
+ LLUUID::operator bool() const
+{
+ U32 *word = (U32 *)mData;
+ return (word[0] | word[1] | word[2] | word[3]) > 0;
+}
+*/
+
+bool LLUUID::notNull() const
+{
+ U32* word = (U32*)mData;
+ return (word[0] | word[1] | word[2] | word[3]) > 0;
+}
+
+// Faster than == LLUUID::null because doesn't require
+// as much memory access.
+bool LLUUID::isNull() const
+{
+ U32* word = (U32*)mData;
+ // If all bits are zero, return !0 == true
+ return !(word[0] | word[1] | word[2] | word[3]);
+}
+
+LLUUID::LLUUID(const char* in_string)
+{
+ if (!in_string || in_string[0] == 0)
+ {
+ setNull();
+ return;
+ }
+
+ set(in_string);
+}
+
+LLUUID::LLUUID(const std::string& in_string)
+{
+ if (in_string.empty())
+ {
+ setNull();
+ return;
+ }
+
+ set(in_string);
+}
+
+// IW: DON'T "optimize" these w/ U32s or you'll scoogie the sort order
+// IW: this will make me very sad
+bool LLUUID::operator<(const LLUUID& rhs) const
+{
+ U32 i;
+ for (i = 0; i < (UUID_BYTES - 1); i++)
+ {
+ if (mData[i] != rhs.mData[i])
+ {
+ return (mData[i] < rhs.mData[i]);
+ }
+ }
+ return (mData[UUID_BYTES - 1] < rhs.mData[UUID_BYTES - 1]);
+}
+
+bool LLUUID::operator>(const LLUUID& rhs) const
+{
+ U32 i;
+ for (i = 0; i < (UUID_BYTES - 1); i++)
+ {
+ if (mData[i] != rhs.mData[i])
+ {
+ return (mData[i] > rhs.mData[i]);
+ }
+ }
+ return (mData[UUID_BYTES - 1] > rhs.mData[UUID_BYTES - 1]);
+}
+
+U16 LLUUID::getCRC16() const
+{
+ // A UUID is 16 bytes, or 8 shorts.
+ U16* short_data = (U16*)mData;
+ U16 out = 0;
+ out += short_data[0];
+ out += short_data[1];
+ out += short_data[2];
+ out += short_data[3];
+ out += short_data[4];
+ out += short_data[5];
+ out += short_data[6];
+ out += short_data[7];
+ return out;
+}
+
+U32 LLUUID::getCRC32() const
+{
+ U32* tmp = (U32*)mData;
+ return tmp[0] + tmp[1] + tmp[2] + tmp[3];
+}
diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index eea5e39c61..526a79f3a7 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -1,194 +1,194 @@ -/** - * @file lluuid.h - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLUUID_H -#define LL_LLUUID_H - -#include <iostream> -#include <set> -#include <vector> -#include "stdtypes.h" -#include "llpreprocessor.h" -#include <boost/functional/hash.hpp> - -class LLMutex; - -const S32 UUID_BYTES = 16; -const S32 UUID_WORDS = 4; -const S32 UUID_STR_LENGTH = 37; // actually wrong, should be 36 and use size below -const S32 UUID_STR_SIZE = 37; -const S32 UUID_BASE85_LENGTH = 21; // including the trailing NULL. - -struct uuid_time_t { - U32 high; - U32 low; - }; - -class LL_COMMON_API LLUUID -{ -public: - // - // CREATORS - // - LLUUID(); - explicit LLUUID(const char *in_string); // Convert from string. - explicit LLUUID(const std::string& in_string); // Convert from string. - ~LLUUID() = default; - - // - // MANIPULATORS - // - void generate(); // Generate a new UUID - void generate(const std::string& stream); //Generate a new UUID based on hash of input stream - - static LLUUID generateNewID(std::string stream = ""); //static version of above for use in initializer expressions such as constructor params, etc. - - BOOL set(const char *in_string, BOOL emit = TRUE); // Convert from string, if emit is FALSE, do not emit warnings - BOOL set(const std::string& in_string, BOOL emit = TRUE); // Convert from string, if emit is FALSE, do not emit warnings - void setNull(); // Faster than setting to LLUUID::null. - - S32 cmpTime(uuid_time_t *t1, uuid_time_t *t2); - static void getSystemTime(uuid_time_t *timestamp); - void getCurrentTime(uuid_time_t *timestamp); - - // - // ACCESSORS - // - BOOL isNull() const; // Faster than comparing to LLUUID::null. - BOOL notNull() const; // Faster than comparing to LLUUID::null. - // JC: This is dangerous. It allows UUIDs to be cast automatically - // to integers, among other things. Use isNull() or notNull(). - // operator bool() const; - - // JC: These must return real bool's (not BOOLs) or else use of the STL - // will generate bool-to-int performance warnings. - bool operator==(const LLUUID &rhs) const; - bool operator!=(const LLUUID &rhs) const; - bool operator<(const LLUUID &rhs) const; - bool operator>(const LLUUID &rhs) const; - - // xor functions. Useful since any two random uuids xored together - // will yield a determinate third random unique id that can be - // used as a key in a single uuid that represents 2. - const LLUUID& operator^=(const LLUUID& rhs); - LLUUID operator^(const LLUUID& rhs) const; - - // similar to functions above, but not invertible - // yields a third random UUID that can be reproduced from the two inputs - // but which, given the result and one of the inputs can't be used to - // deduce the other input - LLUUID combine(const LLUUID& other) const; - void combine(const LLUUID& other, LLUUID& result) const; - - friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid); - friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid); - - void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0) - void toString(std::string& out) const; - void toCompressedString(char *out) const; // Does not allocate memory, needs 17 characters (including \0) - void toCompressedString(std::string& out) const; - - std::string asString() const; - std::string getString() const; - - U16 getCRC16() const; - U32 getCRC32() const; - - // Returns a 64 bits digest of the UUID, by XORing its two 64 bits long - // words. HB - inline U64 getDigest64() const - { - U64* tmp = (U64*)mData; - return tmp[0] ^ tmp[1]; - } - - static BOOL validate(const std::string& in_string); // Validate that the UUID string is legal. - - static const LLUUID null; - static LLMutex * mMutex; - - static U32 getRandomSeed(); - static S32 getNodeID(unsigned char * node_id); - - static BOOL parseUUID(const std::string& buf, LLUUID* value); - - U8 mData[UUID_BYTES]; -}; -static_assert(std::is_trivially_copyable<LLUUID>::value, "LLUUID must be trivial copy"); -static_assert(std::is_trivially_move_assignable<LLUUID>::value, "LLUUID must be trivial move"); -static_assert(std::is_standard_layout<LLUUID>::value, "LLUUID must be a standard layout type"); - -typedef std::vector<LLUUID> uuid_vec_t; -typedef std::set<LLUUID> uuid_set_t; - -// Helper structure for ordering lluuids in stl containers. eg: -// std::map<LLUUID, LLWidget*, lluuid_less> widget_map; -// -// (isn't this the default behavior anyway? I think we could -// everywhere replace these with uuid_set_t, but someone should -// verify.) -struct lluuid_less -{ - bool operator()(const LLUUID& lhs, const LLUUID& rhs) const - { - return (lhs < rhs) ? true : false; - } -}; - -typedef std::set<LLUUID, lluuid_less> uuid_list_t; -/* - * Sub-classes for keeping transaction IDs and asset IDs - * straight. - */ -typedef LLUUID LLAssetID; - -class LL_COMMON_API LLTransactionID : public LLUUID -{ -public: - LLTransactionID() : LLUUID() { } - - static const LLTransactionID tnull; - LLAssetID makeAssetID(const LLUUID& session) const; -}; - -// std::hash implementation for LLUUID -namespace std -{ - template<> struct hash<LLUUID> - { - inline size_t operator()(const LLUUID& id) const noexcept - { - return (size_t)id.getDigest64(); - } - }; -} - -// For use with boost containers. -inline size_t hash_value(const LLUUID& id) noexcept -{ - return (size_t)id.getDigest64(); -} - -#endif // LL_LLUUID_H +/**
+ * @file lluuid.h
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLUUID_H
+#define LL_LLUUID_H
+
+#include <iostream>
+#include <set>
+#include <vector>
+#include "stdtypes.h"
+#include "llpreprocessor.h"
+#include <boost/functional/hash.hpp>
+
+class LLMutex;
+
+const S32 UUID_BYTES = 16;
+const S32 UUID_WORDS = 4;
+const S32 UUID_STR_LENGTH = 37; // actually wrong, should be 36 and use size below
+const S32 UUID_STR_SIZE = 37;
+const S32 UUID_BASE85_LENGTH = 21; // including the trailing NULL.
+
+struct uuid_time_t {
+ U32 high;
+ U32 low;
+ };
+
+class LL_COMMON_API LLUUID
+{
+public:
+ //
+ // CREATORS
+ //
+ LLUUID();
+ explicit LLUUID(const char *in_string); // Convert from string.
+ explicit LLUUID(const std::string& in_string); // Convert from string.
+ ~LLUUID() = default;
+
+ //
+ // MANIPULATORS
+ //
+ void generate(); // Generate a new UUID
+ void generate(const std::string& stream); //Generate a new UUID based on hash of input stream
+
+ static LLUUID generateNewID(std::string stream = ""); //static version of above for use in initializer expressions such as constructor params, etc.
+
+ bool set(const char *in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings
+ bool set(const std::string& in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings
+ void setNull(); // Faster than setting to LLUUID::null.
+
+ S32 cmpTime(uuid_time_t *t1, uuid_time_t *t2);
+ static void getSystemTime(uuid_time_t *timestamp);
+ void getCurrentTime(uuid_time_t *timestamp);
+
+ //
+ // ACCESSORS
+ //
+ bool isNull() const; // Faster than comparing to LLUUID::null.
+ bool notNull() const; // Faster than comparing to LLUUID::null.
+ // JC: This is dangerous. It allows UUIDs to be cast automatically
+ // to integers, among other things. Use isNull() or notNull().
+ // operator bool() const;
+
+ // JC: These must return real bool's (not BOOLs) or else use of the STL
+ // will generate bool-to-int performance warnings.
+ bool operator==(const LLUUID &rhs) const;
+ bool operator!=(const LLUUID &rhs) const;
+ bool operator<(const LLUUID &rhs) const;
+ bool operator>(const LLUUID &rhs) const;
+
+ // xor functions. Useful since any two random uuids xored together
+ // will yield a determinate third random unique id that can be
+ // used as a key in a single uuid that represents 2.
+ const LLUUID& operator^=(const LLUUID& rhs);
+ LLUUID operator^(const LLUUID& rhs) const;
+
+ // similar to functions above, but not invertible
+ // yields a third random UUID that can be reproduced from the two inputs
+ // but which, given the result and one of the inputs can't be used to
+ // deduce the other input
+ LLUUID combine(const LLUUID& other) const;
+ void combine(const LLUUID& other, LLUUID& result) const;
+
+ friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid);
+ friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid);
+
+ void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0)
+ void toString(std::string& out) const;
+ void toCompressedString(char *out) const; // Does not allocate memory, needs 17 characters (including \0)
+ void toCompressedString(std::string& out) const;
+
+ std::string asString() const;
+ std::string getString() const;
+
+ U16 getCRC16() const;
+ U32 getCRC32() const;
+
+ // Returns a 64 bits digest of the UUID, by XORing its two 64 bits long
+ // words. HB
+ inline U64 getDigest64() const
+ {
+ U64* tmp = (U64*)mData;
+ return tmp[0] ^ tmp[1];
+ }
+
+ static bool validate(const std::string& in_string); // Validate that the UUID string is legal.
+
+ static const LLUUID null;
+ static LLMutex * mMutex;
+
+ static U32 getRandomSeed();
+ static S32 getNodeID(unsigned char * node_id);
+
+ static bool parseUUID(const std::string& buf, LLUUID* value);
+
+ U8 mData[UUID_BYTES];
+};
+static_assert(std::is_trivially_copyable<LLUUID>::value, "LLUUID must be trivial copy");
+static_assert(std::is_trivially_move_assignable<LLUUID>::value, "LLUUID must be trivial move");
+static_assert(std::is_standard_layout<LLUUID>::value, "LLUUID must be a standard layout type");
+
+typedef std::vector<LLUUID> uuid_vec_t;
+typedef std::set<LLUUID> uuid_set_t;
+
+// Helper structure for ordering lluuids in stl containers. eg:
+// std::map<LLUUID, LLWidget*, lluuid_less> widget_map;
+//
+// (isn't this the default behavior anyway? I think we could
+// everywhere replace these with uuid_set_t, but someone should
+// verify.)
+struct lluuid_less
+{
+ bool operator()(const LLUUID& lhs, const LLUUID& rhs) const
+ {
+ return lhs < rhs;
+ }
+};
+
+typedef std::set<LLUUID, lluuid_less> uuid_list_t;
+/*
+ * Sub-classes for keeping transaction IDs and asset IDs
+ * straight.
+ */
+typedef LLUUID LLAssetID;
+
+class LL_COMMON_API LLTransactionID : public LLUUID
+{
+public:
+ LLTransactionID() : LLUUID() { }
+
+ static const LLTransactionID tnull;
+ LLAssetID makeAssetID(const LLUUID& session) const;
+};
+
+// std::hash implementation for LLUUID
+namespace std
+{
+ template<> struct hash<LLUUID>
+ {
+ inline size_t operator()(const LLUUID& id) const noexcept
+ {
+ return (size_t)id.getDigest64();
+ }
+ };
+}
+
+// For use with boost containers.
+inline size_t hash_value(const LLUUID& id) noexcept
+{
+ return (size_t)id.getDigest64();
+}
+
+#endif // LL_LLUUID_H
diff --git a/indra/llcommon/llworkerthread.cpp b/indra/llcommon/llworkerthread.cpp index 67df2b5840..22a922c94b 100644 --- a/indra/llcommon/llworkerthread.cpp +++ b/indra/llcommon/llworkerthread.cpp @@ -1,398 +1,398 @@ -/** - * @file llworkerthread.cpp - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" -#include "llworkerthread.h" -#include "llstl.h" - -#if USE_FRAME_CALLBACK_MANAGER -#include "llframecallbackmanager.h" -#endif - -//============================================================================ -// Run on MAIN thread - -LLWorkerThread::LLWorkerThread(const std::string& name, bool threaded, bool should_pause) : - LLQueuedThread(name, threaded, should_pause) -{ - mDeleteMutex = new LLMutex(); - - if(!mLocalAPRFilePoolp) - { - mLocalAPRFilePoolp = new LLVolatileAPRPool() ; - } -} - -LLWorkerThread::~LLWorkerThread() -{ - // Delete any workers in the delete queue (should be safe - had better be!) - if (!mDeleteList.empty()) - { - LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size() - << " entries in delete list." << LL_ENDL; - } - - delete mDeleteMutex; - - // ~LLQueuedThread() will be called here -} - -//called only in destructor. -void LLWorkerThread::clearDeleteList() -{ - // Delete any workers in the delete queue (should be safe - had better be!) - if (!mDeleteList.empty()) - { - LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size() - << " entries in delete list." << LL_ENDL; - - mDeleteMutex->lock(); - for (LLWorkerClass* worker : mDeleteList) - { - worker->mRequestHandle = LLWorkerThread::nullHandle(); - worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK); - worker->clearFlags(LLWorkerClass::WCF_WORKING); - delete worker; - } - mDeleteList.clear() ; - mDeleteMutex->unlock() ; - } -} - -// virtual -size_t LLWorkerThread::update(F32 max_time_ms) -{ - auto res = LLQueuedThread::update(max_time_ms); - // Delete scheduled workers - std::vector<LLWorkerClass*> delete_list; - std::vector<LLWorkerClass*> abort_list; - mDeleteMutex->lock(); - for (delete_list_t::iterator iter = mDeleteList.begin(); - iter != mDeleteList.end(); ) - { - delete_list_t::iterator curiter = iter++; - LLWorkerClass* worker = *curiter; - if (worker->deleteOK()) - { - if (worker->getFlags(LLWorkerClass::WCF_WORK_FINISHED)) - { - worker->setFlags(LLWorkerClass::WCF_DELETE_REQUESTED); - delete_list.push_back(worker); - mDeleteList.erase(curiter); - } - else if (!worker->getFlags(LLWorkerClass::WCF_ABORT_REQUESTED)) - { - abort_list.push_back(worker); - } - } - } - mDeleteMutex->unlock(); - // abort and delete after releasing mutex - for (LLWorkerClass* worker : abort_list) - { - worker->abortWork(false); - } - for (LLWorkerClass* worker : delete_list) - { - if (worker->mRequestHandle) - { - // Finished but not completed - completeRequest(worker->mRequestHandle); - worker->mRequestHandle = LLWorkerThread::nullHandle(); - worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK); - } - delete worker; - } - // delete and aborted entries mean there's still work to do - res += delete_list.size() + abort_list.size(); - return res; -} - -//---------------------------------------------------------------------------- - -LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param) -{ - handle_t handle = generateHandle(); - - WorkRequest* req = new WorkRequest(handle, workerclass, param); - - bool res = addRequest(req); - if (!res) - { - LL_ERRS() << "add called after LLWorkerThread::cleanupClass()" << LL_ENDL; - req->deleteRequest(); - handle = nullHandle(); - } - - return handle; -} - -void LLWorkerThread::deleteWorker(LLWorkerClass* workerclass) -{ - mDeleteMutex->lock(); - mDeleteList.push_back(workerclass); - mDeleteMutex->unlock(); -} - -//============================================================================ -// Runs on its OWN thread - -LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param) : - LLQueuedThread::QueuedRequest(handle), - mWorkerClass(workerclass), - mParam(param) -{ -} - -LLWorkerThread::WorkRequest::~WorkRequest() -{ -} - -// virtual (required for access by LLWorkerThread) -void LLWorkerThread::WorkRequest::deleteRequest() -{ - LLQueuedThread::QueuedRequest::deleteRequest(); -} - -// virtual -bool LLWorkerThread::WorkRequest::processRequest() -{ - LL_PROFILE_ZONE_SCOPED; - LLWorkerClass* workerclass = getWorkerClass(); - workerclass->setWorking(true); - bool complete = workerclass->doWork(getParam()); - workerclass->setWorking(false); - return complete; -} - -// virtual -void LLWorkerThread::WorkRequest::finishRequest(bool completed) -{ - LL_PROFILE_ZONE_SCOPED; - LLWorkerClass* workerclass = getWorkerClass(); - workerclass->finishWork(getParam(), completed); - U32 flags = LLWorkerClass::WCF_WORK_FINISHED | (completed ? 0 : LLWorkerClass::WCF_WORK_ABORTED); - workerclass->setFlags(flags); -} - -//============================================================================ -// LLWorkerClass:: operates in main thread - -LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& name) - : mWorkerThread(workerthread), - mWorkerClassName(name), - mRequestHandle(LLWorkerThread::nullHandle()), - mMutex(), - mWorkFlags(0) -{ - if (!mWorkerThread) - { - LL_ERRS() << "LLWorkerClass() called with NULL workerthread: " << name << LL_ENDL; - } -} - -LLWorkerClass::~LLWorkerClass() -{ - llassert_always(!(mWorkFlags & WCF_WORKING)); - llassert_always(mWorkFlags & WCF_DELETE_REQUESTED); - llassert_always(!mMutex.isLocked()); - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle); - if (!workreq) - { - LL_ERRS() << "LLWorkerClass destroyed with stale work handle" << LL_ENDL; - } - if (workreq->getStatus() != LLWorkerThread::STATUS_ABORTED && - workreq->getStatus() != LLWorkerThread::STATUS_COMPLETE) - { - LL_ERRS() << "LLWorkerClass destroyed with active worker! Worker Status: " << workreq->getStatus() << LL_ENDL; - } - } -} - -void LLWorkerClass::setWorkerThread(LLWorkerThread* workerthread) -{ - mMutex.lock(); - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - LL_ERRS() << "LLWorkerClass attempt to change WorkerThread with active worker!" << LL_ENDL; - } - mWorkerThread = workerthread; - mMutex.unlock(); -} - -//---------------------------------------------------------------------------- - -//virtual -void LLWorkerClass::finishWork(S32 param, bool success) -{ -} - -//virtual -bool LLWorkerClass::deleteOK() -{ - return true; // default always OK -} - -//---------------------------------------------------------------------------- - -// Called from worker thread -void LLWorkerClass::setWorking(bool working) -{ - mMutex.lock(); - if (working) - { - llassert_always(!(mWorkFlags & WCF_WORKING)); - setFlags(WCF_WORKING); - } - else - { - llassert_always((mWorkFlags & WCF_WORKING)); - clearFlags(WCF_WORKING); - } - mMutex.unlock(); -} - -//---------------------------------------------------------------------------- - -bool LLWorkerClass::yield() -{ - LLThread::yield(); - mWorkerThread->checkPause(); - bool res; - mMutex.lock(); - res = (getFlags() & WCF_ABORT_REQUESTED) ? true : false; - mMutex.unlock(); - return res; -} - -//---------------------------------------------------------------------------- - -// calls startWork, adds doWork() to queue -void LLWorkerClass::addWork(S32 param) -{ - mMutex.lock(); - llassert_always(!(mWorkFlags & (WCF_WORKING|WCF_HAVE_WORK))); - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - LL_ERRS() << "LLWorkerClass attempt to add work with active worker!" << LL_ENDL; - } -#if _DEBUG -// LL_INFOS() << "addWork: " << mWorkerClassName << " Param: " << param << LL_ENDL; -#endif - startWork(param); - clearFlags(WCF_WORK_FINISHED|WCF_WORK_ABORTED); - setFlags(WCF_HAVE_WORK); - mRequestHandle = mWorkerThread->addWorkRequest(this, param); - mMutex.unlock(); -} - -void LLWorkerClass::abortWork(bool autocomplete) -{ - mMutex.lock(); -#if _DEBUG -// LLWorkerThread::WorkRequest* workreq = mWorkerThread->getRequest(mRequestHandle); -// if (workreq) -// LL_INFOS() << "abortWork: " << mWorkerClassName << " Param: " << workreq->getParam() << LL_ENDL; -#endif - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - mWorkerThread->abortRequest(mRequestHandle, autocomplete); - setFlags(WCF_ABORT_REQUESTED); - } - mMutex.unlock(); -} - -// if doWork is complete or aborted, call endWork() and return true -bool LLWorkerClass::checkWork(bool aborting) -{ - LLMutexLock lock(&mMutex); - bool complete = false, abort = false; - if (mRequestHandle != LLWorkerThread::nullHandle()) - { - LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle); - if(!workreq) - { - if(mWorkerThread->isQuitting() || mWorkerThread->isStopped()) //the mWorkerThread is not running - { - mRequestHandle = LLWorkerThread::nullHandle(); - clearFlags(WCF_HAVE_WORK); - } - else - { - llassert_always(workreq); - } - return true ; - } - - LLQueuedThread::status_t status = workreq->getStatus(); - if (status == LLWorkerThread::STATUS_ABORTED) - { - complete = true; - abort = true; - } - else if (status == LLWorkerThread::STATUS_COMPLETE) - { - complete = true; - } - else - { - llassert_always(!aborting || (workreq->getFlags() & LLQueuedThread::FLAG_ABORT)); - } - if (complete) - { - llassert_always(!(getFlags(WCF_WORKING))); - endWork(workreq->getParam(), abort); - mWorkerThread->completeRequest(mRequestHandle); - mRequestHandle = LLWorkerThread::nullHandle(); - clearFlags(WCF_HAVE_WORK); - } - } - else - { - complete = true; - } - return complete; -} - -void LLWorkerClass::scheduleDelete() -{ - bool do_delete = false; - mMutex.lock(); - if (!(getFlags(WCF_DELETE_REQUESTED))) - { - setFlags(WCF_DELETE_REQUESTED); - do_delete = true; - } - mMutex.unlock(); - if (do_delete) - { - mWorkerThread->deleteWorker(this); - } -} - -//============================================================================ - +/**
+ * @file llworkerthread.cpp
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+#include "llworkerthread.h"
+#include "llstl.h"
+
+#if USE_FRAME_CALLBACK_MANAGER
+#include "llframecallbackmanager.h"
+#endif
+
+//============================================================================
+// Run on MAIN thread
+
+LLWorkerThread::LLWorkerThread(const std::string& name, bool threaded, bool should_pause) :
+ LLQueuedThread(name, threaded, should_pause)
+{
+ mDeleteMutex = new LLMutex();
+
+ if(!mLocalAPRFilePoolp)
+ {
+ mLocalAPRFilePoolp = new LLVolatileAPRPool() ;
+ }
+}
+
+LLWorkerThread::~LLWorkerThread()
+{
+ // Delete any workers in the delete queue (should be safe - had better be!)
+ if (!mDeleteList.empty())
+ {
+ LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size()
+ << " entries in delete list." << LL_ENDL;
+ }
+
+ delete mDeleteMutex;
+
+ // ~LLQueuedThread() will be called here
+}
+
+//called only in destructor.
+void LLWorkerThread::clearDeleteList()
+{
+ // Delete any workers in the delete queue (should be safe - had better be!)
+ if (!mDeleteList.empty())
+ {
+ LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size()
+ << " entries in delete list." << LL_ENDL;
+
+ mDeleteMutex->lock();
+ for (LLWorkerClass* worker : mDeleteList)
+ {
+ worker->mRequestHandle = LLWorkerThread::nullHandle();
+ worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK);
+ worker->clearFlags(LLWorkerClass::WCF_WORKING);
+ delete worker;
+ }
+ mDeleteList.clear() ;
+ mDeleteMutex->unlock() ;
+ }
+}
+
+// virtual
+size_t LLWorkerThread::update(F32 max_time_ms)
+{
+ auto res = LLQueuedThread::update(max_time_ms);
+ // Delete scheduled workers
+ std::vector<LLWorkerClass*> delete_list;
+ std::vector<LLWorkerClass*> abort_list;
+ mDeleteMutex->lock();
+ for (delete_list_t::iterator iter = mDeleteList.begin();
+ iter != mDeleteList.end(); )
+ {
+ delete_list_t::iterator curiter = iter++;
+ LLWorkerClass* worker = *curiter;
+ if (worker->deleteOK())
+ {
+ if (worker->getFlags(LLWorkerClass::WCF_WORK_FINISHED))
+ {
+ worker->setFlags(LLWorkerClass::WCF_DELETE_REQUESTED);
+ delete_list.push_back(worker);
+ mDeleteList.erase(curiter);
+ }
+ else if (!worker->getFlags(LLWorkerClass::WCF_ABORT_REQUESTED))
+ {
+ abort_list.push_back(worker);
+ }
+ }
+ }
+ mDeleteMutex->unlock();
+ // abort and delete after releasing mutex
+ for (LLWorkerClass* worker : abort_list)
+ {
+ worker->abortWork(false);
+ }
+ for (LLWorkerClass* worker : delete_list)
+ {
+ if (worker->mRequestHandle)
+ {
+ // Finished but not completed
+ completeRequest(worker->mRequestHandle);
+ worker->mRequestHandle = LLWorkerThread::nullHandle();
+ worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK);
+ }
+ delete worker;
+ }
+ // delete and aborted entries mean there's still work to do
+ res += delete_list.size() + abort_list.size();
+ return res;
+}
+
+//----------------------------------------------------------------------------
+
+LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param)
+{
+ handle_t handle = generateHandle();
+
+ WorkRequest* req = new WorkRequest(handle, workerclass, param);
+
+ bool res = addRequest(req);
+ if (!res)
+ {
+ LL_ERRS() << "add called after LLWorkerThread::cleanupClass()" << LL_ENDL;
+ req->deleteRequest();
+ handle = nullHandle();
+ }
+
+ return handle;
+}
+
+void LLWorkerThread::deleteWorker(LLWorkerClass* workerclass)
+{
+ mDeleteMutex->lock();
+ mDeleteList.push_back(workerclass);
+ mDeleteMutex->unlock();
+}
+
+//============================================================================
+// Runs on its OWN thread
+
+LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param) :
+ LLQueuedThread::QueuedRequest(handle),
+ mWorkerClass(workerclass),
+ mParam(param)
+{
+}
+
+LLWorkerThread::WorkRequest::~WorkRequest()
+{
+}
+
+// virtual (required for access by LLWorkerThread)
+void LLWorkerThread::WorkRequest::deleteRequest()
+{
+ LLQueuedThread::QueuedRequest::deleteRequest();
+}
+
+// virtual
+bool LLWorkerThread::WorkRequest::processRequest()
+{
+ LL_PROFILE_ZONE_SCOPED;
+ LLWorkerClass* workerclass = getWorkerClass();
+ workerclass->setWorking(true);
+ bool complete = workerclass->doWork(getParam());
+ workerclass->setWorking(false);
+ return complete;
+}
+
+// virtual
+void LLWorkerThread::WorkRequest::finishRequest(bool completed)
+{
+ LL_PROFILE_ZONE_SCOPED;
+ LLWorkerClass* workerclass = getWorkerClass();
+ workerclass->finishWork(getParam(), completed);
+ U32 flags = LLWorkerClass::WCF_WORK_FINISHED | (completed ? 0 : LLWorkerClass::WCF_WORK_ABORTED);
+ workerclass->setFlags(flags);
+}
+
+//============================================================================
+// LLWorkerClass:: operates in main thread
+
+LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& name)
+ : mWorkerThread(workerthread),
+ mWorkerClassName(name),
+ mRequestHandle(LLWorkerThread::nullHandle()),
+ mMutex(),
+ mWorkFlags(0)
+{
+ if (!mWorkerThread)
+ {
+ LL_ERRS() << "LLWorkerClass() called with NULL workerthread: " << name << LL_ENDL;
+ }
+}
+
+LLWorkerClass::~LLWorkerClass()
+{
+ llassert_always(!(mWorkFlags & WCF_WORKING));
+ llassert_always(mWorkFlags & WCF_DELETE_REQUESTED);
+ llassert_always(!mMutex.isLocked());
+ if (mRequestHandle != LLWorkerThread::nullHandle())
+ {
+ LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle);
+ if (!workreq)
+ {
+ LL_ERRS() << "LLWorkerClass destroyed with stale work handle" << LL_ENDL;
+ }
+ if (workreq->getStatus() != LLWorkerThread::STATUS_ABORTED &&
+ workreq->getStatus() != LLWorkerThread::STATUS_COMPLETE)
+ {
+ LL_ERRS() << "LLWorkerClass destroyed with active worker! Worker Status: " << workreq->getStatus() << LL_ENDL;
+ }
+ }
+}
+
+void LLWorkerClass::setWorkerThread(LLWorkerThread* workerthread)
+{
+ mMutex.lock();
+ if (mRequestHandle != LLWorkerThread::nullHandle())
+ {
+ LL_ERRS() << "LLWorkerClass attempt to change WorkerThread with active worker!" << LL_ENDL;
+ }
+ mWorkerThread = workerthread;
+ mMutex.unlock();
+}
+
+//----------------------------------------------------------------------------
+
+//virtual
+void LLWorkerClass::finishWork(S32 param, bool success)
+{
+}
+
+//virtual
+bool LLWorkerClass::deleteOK()
+{
+ return true; // default always OK
+}
+
+//----------------------------------------------------------------------------
+
+// Called from worker thread
+void LLWorkerClass::setWorking(bool working)
+{
+ mMutex.lock();
+ if (working)
+ {
+ llassert_always(!(mWorkFlags & WCF_WORKING));
+ setFlags(WCF_WORKING);
+ }
+ else
+ {
+ llassert_always((mWorkFlags & WCF_WORKING));
+ clearFlags(WCF_WORKING);
+ }
+ mMutex.unlock();
+}
+
+//----------------------------------------------------------------------------
+
+bool LLWorkerClass::yield()
+{
+ LLThread::yield();
+ mWorkerThread->checkPause();
+ bool res;
+ mMutex.lock();
+ res = (getFlags() & WCF_ABORT_REQUESTED) != 0;
+ mMutex.unlock();
+ return res;
+}
+
+//----------------------------------------------------------------------------
+
+// calls startWork, adds doWork() to queue
+void LLWorkerClass::addWork(S32 param)
+{
+ mMutex.lock();
+ llassert_always(!(mWorkFlags & (WCF_WORKING|WCF_HAVE_WORK)));
+ if (mRequestHandle != LLWorkerThread::nullHandle())
+ {
+ LL_ERRS() << "LLWorkerClass attempt to add work with active worker!" << LL_ENDL;
+ }
+#if _DEBUG
+// LL_INFOS() << "addWork: " << mWorkerClassName << " Param: " << param << LL_ENDL;
+#endif
+ startWork(param);
+ clearFlags(WCF_WORK_FINISHED|WCF_WORK_ABORTED);
+ setFlags(WCF_HAVE_WORK);
+ mRequestHandle = mWorkerThread->addWorkRequest(this, param);
+ mMutex.unlock();
+}
+
+void LLWorkerClass::abortWork(bool autocomplete)
+{
+ mMutex.lock();
+#if _DEBUG
+// LLWorkerThread::WorkRequest* workreq = mWorkerThread->getRequest(mRequestHandle);
+// if (workreq)
+// LL_INFOS() << "abortWork: " << mWorkerClassName << " Param: " << workreq->getParam() << LL_ENDL;
+#endif
+ if (mRequestHandle != LLWorkerThread::nullHandle())
+ {
+ mWorkerThread->abortRequest(mRequestHandle, autocomplete);
+ setFlags(WCF_ABORT_REQUESTED);
+ }
+ mMutex.unlock();
+}
+
+// if doWork is complete or aborted, call endWork() and return true
+bool LLWorkerClass::checkWork(bool aborting)
+{
+ LLMutexLock lock(&mMutex);
+ bool complete = false, abort = false;
+ if (mRequestHandle != LLWorkerThread::nullHandle())
+ {
+ LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle);
+ if(!workreq)
+ {
+ if(mWorkerThread->isQuitting() || mWorkerThread->isStopped()) //the mWorkerThread is not running
+ {
+ mRequestHandle = LLWorkerThread::nullHandle();
+ clearFlags(WCF_HAVE_WORK);
+ }
+ else
+ {
+ llassert_always(workreq);
+ }
+ return true ;
+ }
+
+ LLQueuedThread::status_t status = workreq->getStatus();
+ if (status == LLWorkerThread::STATUS_ABORTED)
+ {
+ complete = true;
+ abort = true;
+ }
+ else if (status == LLWorkerThread::STATUS_COMPLETE)
+ {
+ complete = true;
+ }
+ else
+ {
+ llassert_always(!aborting || (workreq->getFlags() & LLQueuedThread::FLAG_ABORT));
+ }
+ if (complete)
+ {
+ llassert_always(!(getFlags(WCF_WORKING)));
+ endWork(workreq->getParam(), abort);
+ mWorkerThread->completeRequest(mRequestHandle);
+ mRequestHandle = LLWorkerThread::nullHandle();
+ clearFlags(WCF_HAVE_WORK);
+ }
+ }
+ else
+ {
+ complete = true;
+ }
+ return complete;
+}
+
+void LLWorkerClass::scheduleDelete()
+{
+ bool do_delete = false;
+ mMutex.lock();
+ if (!(getFlags(WCF_DELETE_REQUESTED)))
+ {
+ setFlags(WCF_DELETE_REQUESTED);
+ do_delete = true;
+ }
+ mMutex.unlock();
+ if (do_delete)
+ {
+ mWorkerThread->deleteWorker(this);
+ }
+}
+
+//============================================================================
+
diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h index 01c96852d3..803fff78d9 100644 --- a/indra/llcommon/llworkerthread.h +++ b/indra/llcommon/llworkerthread.h @@ -1,201 +1,201 @@ -/** - * @file llworkerthread.h - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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_LLWORKERTHREAD_H -#define LL_LLWORKERTHREAD_H - -#include <list> -#include <map> -#include <queue> -#include <set> -#include <string> - -#include "llqueuedthread.h" -#include "llatomic.h" -#include "llmutex.h" - -#define USE_FRAME_CALLBACK_MANAGER 0 - -//============================================================================ - -class LLWorkerClass; - -//============================================================================ -// Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small -// It is assumed that LLWorkerThreads are rarely created/destroyed. - -class LL_COMMON_API LLWorkerThread : public LLQueuedThread -{ - friend class LLWorkerClass; -public: - class WorkRequest : public LLQueuedThread::QueuedRequest - { - protected: - virtual ~WorkRequest(); // use deleteRequest() - - public: - WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param); - - S32 getParam() - { - return mParam; - } - LLWorkerClass* getWorkerClass() - { - return mWorkerClass; - } - - /*virtual*/ bool processRequest(); - /*virtual*/ void finishRequest(bool completed); - /*virtual*/ void deleteRequest(); - - private: - LLWorkerClass* mWorkerClass; - S32 mParam; - }; - -protected: - void clearDeleteList() ; - -private: - typedef std::list<LLWorkerClass*> delete_list_t; - delete_list_t mDeleteList; - LLMutex* mDeleteMutex; - -public: - LLWorkerThread(const std::string& name, bool threaded = true, bool should_pause = false); - ~LLWorkerThread(); - - /*virtual*/ size_t update(F32 max_time_ms); - - handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param); - - S32 getNumDeletes() { return (S32)mDeleteList.size(); } // debug - -private: - void deleteWorker(LLWorkerClass* workerclass); // schedule for deletion - -}; - -//============================================================================ - -// This is a base class which any class with worker functions should derive from. -// Example Usage: -// LLMyWorkerClass* foo = new LLMyWorkerClass(); -// foo->fetchData(); // calls addWork() -// while(1) // main loop -// { -// if (foo->hasData()) // calls checkWork() -// foo->processData(); -// } -// -// WorkerClasses only have one set of work functions. If they need to do multiple -// background tasks, use 'param' to switch amnong them. -// Only one background task can be active at a time (per instance). -// i.e. don't call addWork() if haveWork() returns true - -class LL_COMMON_API LLWorkerClass -{ - friend class LLWorkerThread; - friend class LLWorkerThread::WorkRequest; - -public: - typedef LLWorkerThread::handle_t handle_t; - enum FLAGS - { - WCF_HAVE_WORK = 0x01, - WCF_WORKING = 0x02, - WCF_WORK_FINISHED = 0x10, - WCF_WORK_ABORTED = 0x20, - WCF_DELETE_REQUESTED = 0x40, - WCF_ABORT_REQUESTED = 0x80 - }; - -public: - LLWorkerClass(LLWorkerThread* workerthread, const std::string& name); - virtual ~LLWorkerClass(); - - // pure virtual, called from WORKER THREAD, returns TRUE if done - virtual bool doWork(S32 param)=0; // Called from WorkRequest::processRequest() - // virtual, called from finishRequest() after completed or aborted - virtual void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) - // virtual, returns true if safe to delete the worker - virtual bool deleteOK(); // called from update() (WORK THREAD) - - // schedlueDelete(): schedules deletion once aborted or completed - void scheduleDelete(); - - bool haveWork() { return getFlags(WCF_HAVE_WORK); } // may still be true if aborted - bool isWorking() { return getFlags(WCF_WORKING); } - bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); } - - const std::string& getName() const { return mWorkerClassName; } - -protected: - // called from WORKER THREAD - void setWorking(bool working); - - // Call from doWork only to avoid eating up cpu time. - // Returns true if work has been aborted - // yields the current thread and calls mWorkerThread->checkPause() - bool yield(); - - void setWorkerThread(LLWorkerThread* workerthread); - - // addWork(): calls startWork, adds doWork() to queue - void addWork(S32 param); - - // abortWork(): requests that work be aborted - void abortWork(bool autocomplete); - - // checkWork(): if doWork is complete or aborted, call endWork() and return true - bool checkWork(bool aborting = false); - -private: - void setFlags(U32 flags) { mWorkFlags = mWorkFlags | flags; } - void clearFlags(U32 flags) { mWorkFlags = mWorkFlags & ~flags; } - U32 getFlags() { return mWorkFlags; } -public: - bool getFlags(U32 flags) { return mWorkFlags & flags ? true : false; } - -private: - // pure virtuals - virtual void startWork(S32 param)=0; // called from addWork() (MAIN THREAD) - virtual void endWork(S32 param, bool aborted)=0; // called from doWork() (MAIN THREAD) - -protected: - LLWorkerThread* mWorkerThread; - std::string mWorkerClassName; - handle_t mRequestHandle; - -private: - LLMutex mMutex; - LLAtomicU32 mWorkFlags; -}; - -//============================================================================ - - -#endif // LL_LLWORKERTHREAD_H +/**
+ * @file llworkerthread.h
+ *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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_LLWORKERTHREAD_H
+#define LL_LLWORKERTHREAD_H
+
+#include <list>
+#include <map>
+#include <queue>
+#include <set>
+#include <string>
+
+#include "llqueuedthread.h"
+#include "llatomic.h"
+#include "llmutex.h"
+
+#define USE_FRAME_CALLBACK_MANAGER 0
+
+//============================================================================
+
+class LLWorkerClass;
+
+//============================================================================
+// Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small
+// It is assumed that LLWorkerThreads are rarely created/destroyed.
+
+class LL_COMMON_API LLWorkerThread : public LLQueuedThread
+{
+ friend class LLWorkerClass;
+public:
+ class WorkRequest : public LLQueuedThread::QueuedRequest
+ {
+ protected:
+ virtual ~WorkRequest(); // use deleteRequest()
+
+ public:
+ WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param);
+
+ S32 getParam()
+ {
+ return mParam;
+ }
+ LLWorkerClass* getWorkerClass()
+ {
+ return mWorkerClass;
+ }
+
+ /*virtual*/ bool processRequest();
+ /*virtual*/ void finishRequest(bool completed);
+ /*virtual*/ void deleteRequest();
+
+ private:
+ LLWorkerClass* mWorkerClass;
+ S32 mParam;
+ };
+
+protected:
+ void clearDeleteList() ;
+
+private:
+ typedef std::list<LLWorkerClass*> delete_list_t;
+ delete_list_t mDeleteList;
+ LLMutex* mDeleteMutex;
+
+public:
+ LLWorkerThread(const std::string& name, bool threaded = true, bool should_pause = false);
+ ~LLWorkerThread();
+
+ /*virtual*/ size_t update(F32 max_time_ms);
+
+ handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param);
+
+ S32 getNumDeletes() { return (S32)mDeleteList.size(); } // debug
+
+private:
+ void deleteWorker(LLWorkerClass* workerclass); // schedule for deletion
+
+};
+
+//============================================================================
+
+// This is a base class which any class with worker functions should derive from.
+// Example Usage:
+// LLMyWorkerClass* foo = new LLMyWorkerClass();
+// foo->fetchData(); // calls addWork()
+// while(1) // main loop
+// {
+// if (foo->hasData()) // calls checkWork()
+// foo->processData();
+// }
+//
+// WorkerClasses only have one set of work functions. If they need to do multiple
+// background tasks, use 'param' to switch amnong them.
+// Only one background task can be active at a time (per instance).
+// i.e. don't call addWork() if haveWork() returns true
+
+class LL_COMMON_API LLWorkerClass
+{
+ friend class LLWorkerThread;
+ friend class LLWorkerThread::WorkRequest;
+
+public:
+ typedef LLWorkerThread::handle_t handle_t;
+ enum FLAGS
+ {
+ WCF_HAVE_WORK = 0x01,
+ WCF_WORKING = 0x02,
+ WCF_WORK_FINISHED = 0x10,
+ WCF_WORK_ABORTED = 0x20,
+ WCF_DELETE_REQUESTED = 0x40,
+ WCF_ABORT_REQUESTED = 0x80
+ };
+
+public:
+ LLWorkerClass(LLWorkerThread* workerthread, const std::string& name);
+ virtual ~LLWorkerClass();
+
+ // pure virtual, called from WORKER THREAD, returns true if done
+ virtual bool doWork(S32 param)=0; // Called from WorkRequest::processRequest()
+ // virtual, called from finishRequest() after completed or aborted
+ virtual void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD)
+ // virtual, returns true if safe to delete the worker
+ virtual bool deleteOK(); // called from update() (WORK THREAD)
+
+ // schedlueDelete(): schedules deletion once aborted or completed
+ void scheduleDelete();
+
+ bool haveWork() { return getFlags(WCF_HAVE_WORK); } // may still be true if aborted
+ bool isWorking() { return getFlags(WCF_WORKING); }
+ bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); }
+
+ const std::string& getName() const { return mWorkerClassName; }
+
+protected:
+ // called from WORKER THREAD
+ void setWorking(bool working);
+
+ // Call from doWork only to avoid eating up cpu time.
+ // Returns true if work has been aborted
+ // yields the current thread and calls mWorkerThread->checkPause()
+ bool yield();
+
+ void setWorkerThread(LLWorkerThread* workerthread);
+
+ // addWork(): calls startWork, adds doWork() to queue
+ void addWork(S32 param);
+
+ // abortWork(): requests that work be aborted
+ void abortWork(bool autocomplete);
+
+ // checkWork(): if doWork is complete or aborted, call endWork() and return true
+ bool checkWork(bool aborting = false);
+
+private:
+ void setFlags(U32 flags) { mWorkFlags = mWorkFlags | flags; }
+ void clearFlags(U32 flags) { mWorkFlags = mWorkFlags & ~flags; }
+ U32 getFlags() { return mWorkFlags; }
+public:
+ bool getFlags(U32 flags) { return (mWorkFlags & flags) != 0; }
+
+private:
+ // pure virtuals
+ virtual void startWork(S32 param)=0; // called from addWork() (MAIN THREAD)
+ virtual void endWork(S32 param, bool aborted)=0; // called from doWork() (MAIN THREAD)
+
+protected:
+ LLWorkerThread* mWorkerThread;
+ std::string mWorkerClassName;
+ handle_t mRequestHandle;
+
+private:
+ LLMutex mMutex;
+ LLAtomicU32 mWorkFlags;
+};
+
+//============================================================================
+
+
+#endif // LL_LLWORKERTHREAD_H
diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 3fadfa5334..89ef5b3450 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -1,871 +1,871 @@ -/** - * @file llstring_test.cpp - * @author Adroit, Steve Linden, Tofu Linden - * @date 2006-12-24 - * @brief Test cases of llstring.cpp - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, 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 "linden_common.h" - -#include <boost/assign/list_of.hpp> -#include "../llstring.h" -#include "StringVec.h" // must come BEFORE lltut.h -#include "../test/lltut.h" - -using boost::assign::list_of; - -namespace tut -{ - struct string_index - { - }; - typedef test_group<string_index> string_index_t; - typedef string_index_t::object string_index_object_t; - tut::string_index_t tut_string_index("LLString"); - - template<> template<> - void string_index_object_t::test<1>() - { - std::string llstr1; - ensure("Empty std::string", (llstr1.size() == 0) && llstr1.empty()); - - std::string llstr2("Hello"); - ensure("std::string = Hello", (!strcmp(llstr2.c_str(), "Hello")) && (llstr2.size() == 5) && !llstr2.empty()); - - std::string llstr3(llstr2); - ensure("std::string = std::string(std::string)", (!strcmp(llstr3.c_str(), "Hello")) && (llstr3.size() == 5) && !llstr3.empty()); - - std::string str("Hello World"); - std::string llstr4(str, 6); - ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (!strcmp(llstr4.c_str(), "World")) && (llstr4.size() == 5) && !llstr4.empty()); - - std::string llstr5(str, str.size()); - ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (llstr5.size() == 0) && llstr5.empty()); - - std::string llstr6(5, 'A'); - ensure("std::string = std::string(count, c)", (!strcmp(llstr6.c_str(), "AAAAA")) && (llstr6.size() == 5) && !llstr6.empty()); - - std::string llstr7("Hello World", 5); - ensure("std::string(s, n)", (!strcmp(llstr7.c_str(), "Hello")) && (llstr7.size() == 5) && !llstr7.empty()); - - std::string llstr8("Hello World", 6, 5); - ensure("std::string(s, n, count)", (!strcmp(llstr8.c_str(), "World")) && (llstr8.size() == 5) && !llstr8.empty()); - - std::string llstr9("Hello World", sizeof("Hello World")-1, 5); // go past end - ensure("std::string(s, n, count) goes past end", (llstr9.size() == 0) && llstr9.empty()); - } - - template<> template<> - void string_index_object_t::test<3>() - { - std::string str("Len=5"); - ensure("isValidIndex failed", LLStringUtil::isValidIndex(str, 0) == TRUE && - LLStringUtil::isValidIndex(str, 5) == TRUE && - LLStringUtil::isValidIndex(str, 6) == FALSE); - - std::string str1; - ensure("isValidIndex failed fo rempty string", LLStringUtil::isValidIndex(str1, 0) == FALSE); - } - - template<> template<> - void string_index_object_t::test<4>() - { - std::string str_val(" Testing the extra whitespaces "); - LLStringUtil::trimHead(str_val); - ensure_equals("1: trimHead failed", str_val, "Testing the extra whitespaces "); - - std::string str_val1("\n\t\r\n Testing the extra whitespaces "); - LLStringUtil::trimHead(str_val1); - ensure_equals("2: trimHead failed", str_val1, "Testing the extra whitespaces "); - } - - template<> template<> - void string_index_object_t::test<5>() - { - std::string str_val(" Testing the extra whitespaces "); - LLStringUtil::trimTail(str_val); - ensure_equals("1: trimTail failed", str_val, " Testing the extra whitespaces"); - - std::string str_val1("\n Testing the extra whitespaces \n\t\r\n "); - LLStringUtil::trimTail(str_val1); - ensure_equals("2: trimTail failed", str_val1, "\n Testing the extra whitespaces"); - } - - - template<> template<> - void string_index_object_t::test<6>() - { - std::string str_val(" \t \r Testing the extra \r\n whitespaces \n \t "); - LLStringUtil::trim(str_val); - ensure_equals("1: trim failed", str_val, "Testing the extra \r\n whitespaces"); - } - - template<> template<> - void string_index_object_t::test<7>() - { - std::string str("Second LindenLabs"); - LLStringUtil::truncate(str, 6); - ensure_equals("1: truncate", str, "Second"); - - // further truncate more than the length - LLStringUtil::truncate(str, 0); - ensure_equals("2: truncate", str, ""); - } - - template<> template<> - void string_index_object_t::test<8>() - { - std::string str_val("SecondLife Source"); - LLStringUtil::toUpper(str_val); - ensure_equals("toUpper failed", str_val, "SECONDLIFE SOURCE"); - } - - template<> template<> - void string_index_object_t::test<9>() - { - std::string str_val("SecondLife Source"); - LLStringUtil::toLower(str_val); - ensure_equals("toLower failed", str_val, "secondlife source"); - } - - template<> template<> - void string_index_object_t::test<10>() - { - std::string str_val("Second"); - ensure("1. isHead failed", LLStringUtil::isHead(str_val, "SecondLife Source") == TRUE); - ensure("2. isHead failed", LLStringUtil::isHead(str_val, " SecondLife Source") == FALSE); - std::string str_val2(""); - ensure("3. isHead failed", LLStringUtil::isHead(str_val2, "") == FALSE); - } - - template<> template<> - void string_index_object_t::test<11>() - { - std::string str_val("Hello.\n\n Lindenlabs. \n This is \na simple test.\n"); - std::string orig_str_val(str_val); - LLStringUtil::addCRLF(str_val); - ensure_equals("addCRLF failed", str_val, "Hello.\r\n\r\n Lindenlabs. \r\n This is \r\na simple test.\r\n"); - LLStringUtil::removeCRLF(str_val); - ensure_equals("removeCRLF failed", str_val, orig_str_val); - } - - template<> template<> - void string_index_object_t::test<12>() - { - std::string str_val("Hello.\n\n\t \t Lindenlabs. \t\t"); - std::string orig_str_val(str_val); - LLStringUtil::replaceTabsWithSpaces(str_val, 1); - ensure_equals("replaceTabsWithSpaces failed", str_val, "Hello.\n\n Lindenlabs. "); - LLStringUtil::replaceTabsWithSpaces(orig_str_val, 0); - ensure_equals("replaceTabsWithSpaces failed for 0", orig_str_val, "Hello.\n\n Lindenlabs. "); - - str_val = "\t\t\t\t"; - LLStringUtil::replaceTabsWithSpaces(str_val, 0); - ensure_equals("replaceTabsWithSpaces failed for all tabs", str_val, ""); - } - - template<> template<> - void string_index_object_t::test<13>() - { - std::string str_val("Hello.\n\n\t\t\r\nLindenlabsX."); - LLStringUtil::replaceNonstandardASCII(str_val, 'X'); - ensure_equals("replaceNonstandardASCII failed", str_val, "Hello.\n\nXXX\nLindenlabsX."); - } - - template<> template<> - void string_index_object_t::test<14>() - { - std::string str_val("Hello.\n\t\r\nABCDEFGHIABABAB"); - LLStringUtil::replaceChar(str_val, 'A', 'X'); - ensure_equals("1: replaceChar failed", str_val, "Hello.\n\t\r\nXBCDEFGHIXBXBXB"); - std::string str_val1("Hello.\n\t\r\nABCDEFGHIABABAB"); - } - - template<> template<> - void string_index_object_t::test<15>() - { - std::string str_val("Hello.\n\r\t"); - ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == TRUE); - - str_val = "ABC "; - ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == FALSE); - } - - template<> template<> - void string_index_object_t::test<16>() - { - std::string str_val("Hello.\n\r\t Again!"); - LLStringUtil::stripNonprintable(str_val); - ensure_equals("stripNonprintable failed", str_val, "Hello. Again!"); - - str_val = "\r\n\t\t"; - LLStringUtil::stripNonprintable(str_val); - ensure_equals("stripNonprintable resulting in empty string failed", str_val, ""); - - str_val = ""; - LLStringUtil::stripNonprintable(str_val); - ensure_equals("stripNonprintable of empty string resulting in empty string failed", str_val, ""); - } - - template<> template<> - void string_index_object_t::test<17>() - { - BOOL value; - std::string str_val("1"); - ensure("convertToBOOL 1 failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "T"; - ensure("convertToBOOL T failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "t"; - ensure("convertToBOOL t failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "TRUE"; - ensure("convertToBOOL TRUE failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "True"; - ensure("convertToBOOL True failed", LLStringUtil::convertToBOOL(str_val, value) && value); - str_val = "true"; - ensure("convertToBOOL true failed", LLStringUtil::convertToBOOL(str_val, value) && value); - - str_val = "0"; - ensure("convertToBOOL 0 failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "F"; - ensure("convertToBOOL F failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "f"; - ensure("convertToBOOL f failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "FALSE"; - ensure("convertToBOOL FASLE failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "False"; - ensure("convertToBOOL False failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - str_val = "false"; - ensure("convertToBOOL false failed", LLStringUtil::convertToBOOL(str_val, value) && !value); - - str_val = "Tblah"; - ensure("convertToBOOL false failed", !LLStringUtil::convertToBOOL(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<18>() - { - U8 value; - std::string str_val("255"); - ensure("1: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 255); - - str_val = "0"; - ensure("2: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 0); - - str_val = "-1"; - ensure("3: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); - - str_val = "256"; // bigger than MAX_U8 - ensure("4: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<19>() - { - S8 value; - std::string str_val("127"); - ensure("1: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 127); - - str_val = "0"; - ensure("2: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 0); - - str_val = "-128"; - ensure("3: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == -128); - - str_val = "128"; // bigger than MAX_S8 - ensure("4: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); - - str_val = "-129"; - ensure("5: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<20>() - { - S16 value; - std::string str_val("32767"); - ensure("1: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 32767); - - str_val = "0"; - ensure("2: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 0); - - str_val = "-32768"; - ensure("3: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == -32768); - - str_val = "32768"; - ensure("4: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); - - str_val = "-32769"; - ensure("5: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<21>() - { - U16 value; - std::string str_val("65535"); //0xFFFF - ensure("1: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 65535); - - str_val = "0"; - ensure("2: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 0); - - str_val = "-1"; - ensure("3: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); - - str_val = "65536"; - ensure("4: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<22>() - { - U32 value; - std::string str_val("4294967295"); //0xFFFFFFFF - ensure("1: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 4294967295UL); - - str_val = "0"; - ensure("2: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 0); - - str_val = "4294967296"; - ensure("3: convertToU32 failed", !LLStringUtil::convertToU32(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<23>() - { - S32 value; - std::string str_val("2147483647"); //0x7FFFFFFF - ensure("1: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 2147483647); - - str_val = "0"; - ensure("2: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 0); - - // Avoid "unary minus operator applied to unsigned type" warning on VC++. JC - S32 min_val = -2147483647 - 1; - str_val = "-2147483648"; - ensure("3: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == min_val); - - str_val = "2147483648"; - ensure("4: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); - - str_val = "-2147483649"; - ensure("5: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); - } - - template<> template<> - void string_index_object_t::test<24>() - { - F32 value; - std::string str_val("2147483647"); //0x7FFFFFFF - ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647); - - str_val = "0"; - ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0); - - /* Need to find max/min F32 values - str_val = "-2147483648"; - ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); - - str_val = "2147483648"; - ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - - str_val = "-2147483649"; - ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - */ - } - - template<> template<> - void string_index_object_t::test<25>() - { - F64 value; - std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF - ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807LL); - - str_val = "0"; - ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F); - - /* Need to find max/min F64 values - str_val = "-2147483648"; - ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); - - str_val = "2147483648"; - ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - - str_val = "-2147483649"; - ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); - */ - } - - template<> template<> - void string_index_object_t::test<26>() - { - const char* str1 = NULL; - const char* str2 = NULL; - - ensure("1: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); - str2 = "A"; - ensure("2: compareStrings failed", LLStringUtil::compareStrings(str1, str2) > 0); - ensure("3: compareStrings failed", LLStringUtil::compareStrings(str2, str1) < 0); - - str1 = "A is smaller than B"; - str2 = "B is greater than A"; - ensure("4: compareStrings failed", LLStringUtil::compareStrings(str1, str2) < 0); - - str2 = "A is smaller than B"; - ensure("5: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); - } - - template<> template<> - void string_index_object_t::test<27>() - { - const char* str1 = NULL; - const char* str2 = NULL; - - ensure("1: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); - str2 = "A"; - ensure("2: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) > 0); - ensure("3: compareInsensitive failed", LLStringUtil::compareInsensitive(str2, str1) < 0); - - str1 = "A is equal to a"; - str2 = "a is EQUAL to A"; - ensure("4: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); - } - - template<> template<> - void string_index_object_t::test<28>() - { - std::string lhs_str("PROgraM12files"); - std::string rhs_str("PROgram12Files"); - ensure("compareDict 1 failed", LLStringUtil::compareDict(lhs_str, rhs_str) < 0); - ensure("precedesDict 1 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == TRUE); - - lhs_str = "PROgram12Files"; - rhs_str = "PROgram12Files"; - ensure("compareDict 2 failed", LLStringUtil::compareDict(lhs_str, rhs_str) == 0); - ensure("precedesDict 2 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == FALSE); - - lhs_str = "PROgram12Files"; - rhs_str = "PROgRAM12FILES"; - ensure("compareDict 3 failed", LLStringUtil::compareDict(lhs_str, rhs_str) > 0); - ensure("precedesDict 3 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == FALSE); - } - - template<> template<> - void string_index_object_t::test<29>() - { - char str1[] = "First String..."; - char str2[100]; - - LLStringUtil::copy(str2, str1, 100); - ensure("LLStringUtil::copy with enough dest length failed", strcmp(str2, str1) == 0); - LLStringUtil::copy(str2, str1, sizeof("First")); - ensure("LLStringUtil::copy with less dest length failed", strcmp(str2, "First") == 0); - } - - template<> template<> - void string_index_object_t::test<30>() - { - std::string str1 = "This is the sentence..."; - std::string str2 = "This is the "; - std::string str3 = "first "; - std::string str4 = "This is the first sentence..."; - std::string str5 = "This is the sentence...first "; - std::string dest; - - dest = str1; - LLStringUtil::copyInto(dest, str3, str2.length()); - ensure("LLStringUtil::copyInto insert failed", dest == str4); - - dest = str1; - LLStringUtil::copyInto(dest, str3, dest.length()); - ensure("LLStringUtil::copyInto append failed", dest == str5); - } - - template<> template<> - void string_index_object_t::test<31>() - { - std::string stripped; - - // Plain US ASCII text, including spaces and punctuation, - // should not be altered. - std::string simple_text = "Hello, world!"; - stripped = LLStringFn::strip_invalid_xml(simple_text); - ensure("Simple text passed unchanged", stripped == simple_text); - - // Control characters should be removed - // except for 0x09, 0x0a, 0x0d - std::string control_chars; - for (char c = 0x01; c < 0x20; c++) - { - control_chars.push_back(c); - } - std::string allowed_control_chars; - allowed_control_chars.push_back( (char)0x09 ); - allowed_control_chars.push_back( (char)0x0a ); - allowed_control_chars.push_back( (char)0x0d ); - - stripped = LLStringFn::strip_invalid_xml(control_chars); - ensure("Only tab, LF, CR control characters allowed", - stripped == allowed_control_chars); - - // UTF-8 should be passed intact, including high byte - // characters. Try Francais (with C squiggle cedilla) - std::string french = "Fran"; - french.push_back( (char)0xC3 ); - french.push_back( (char)0xA7 ); - french += "ais"; - stripped = LLStringFn::strip_invalid_xml( french ); - ensure("UTF-8 high byte text is allowed", french == stripped ); - } - - template<> template<> - void string_index_object_t::test<32>() - { - // Test LLStringUtil::format() string interpolation - LLStringUtil::format_map_t fmt_map; - std::string s; - int subcount; - - fmt_map["[TRICK1]"] = "[A]"; - fmt_map["[A]"] = "a"; - fmt_map["[B]"] = "b"; - fmt_map["[AAA]"] = "aaa"; - fmt_map["[BBB]"] = "bbb"; - fmt_map["[TRICK2]"] = "[A]"; - fmt_map["[EXPLOIT]"] = "!!!!!!!!!!!![EXPLOIT]!!!!!!!!!!!!"; - fmt_map["[KEYLONGER]"] = "short"; - fmt_map["[KEYSHORTER]"] = "Am I not a long string?"; - fmt_map["?"] = "?"; - fmt_map["[DELETE]"] = ""; - fmt_map["[]"] = "[]"; // doesn't do a substitution, but shouldn't crash either - - for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) - { - // Test when source string is entirely one key - std::string s1 = (std::string)iter->first; - std::string s2 = (std::string)iter->second; - subcount = LLStringUtil::format(s1, fmt_map); - ensure_equals("LLStringUtil::format: Raw interpolation result", s1, s2); - if (s1 == "?" || s1 == "[]") // no interp expected - { - ensure_equals("LLStringUtil::format: Raw interpolation result count", 0, subcount); - } - else - { - ensure_equals("LLStringUtil::format: Raw interpolation result count", 1, subcount); - } - } - - for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) - { - // Test when source string is one key, duplicated - std::string s1 = (std::string)iter->first; - std::string s2 = (std::string)iter->second; - s = s1 + s1 + s1 + s1; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Rawx4 interpolation result", s, s2 + s2 + s2 + s2); - if (s1 == "?" || s1 == "[]") // no interp expected - { - ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 0, subcount); - } - else - { - ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 4, subcount); - } - } - - // Test when source string has no keys - std::string srcs = "!!!!!!!!!!!!!!!!"; - s = srcs; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: No key test result", s, srcs); - ensure_equals("LLStringUtil::format: No key test result count", 0, subcount); - - // Test when source string has no keys and is empty - std::string srcs3; - s = srcs3; - subcount = LLStringUtil::format(s, fmt_map); - ensure("LLStringUtil::format: No key test3 result", s.empty()); - ensure_equals("LLStringUtil::format: No key test3 result count", 0, subcount); - - // Test a substitution where a key is substituted with blankness - std::string srcs2 = "[DELETE]"; - s = srcs2; - subcount = LLStringUtil::format(s, fmt_map); - ensure("LLStringUtil::format: Delete key test2 result", s.empty()); - ensure_equals("LLStringUtil::format: Delete key test2 result count", 1, subcount); - - // Test an assorted substitution - std::string srcs4 = "[TRICK1][A][B][AAA][BBB][TRICK2][KEYLONGER][KEYSHORTER]?[DELETE]"; - s = srcs4; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test1 result", s, "[A]abaaabbb[A]shortAm I not a long string??"); - ensure_equals("LLStringUtil::format: Assorted Test1 result count", 9, subcount); - - // Test an assorted substitution - std::string srcs5 = "[DELETE]?[KEYSHORTER][KEYLONGER][TRICK2][BBB][AAA][B][A][TRICK1]"; - s = srcs5; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "?Am I not a long string?short[A]bbbaaaba[A]"); - ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); - - // Test on nested brackets - std::string srcs6 = "[[TRICK1]][[A]][[B]][[AAA]][[BBB]][[TRICK2]][[KEYLONGER]][[KEYSHORTER]]?[[DELETE]]"; - s = srcs6; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "[[A]][a][b][aaa][bbb][[A]][short][Am I not a long string?]?[]"); - ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); - - - // Test an assorted substitution - std::string srcs8 = "foo[DELETE]bar?"; - s = srcs8; - subcount = LLStringUtil::format(s, fmt_map); - ensure_equals("LLStringUtil::format: Assorted Test3 result", s, "foobar?"); - ensure_equals("LLStringUtil::format: Assorted Test3 result count", 1, subcount); - } - - template<> template<> - void string_index_object_t::test<33>() - { - // Test LLStringUtil::format() string interpolation - LLStringUtil::format_map_t blank_fmt_map; - std::string s; - int subcount; - - // Test substituting out of a blank format_map - std::string srcs6 = "12345"; - s = srcs6; - subcount = LLStringUtil::format(s, blank_fmt_map); - ensure_equals("LLStringUtil::format: Blankfmt Test1 result", s, "12345"); - ensure_equals("LLStringUtil::format: Blankfmt Test1 result count", 0, subcount); - - // Test substituting a blank string out of a blank format_map - std::string srcs7; - s = srcs7; - subcount = LLStringUtil::format(s, blank_fmt_map); - ensure("LLStringUtil::format: Blankfmt Test2 result", s.empty()); - ensure_equals("LLStringUtil::format: Blankfmt Test2 result count", 0, subcount); - } - - template<> template<> - void string_index_object_t::test<34>() - { - // Test that incorrect LLStringUtil::format() use does not explode. - LLStringUtil::format_map_t nasty_fmt_map; - std::string s; - int subcount; - - nasty_fmt_map[""] = "never used"; // see, this is nasty. - - // Test substituting out of a nasty format_map - std::string srcs6 = "12345"; - s = srcs6; - subcount = LLStringUtil::format(s, nasty_fmt_map); - ensure_equals("LLStringUtil::format: Nastyfmt Test1 result", s, "12345"); - ensure_equals("LLStringUtil::format: Nastyfmt Test1 result count", 0, subcount); - - // Test substituting a blank string out of a nasty format_map - std::string srcs7; - s = srcs7; - subcount = LLStringUtil::format(s, nasty_fmt_map); - ensure("LLStringUtil::format: Nastyfmt Test2 result", s.empty()); - ensure_equals("LLStringUtil::format: Nastyfmt Test2 result count", 0, subcount); - } - - template<> template<> - void string_index_object_t::test<35>() - { - // Make sure startsWith works - std::string string("anybody in there?"); - std::string substr("anybody"); - ensure("startsWith works.", LLStringUtil::startsWith(string, substr)); - } - - template<> template<> - void string_index_object_t::test<36>() - { - // Make sure startsWith correctly fails - std::string string("anybody in there?"); - std::string substr("there"); - ensure("startsWith fails.", !LLStringUtil::startsWith(string, substr)); - } - - template<> template<> - void string_index_object_t::test<37>() - { - // startsWith fails on empty strings - std::string value("anybody in there?"); - std::string empty; - ensure("empty string.", !LLStringUtil::startsWith(value, empty)); - ensure("empty substr.", !LLStringUtil::startsWith(empty, value)); - ensure("empty everything.", !LLStringUtil::startsWith(empty, empty)); - } - - template<> template<> - void string_index_object_t::test<38>() - { - // Make sure endsWith works correctly - std::string string("anybody in there?"); - std::string substr("there?"); - ensure("endsWith works.", LLStringUtil::endsWith(string, substr)); - } - - template<> template<> - void string_index_object_t::test<39>() - { - // Make sure endsWith correctly fails - std::string string("anybody in there?"); - std::string substr("anybody"); - ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); - substr = "there"; - ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); - substr = "ther?"; - ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); - } - - template<> template<> - void string_index_object_t::test<40>() - { - // endsWith fails on empty strings - std::string value("anybody in there?"); - std::string empty; - ensure("empty string.", !LLStringUtil::endsWith(value, empty)); - ensure("empty substr.", !LLStringUtil::endsWith(empty, value)); - ensure("empty everything.", !LLStringUtil::endsWith(empty, empty)); - } - - template<> template<> - void string_index_object_t::test<41>() - { - set_test_name("getTokens(\"delims\")"); - ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec()); - ensure_equals("only delims", - LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec()); - ensure_equals("sequence of delims", - LLStringUtil::getTokens(",,, one ,,,", ","), list_of("one")); - // nat considers this a dubious implementation side effect, but I'd - // hate to change it now... - ensure_equals("noncontiguous tokens", - LLStringUtil::getTokens(", ,, , one ,,,", ","), list_of("")("")("one")); - ensure_equals("space-padded tokens", - LLStringUtil::getTokens(", one , two ,", ","), list_of("one")("two")); - ensure_equals("no delims", LLStringUtil::getTokens("one", ","), list_of("one")); - } - - // Shorthand for verifying that getTokens() behaves the same when you - // don't pass a string of escape characters, when you pass an empty string - // (different overloads), and when you pass a string of characters that - // aren't actually present. - void ensure_getTokens(const std::string& desc, - const std::string& string, - const std::string& drop_delims, - const std::string& keep_delims, - const std::string& quotes, - const std::vector<std::string>& expect) - { - ensure_equals(desc + " - no esc", - LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes), - expect); - ensure_equals(desc + " - empty esc", - LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""), - expect); - ensure_equals(desc + " - unused esc", - LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"), - expect); - } - - void ensure_getTokens(const std::string& desc, - const std::string& string, - const std::string& drop_delims, - const std::string& keep_delims, - const std::vector<std::string>& expect) - { - ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect); - } - - template<> template<> - void string_index_object_t::test<42>() - { - set_test_name("getTokens(\"delims\", etc.)"); - // Signatures to test in this method: - // getTokens(string, drop_delims, keep_delims [, quotes [, escapes]]) - // If you omit keep_delims, you get the older function (test above). - - // cases like the getTokens(string, delims) tests above - ensure_getTokens("empty string", "", " ", "", StringVec()); - ensure_getTokens("only delims", - " \r\n ", " \r\n", "", StringVec()); - ensure_getTokens("sequence of delims", - ",,, one ,,,", ", ", "", list_of("one")); - // Note contrast with the case in the previous method - ensure_getTokens("noncontiguous tokens", - ", ,, , one ,,,", ", ", "", list_of("one")); - ensure_getTokens("space-padded tokens", - ", one , two ,", ", ", "", - list_of("one")("two")); - ensure_getTokens("no delims", "one", ",", "", list_of("one")); - - // drop_delims vs. keep_delims - ensure_getTokens("arithmetic", - " ab+def / xx* yy ", " ", "+-*/", - list_of("ab")("+")("def")("/")("xx")("*")("yy")); - - // quotes - ensure_getTokens("no quotes", - "She said, \"Don't go.\"", " ", ",", "", - list_of("She")("said")(",")("\"Don't")("go.\"")); - ensure_getTokens("quotes", - "She said, \"Don't go.\"", " ", ",", "\"", - list_of("She")("said")(",")("Don't go.")); - ensure_getTokens("quotes and delims", - "run c:/'Documents and Settings'/someone", " ", "", "'", - list_of("run")("c:/Documents and Settings/someone")); - ensure_getTokens("unmatched quote", - "baby don't leave", " ", "", "'", - list_of("baby")("don't")("leave")); - ensure_getTokens("adjacent quoted", - "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'", - list_of("abcdef \"ghijkl' mnopqr")); - ensure_getTokens("quoted empty string", - "--set SomeVar ''", " ", "", "'", - list_of("--set")("SomeVar")("")); - - // escapes - // Don't use backslash as an escape for these tests -- you'll go nuts - // between the C++ string scanner and getTokens() escapes. Test with - // something else! - ensure_equals("escaped delims", - LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"), - list_of(" a")("-")("dog-gone phrase")); - ensure_equals("escaped quotes", - LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"), - list_of("say:")("this isn't working.")); - ensure_equals("escaped escape", - LLStringUtil::getTokens("want x^^2", " ", "", "", "^"), - list_of("want")("x^2")); - ensure_equals("escape at end", - LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"), - list_of("it's up")("there^")); - } -} +/**
+ * @file llstring_test.cpp
+ * @author Adroit, Steve Linden, Tofu Linden
+ * @date 2006-12-24
+ * @brief Test cases of llstring.cpp
+ *
+ * $LicenseInfo:firstyear=2007&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, 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 "linden_common.h"
+
+#include <boost/assign/list_of.hpp>
+#include "../llstring.h"
+#include "StringVec.h" // must come BEFORE lltut.h
+#include "../test/lltut.h"
+
+using boost::assign::list_of;
+
+namespace tut
+{
+ struct string_index
+ {
+ };
+ typedef test_group<string_index> string_index_t;
+ typedef string_index_t::object string_index_object_t;
+ tut::string_index_t tut_string_index("LLString");
+
+ template<> template<>
+ void string_index_object_t::test<1>()
+ {
+ std::string llstr1;
+ ensure("Empty std::string", (llstr1.size() == 0) && llstr1.empty());
+
+ std::string llstr2("Hello");
+ ensure("std::string = Hello", (!strcmp(llstr2.c_str(), "Hello")) && (llstr2.size() == 5) && !llstr2.empty());
+
+ std::string llstr3(llstr2);
+ ensure("std::string = std::string(std::string)", (!strcmp(llstr3.c_str(), "Hello")) && (llstr3.size() == 5) && !llstr3.empty());
+
+ std::string str("Hello World");
+ std::string llstr4(str, 6);
+ ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (!strcmp(llstr4.c_str(), "World")) && (llstr4.size() == 5) && !llstr4.empty());
+
+ std::string llstr5(str, str.size());
+ ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (llstr5.size() == 0) && llstr5.empty());
+
+ std::string llstr6(5, 'A');
+ ensure("std::string = std::string(count, c)", (!strcmp(llstr6.c_str(), "AAAAA")) && (llstr6.size() == 5) && !llstr6.empty());
+
+ std::string llstr7("Hello World", 5);
+ ensure("std::string(s, n)", (!strcmp(llstr7.c_str(), "Hello")) && (llstr7.size() == 5) && !llstr7.empty());
+
+ std::string llstr8("Hello World", 6, 5);
+ ensure("std::string(s, n, count)", (!strcmp(llstr8.c_str(), "World")) && (llstr8.size() == 5) && !llstr8.empty());
+
+ std::string llstr9("Hello World", sizeof("Hello World")-1, 5); // go past end
+ ensure("std::string(s, n, count) goes past end", (llstr9.size() == 0) && llstr9.empty());
+ }
+
+ template<> template<>
+ void string_index_object_t::test<3>()
+ {
+ std::string str("Len=5");
+ ensure("isValidIndex failed", LLStringUtil::isValidIndex(str, 0) == true &&
+ LLStringUtil::isValidIndex(str, 5) == true &&
+ LLStringUtil::isValidIndex(str, 6) == false);
+
+ std::string str1;
+ ensure("isValidIndex failed fo rempty string", LLStringUtil::isValidIndex(str1, 0) == false);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<4>()
+ {
+ std::string str_val(" Testing the extra whitespaces ");
+ LLStringUtil::trimHead(str_val);
+ ensure_equals("1: trimHead failed", str_val, "Testing the extra whitespaces ");
+
+ std::string str_val1("\n\t\r\n Testing the extra whitespaces ");
+ LLStringUtil::trimHead(str_val1);
+ ensure_equals("2: trimHead failed", str_val1, "Testing the extra whitespaces ");
+ }
+
+ template<> template<>
+ void string_index_object_t::test<5>()
+ {
+ std::string str_val(" Testing the extra whitespaces ");
+ LLStringUtil::trimTail(str_val);
+ ensure_equals("1: trimTail failed", str_val, " Testing the extra whitespaces");
+
+ std::string str_val1("\n Testing the extra whitespaces \n\t\r\n ");
+ LLStringUtil::trimTail(str_val1);
+ ensure_equals("2: trimTail failed", str_val1, "\n Testing the extra whitespaces");
+ }
+
+
+ template<> template<>
+ void string_index_object_t::test<6>()
+ {
+ std::string str_val(" \t \r Testing the extra \r\n whitespaces \n \t ");
+ LLStringUtil::trim(str_val);
+ ensure_equals("1: trim failed", str_val, "Testing the extra \r\n whitespaces");
+ }
+
+ template<> template<>
+ void string_index_object_t::test<7>()
+ {
+ std::string str("Second LindenLabs");
+ LLStringUtil::truncate(str, 6);
+ ensure_equals("1: truncate", str, "Second");
+
+ // further truncate more than the length
+ LLStringUtil::truncate(str, 0);
+ ensure_equals("2: truncate", str, "");
+ }
+
+ template<> template<>
+ void string_index_object_t::test<8>()
+ {
+ std::string str_val("SecondLife Source");
+ LLStringUtil::toUpper(str_val);
+ ensure_equals("toUpper failed", str_val, "SECONDLIFE SOURCE");
+ }
+
+ template<> template<>
+ void string_index_object_t::test<9>()
+ {
+ std::string str_val("SecondLife Source");
+ LLStringUtil::toLower(str_val);
+ ensure_equals("toLower failed", str_val, "secondlife source");
+ }
+
+ template<> template<>
+ void string_index_object_t::test<10>()
+ {
+ std::string str_val("Second");
+ ensure("1. isHead failed", LLStringUtil::isHead(str_val, "SecondLife Source") == true);
+ ensure("2. isHead failed", LLStringUtil::isHead(str_val, " SecondLife Source") == false);
+ std::string str_val2("");
+ ensure("3. isHead failed", LLStringUtil::isHead(str_val2, "") == false);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<11>()
+ {
+ std::string str_val("Hello.\n\n Lindenlabs. \n This is \na simple test.\n");
+ std::string orig_str_val(str_val);
+ LLStringUtil::addCRLF(str_val);
+ ensure_equals("addCRLF failed", str_val, "Hello.\r\n\r\n Lindenlabs. \r\n This is \r\na simple test.\r\n");
+ LLStringUtil::removeCRLF(str_val);
+ ensure_equals("removeCRLF failed", str_val, orig_str_val);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<12>()
+ {
+ std::string str_val("Hello.\n\n\t \t Lindenlabs. \t\t");
+ std::string orig_str_val(str_val);
+ LLStringUtil::replaceTabsWithSpaces(str_val, 1);
+ ensure_equals("replaceTabsWithSpaces failed", str_val, "Hello.\n\n Lindenlabs. ");
+ LLStringUtil::replaceTabsWithSpaces(orig_str_val, 0);
+ ensure_equals("replaceTabsWithSpaces failed for 0", orig_str_val, "Hello.\n\n Lindenlabs. ");
+
+ str_val = "\t\t\t\t";
+ LLStringUtil::replaceTabsWithSpaces(str_val, 0);
+ ensure_equals("replaceTabsWithSpaces failed for all tabs", str_val, "");
+ }
+
+ template<> template<>
+ void string_index_object_t::test<13>()
+ {
+ std::string str_val("Hello.\n\n\t\t\r\nLindenlabsX.");
+ LLStringUtil::replaceNonstandardASCII(str_val, 'X');
+ ensure_equals("replaceNonstandardASCII failed", str_val, "Hello.\n\nXXX\nLindenlabsX.");
+ }
+
+ template<> template<>
+ void string_index_object_t::test<14>()
+ {
+ std::string str_val("Hello.\n\t\r\nABCDEFGHIABABAB");
+ LLStringUtil::replaceChar(str_val, 'A', 'X');
+ ensure_equals("1: replaceChar failed", str_val, "Hello.\n\t\r\nXBCDEFGHIXBXBXB");
+ std::string str_val1("Hello.\n\t\r\nABCDEFGHIABABAB");
+ }
+
+ template<> template<>
+ void string_index_object_t::test<15>()
+ {
+ std::string str_val("Hello.\n\r\t");
+ ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == true);
+
+ str_val = "ABC ";
+ ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == false);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<16>()
+ {
+ std::string str_val("Hello.\n\r\t Again!");
+ LLStringUtil::stripNonprintable(str_val);
+ ensure_equals("stripNonprintable failed", str_val, "Hello. Again!");
+
+ str_val = "\r\n\t\t";
+ LLStringUtil::stripNonprintable(str_val);
+ ensure_equals("stripNonprintable resulting in empty string failed", str_val, "");
+
+ str_val = "";
+ LLStringUtil::stripNonprintable(str_val);
+ ensure_equals("stripNonprintable of empty string resulting in empty string failed", str_val, "");
+ }
+
+ template<> template<>
+ void string_index_object_t::test<17>()
+ {
+ bool value;
+ std::string str_val("1");
+ ensure("convertToBOOL 1 failed", LLStringUtil::convertToBOOL(str_val, value) && value);
+ str_val = "T";
+ ensure("convertToBOOL T failed", LLStringUtil::convertToBOOL(str_val, value) && value);
+ str_val = "t";
+ ensure("convertToBOOL t failed", LLStringUtil::convertToBOOL(str_val, value) && value);
+ str_val = "TRUE";
+ ensure("convertToBOOL TRUE failed", LLStringUtil::convertToBOOL(str_val, value) && value);
+ str_val = "True";
+ ensure("convertToBOOL True failed", LLStringUtil::convertToBOOL(str_val, value) && value);
+ str_val = "true";
+ ensure("convertToBOOL true failed", LLStringUtil::convertToBOOL(str_val, value) && value);
+
+ str_val = "0";
+ ensure("convertToBOOL 0 failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
+ str_val = "F";
+ ensure("convertToBOOL F failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
+ str_val = "f";
+ ensure("convertToBOOL f failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
+ str_val = "FALSE";
+ ensure("convertToBOOL FASLE failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
+ str_val = "False";
+ ensure("convertToBOOL False failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
+ str_val = "false";
+ ensure("convertToBOOL false failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
+
+ str_val = "Tblah";
+ ensure("convertToBOOL false failed", !LLStringUtil::convertToBOOL(str_val, value));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<18>()
+ {
+ U8 value;
+ std::string str_val("255");
+ ensure("1: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 255);
+
+ str_val = "0";
+ ensure("2: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 0);
+
+ str_val = "-1";
+ ensure("3: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value));
+
+ str_val = "256"; // bigger than MAX_U8
+ ensure("4: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<19>()
+ {
+ S8 value;
+ std::string str_val("127");
+ ensure("1: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 127);
+
+ str_val = "0";
+ ensure("2: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 0);
+
+ str_val = "-128";
+ ensure("3: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == -128);
+
+ str_val = "128"; // bigger than MAX_S8
+ ensure("4: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value));
+
+ str_val = "-129";
+ ensure("5: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<20>()
+ {
+ S16 value;
+ std::string str_val("32767");
+ ensure("1: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 32767);
+
+ str_val = "0";
+ ensure("2: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 0);
+
+ str_val = "-32768";
+ ensure("3: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == -32768);
+
+ str_val = "32768";
+ ensure("4: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value));
+
+ str_val = "-32769";
+ ensure("5: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<21>()
+ {
+ U16 value;
+ std::string str_val("65535"); //0xFFFF
+ ensure("1: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 65535);
+
+ str_val = "0";
+ ensure("2: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 0);
+
+ str_val = "-1";
+ ensure("3: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value));
+
+ str_val = "65536";
+ ensure("4: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<22>()
+ {
+ U32 value;
+ std::string str_val("4294967295"); //0xFFFFFFFF
+ ensure("1: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 4294967295UL);
+
+ str_val = "0";
+ ensure("2: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 0);
+
+ str_val = "4294967296";
+ ensure("3: convertToU32 failed", !LLStringUtil::convertToU32(str_val, value));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<23>()
+ {
+ S32 value;
+ std::string str_val("2147483647"); //0x7FFFFFFF
+ ensure("1: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 2147483647);
+
+ str_val = "0";
+ ensure("2: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 0);
+
+ // Avoid "unary minus operator applied to unsigned type" warning on VC++. JC
+ S32 min_val = -2147483647 - 1;
+ str_val = "-2147483648";
+ ensure("3: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == min_val);
+
+ str_val = "2147483648";
+ ensure("4: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value));
+
+ str_val = "-2147483649";
+ ensure("5: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<24>()
+ {
+ F32 value;
+ std::string str_val("2147483647"); //0x7FFFFFFF
+ ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647);
+
+ str_val = "0";
+ ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0);
+
+ /* Need to find max/min F32 values
+ str_val = "-2147483648";
+ ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648);
+
+ str_val = "2147483648";
+ ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value));
+
+ str_val = "-2147483649";
+ ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value));
+ */
+ }
+
+ template<> template<>
+ void string_index_object_t::test<25>()
+ {
+ F64 value;
+ std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF
+ ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807LL);
+
+ str_val = "0";
+ ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F);
+
+ /* Need to find max/min F64 values
+ str_val = "-2147483648";
+ ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648);
+
+ str_val = "2147483648";
+ ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value));
+
+ str_val = "-2147483649";
+ ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value));
+ */
+ }
+
+ template<> template<>
+ void string_index_object_t::test<26>()
+ {
+ const char* str1 = NULL;
+ const char* str2 = NULL;
+
+ ensure("1: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0);
+ str2 = "A";
+ ensure("2: compareStrings failed", LLStringUtil::compareStrings(str1, str2) > 0);
+ ensure("3: compareStrings failed", LLStringUtil::compareStrings(str2, str1) < 0);
+
+ str1 = "A is smaller than B";
+ str2 = "B is greater than A";
+ ensure("4: compareStrings failed", LLStringUtil::compareStrings(str1, str2) < 0);
+
+ str2 = "A is smaller than B";
+ ensure("5: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<27>()
+ {
+ const char* str1 = NULL;
+ const char* str2 = NULL;
+
+ ensure("1: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0);
+ str2 = "A";
+ ensure("2: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) > 0);
+ ensure("3: compareInsensitive failed", LLStringUtil::compareInsensitive(str2, str1) < 0);
+
+ str1 = "A is equal to a";
+ str2 = "a is EQUAL to A";
+ ensure("4: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<28>()
+ {
+ std::string lhs_str("PROgraM12files");
+ std::string rhs_str("PROgram12Files");
+ ensure("compareDict 1 failed", LLStringUtil::compareDict(lhs_str, rhs_str) < 0);
+ ensure("precedesDict 1 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == true);
+
+ lhs_str = "PROgram12Files";
+ rhs_str = "PROgram12Files";
+ ensure("compareDict 2 failed", LLStringUtil::compareDict(lhs_str, rhs_str) == 0);
+ ensure("precedesDict 2 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false);
+
+ lhs_str = "PROgram12Files";
+ rhs_str = "PROgRAM12FILES";
+ ensure("compareDict 3 failed", LLStringUtil::compareDict(lhs_str, rhs_str) > 0);
+ ensure("precedesDict 3 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<29>()
+ {
+ char str1[] = "First String...";
+ char str2[100];
+
+ LLStringUtil::copy(str2, str1, 100);
+ ensure("LLStringUtil::copy with enough dest length failed", strcmp(str2, str1) == 0);
+ LLStringUtil::copy(str2, str1, sizeof("First"));
+ ensure("LLStringUtil::copy with less dest length failed", strcmp(str2, "First") == 0);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<30>()
+ {
+ std::string str1 = "This is the sentence...";
+ std::string str2 = "This is the ";
+ std::string str3 = "first ";
+ std::string str4 = "This is the first sentence...";
+ std::string str5 = "This is the sentence...first ";
+ std::string dest;
+
+ dest = str1;
+ LLStringUtil::copyInto(dest, str3, str2.length());
+ ensure("LLStringUtil::copyInto insert failed", dest == str4);
+
+ dest = str1;
+ LLStringUtil::copyInto(dest, str3, dest.length());
+ ensure("LLStringUtil::copyInto append failed", dest == str5);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<31>()
+ {
+ std::string stripped;
+
+ // Plain US ASCII text, including spaces and punctuation,
+ // should not be altered.
+ std::string simple_text = "Hello, world!";
+ stripped = LLStringFn::strip_invalid_xml(simple_text);
+ ensure("Simple text passed unchanged", stripped == simple_text);
+
+ // Control characters should be removed
+ // except for 0x09, 0x0a, 0x0d
+ std::string control_chars;
+ for (char c = 0x01; c < 0x20; c++)
+ {
+ control_chars.push_back(c);
+ }
+ std::string allowed_control_chars;
+ allowed_control_chars.push_back( (char)0x09 );
+ allowed_control_chars.push_back( (char)0x0a );
+ allowed_control_chars.push_back( (char)0x0d );
+
+ stripped = LLStringFn::strip_invalid_xml(control_chars);
+ ensure("Only tab, LF, CR control characters allowed",
+ stripped == allowed_control_chars);
+
+ // UTF-8 should be passed intact, including high byte
+ // characters. Try Francais (with C squiggle cedilla)
+ std::string french = "Fran";
+ french.push_back( (char)0xC3 );
+ french.push_back( (char)0xA7 );
+ french += "ais";
+ stripped = LLStringFn::strip_invalid_xml( french );
+ ensure("UTF-8 high byte text is allowed", french == stripped );
+ }
+
+ template<> template<>
+ void string_index_object_t::test<32>()
+ {
+ // Test LLStringUtil::format() string interpolation
+ LLStringUtil::format_map_t fmt_map;
+ std::string s;
+ int subcount;
+
+ fmt_map["[TRICK1]"] = "[A]";
+ fmt_map["[A]"] = "a";
+ fmt_map["[B]"] = "b";
+ fmt_map["[AAA]"] = "aaa";
+ fmt_map["[BBB]"] = "bbb";
+ fmt_map["[TRICK2]"] = "[A]";
+ fmt_map["[EXPLOIT]"] = "!!!!!!!!!!!![EXPLOIT]!!!!!!!!!!!!";
+ fmt_map["[KEYLONGER]"] = "short";
+ fmt_map["[KEYSHORTER]"] = "Am I not a long string?";
+ fmt_map["?"] = "?";
+ fmt_map["[DELETE]"] = "";
+ fmt_map["[]"] = "[]"; // doesn't do a substitution, but shouldn't crash either
+
+ for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter)
+ {
+ // Test when source string is entirely one key
+ std::string s1 = (std::string)iter->first;
+ std::string s2 = (std::string)iter->second;
+ subcount = LLStringUtil::format(s1, fmt_map);
+ ensure_equals("LLStringUtil::format: Raw interpolation result", s1, s2);
+ if (s1 == "?" || s1 == "[]") // no interp expected
+ {
+ ensure_equals("LLStringUtil::format: Raw interpolation result count", 0, subcount);
+ }
+ else
+ {
+ ensure_equals("LLStringUtil::format: Raw interpolation result count", 1, subcount);
+ }
+ }
+
+ for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter)
+ {
+ // Test when source string is one key, duplicated
+ std::string s1 = (std::string)iter->first;
+ std::string s2 = (std::string)iter->second;
+ s = s1 + s1 + s1 + s1;
+ subcount = LLStringUtil::format(s, fmt_map);
+ ensure_equals("LLStringUtil::format: Rawx4 interpolation result", s, s2 + s2 + s2 + s2);
+ if (s1 == "?" || s1 == "[]") // no interp expected
+ {
+ ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 0, subcount);
+ }
+ else
+ {
+ ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 4, subcount);
+ }
+ }
+
+ // Test when source string has no keys
+ std::string srcs = "!!!!!!!!!!!!!!!!";
+ s = srcs;
+ subcount = LLStringUtil::format(s, fmt_map);
+ ensure_equals("LLStringUtil::format: No key test result", s, srcs);
+ ensure_equals("LLStringUtil::format: No key test result count", 0, subcount);
+
+ // Test when source string has no keys and is empty
+ std::string srcs3;
+ s = srcs3;
+ subcount = LLStringUtil::format(s, fmt_map);
+ ensure("LLStringUtil::format: No key test3 result", s.empty());
+ ensure_equals("LLStringUtil::format: No key test3 result count", 0, subcount);
+
+ // Test a substitution where a key is substituted with blankness
+ std::string srcs2 = "[DELETE]";
+ s = srcs2;
+ subcount = LLStringUtil::format(s, fmt_map);
+ ensure("LLStringUtil::format: Delete key test2 result", s.empty());
+ ensure_equals("LLStringUtil::format: Delete key test2 result count", 1, subcount);
+
+ // Test an assorted substitution
+ std::string srcs4 = "[TRICK1][A][B][AAA][BBB][TRICK2][KEYLONGER][KEYSHORTER]?[DELETE]";
+ s = srcs4;
+ subcount = LLStringUtil::format(s, fmt_map);
+ ensure_equals("LLStringUtil::format: Assorted Test1 result", s, "[A]abaaabbb[A]shortAm I not a long string??");
+ ensure_equals("LLStringUtil::format: Assorted Test1 result count", 9, subcount);
+
+ // Test an assorted substitution
+ std::string srcs5 = "[DELETE]?[KEYSHORTER][KEYLONGER][TRICK2][BBB][AAA][B][A][TRICK1]";
+ s = srcs5;
+ subcount = LLStringUtil::format(s, fmt_map);
+ ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "?Am I not a long string?short[A]bbbaaaba[A]");
+ ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount);
+
+ // Test on nested brackets
+ std::string srcs6 = "[[TRICK1]][[A]][[B]][[AAA]][[BBB]][[TRICK2]][[KEYLONGER]][[KEYSHORTER]]?[[DELETE]]";
+ s = srcs6;
+ subcount = LLStringUtil::format(s, fmt_map);
+ ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "[[A]][a][b][aaa][bbb][[A]][short][Am I not a long string?]?[]");
+ ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount);
+
+
+ // Test an assorted substitution
+ std::string srcs8 = "foo[DELETE]bar?";
+ s = srcs8;
+ subcount = LLStringUtil::format(s, fmt_map);
+ ensure_equals("LLStringUtil::format: Assorted Test3 result", s, "foobar?");
+ ensure_equals("LLStringUtil::format: Assorted Test3 result count", 1, subcount);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<33>()
+ {
+ // Test LLStringUtil::format() string interpolation
+ LLStringUtil::format_map_t blank_fmt_map;
+ std::string s;
+ int subcount;
+
+ // Test substituting out of a blank format_map
+ std::string srcs6 = "12345";
+ s = srcs6;
+ subcount = LLStringUtil::format(s, blank_fmt_map);
+ ensure_equals("LLStringUtil::format: Blankfmt Test1 result", s, "12345");
+ ensure_equals("LLStringUtil::format: Blankfmt Test1 result count", 0, subcount);
+
+ // Test substituting a blank string out of a blank format_map
+ std::string srcs7;
+ s = srcs7;
+ subcount = LLStringUtil::format(s, blank_fmt_map);
+ ensure("LLStringUtil::format: Blankfmt Test2 result", s.empty());
+ ensure_equals("LLStringUtil::format: Blankfmt Test2 result count", 0, subcount);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<34>()
+ {
+ // Test that incorrect LLStringUtil::format() use does not explode.
+ LLStringUtil::format_map_t nasty_fmt_map;
+ std::string s;
+ int subcount;
+
+ nasty_fmt_map[""] = "never used"; // see, this is nasty.
+
+ // Test substituting out of a nasty format_map
+ std::string srcs6 = "12345";
+ s = srcs6;
+ subcount = LLStringUtil::format(s, nasty_fmt_map);
+ ensure_equals("LLStringUtil::format: Nastyfmt Test1 result", s, "12345");
+ ensure_equals("LLStringUtil::format: Nastyfmt Test1 result count", 0, subcount);
+
+ // Test substituting a blank string out of a nasty format_map
+ std::string srcs7;
+ s = srcs7;
+ subcount = LLStringUtil::format(s, nasty_fmt_map);
+ ensure("LLStringUtil::format: Nastyfmt Test2 result", s.empty());
+ ensure_equals("LLStringUtil::format: Nastyfmt Test2 result count", 0, subcount);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<35>()
+ {
+ // Make sure startsWith works
+ std::string string("anybody in there?");
+ std::string substr("anybody");
+ ensure("startsWith works.", LLStringUtil::startsWith(string, substr));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<36>()
+ {
+ // Make sure startsWith correctly fails
+ std::string string("anybody in there?");
+ std::string substr("there");
+ ensure("startsWith fails.", !LLStringUtil::startsWith(string, substr));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<37>()
+ {
+ // startsWith fails on empty strings
+ std::string value("anybody in there?");
+ std::string empty;
+ ensure("empty string.", !LLStringUtil::startsWith(value, empty));
+ ensure("empty substr.", !LLStringUtil::startsWith(empty, value));
+ ensure("empty everything.", !LLStringUtil::startsWith(empty, empty));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<38>()
+ {
+ // Make sure endsWith works correctly
+ std::string string("anybody in there?");
+ std::string substr("there?");
+ ensure("endsWith works.", LLStringUtil::endsWith(string, substr));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<39>()
+ {
+ // Make sure endsWith correctly fails
+ std::string string("anybody in there?");
+ std::string substr("anybody");
+ ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr));
+ substr = "there";
+ ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr));
+ substr = "ther?";
+ ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<40>()
+ {
+ // endsWith fails on empty strings
+ std::string value("anybody in there?");
+ std::string empty;
+ ensure("empty string.", !LLStringUtil::endsWith(value, empty));
+ ensure("empty substr.", !LLStringUtil::endsWith(empty, value));
+ ensure("empty everything.", !LLStringUtil::endsWith(empty, empty));
+ }
+
+ template<> template<>
+ void string_index_object_t::test<41>()
+ {
+ set_test_name("getTokens(\"delims\")");
+ ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec());
+ ensure_equals("only delims",
+ LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec());
+ ensure_equals("sequence of delims",
+ LLStringUtil::getTokens(",,, one ,,,", ","), list_of("one"));
+ // nat considers this a dubious implementation side effect, but I'd
+ // hate to change it now...
+ ensure_equals("noncontiguous tokens",
+ LLStringUtil::getTokens(", ,, , one ,,,", ","), list_of("")("")("one"));
+ ensure_equals("space-padded tokens",
+ LLStringUtil::getTokens(", one , two ,", ","), list_of("one")("two"));
+ ensure_equals("no delims", LLStringUtil::getTokens("one", ","), list_of("one"));
+ }
+
+ // Shorthand for verifying that getTokens() behaves the same when you
+ // don't pass a string of escape characters, when you pass an empty string
+ // (different overloads), and when you pass a string of characters that
+ // aren't actually present.
+ void ensure_getTokens(const std::string& desc,
+ const std::string& string,
+ const std::string& drop_delims,
+ const std::string& keep_delims,
+ const std::string& quotes,
+ const std::vector<std::string>& expect)
+ {
+ ensure_equals(desc + " - no esc",
+ LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes),
+ expect);
+ ensure_equals(desc + " - empty esc",
+ LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""),
+ expect);
+ ensure_equals(desc + " - unused esc",
+ LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"),
+ expect);
+ }
+
+ void ensure_getTokens(const std::string& desc,
+ const std::string& string,
+ const std::string& drop_delims,
+ const std::string& keep_delims,
+ const std::vector<std::string>& expect)
+ {
+ ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect);
+ }
+
+ template<> template<>
+ void string_index_object_t::test<42>()
+ {
+ set_test_name("getTokens(\"delims\", etc.)");
+ // Signatures to test in this method:
+ // getTokens(string, drop_delims, keep_delims [, quotes [, escapes]])
+ // If you omit keep_delims, you get the older function (test above).
+
+ // cases like the getTokens(string, delims) tests above
+ ensure_getTokens("empty string", "", " ", "", StringVec());
+ ensure_getTokens("only delims",
+ " \r\n ", " \r\n", "", StringVec());
+ ensure_getTokens("sequence of delims",
+ ",,, one ,,,", ", ", "", list_of("one"));
+ // Note contrast with the case in the previous method
+ ensure_getTokens("noncontiguous tokens",
+ ", ,, , one ,,,", ", ", "", list_of("one"));
+ ensure_getTokens("space-padded tokens",
+ ", one , two ,", ", ", "",
+ list_of("one")("two"));
+ ensure_getTokens("no delims", "one", ",", "", list_of("one"));
+
+ // drop_delims vs. keep_delims
+ ensure_getTokens("arithmetic",
+ " ab+def / xx* yy ", " ", "+-*/",
+ list_of("ab")("+")("def")("/")("xx")("*")("yy"));
+
+ // quotes
+ ensure_getTokens("no quotes",
+ "She said, \"Don't go.\"", " ", ",", "",
+ list_of("She")("said")(",")("\"Don't")("go.\""));
+ ensure_getTokens("quotes",
+ "She said, \"Don't go.\"", " ", ",", "\"",
+ list_of("She")("said")(",")("Don't go."));
+ ensure_getTokens("quotes and delims",
+ "run c:/'Documents and Settings'/someone", " ", "", "'",
+ list_of("run")("c:/Documents and Settings/someone"));
+ ensure_getTokens("unmatched quote",
+ "baby don't leave", " ", "", "'",
+ list_of("baby")("don't")("leave"));
+ ensure_getTokens("adjacent quoted",
+ "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'",
+ list_of("abcdef \"ghijkl' mnopqr"));
+ ensure_getTokens("quoted empty string",
+ "--set SomeVar ''", " ", "", "'",
+ list_of("--set")("SomeVar")(""));
+
+ // escapes
+ // Don't use backslash as an escape for these tests -- you'll go nuts
+ // between the C++ string scanner and getTokens() escapes. Test with
+ // something else!
+ ensure_equals("escaped delims",
+ LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"),
+ list_of(" a")("-")("dog-gone phrase"));
+ ensure_equals("escaped quotes",
+ LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"),
+ list_of("say:")("this isn't working."));
+ ensure_equals("escaped escape",
+ LLStringUtil::getTokens("want x^^2", " ", "", "", "^"),
+ list_of("want")("x^2"));
+ ensure_equals("escape at end",
+ LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"),
+ list_of("it's up")("there^"));
+ }
+}
diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index 302bbe6f8d..dbcfb775da 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -3,7 +3,7 @@ * @author Nat Goodspeed * @date 2021-10-21 * @brief Implementation for threadpool. - * + * * $LicenseInfo:firstyear=2021&license=viewerlgpl$ * Copyright (c) 2021, Linden Research, Inc. * $/LicenseInfo$ @@ -110,6 +110,10 @@ void LL::ThreadPoolBase::start() LL::ThreadPoolBase::~ThreadPoolBase() { close(); + if (!LLEventPumps::wasDeleted()) + { + LLEventPumps::instance().obtain("LLApp").stopListening(mName); + } } void LL::ThreadPoolBase::close() |