From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/llcommon/StackWalker.h | 452 +-- indra/llcommon/llapp.cpp | 1492 ++++---- indra/llcommon/llapp.h | 684 ++-- indra/llcommon/llapr.cpp | 1494 ++++---- indra/llcommon/llapr.h | 402 +-- indra/llcommon/llassettype.cpp | 492 +-- indra/llcommon/llbase64.cpp | 154 +- indra/llcommon/llbase64.h | 76 +- indra/llcommon/llcallbacklist.cpp | 460 +-- indra/llcommon/llcommon.cpp | 306 +- indra/llcommon/llcommon.h | 86 +- indra/llcommon/llcrc.cpp | 446 +-- indra/llcommon/llcrc.h | 136 +- indra/llcommon/llerror.cpp | 3324 ++++++++--------- indra/llcommon/lleventemitter.h | 204 +- indra/llcommon/lleventtimer.h | 244 +- indra/llcommon/llframetimer.cpp | 300 +- indra/llcommon/llframetimer.h | 302 +- indra/llcommon/llkeythrottle.h | 662 ++-- indra/llcommon/lllivefile.cpp | 396 +-- indra/llcommon/llmemory.cpp | 708 ++-- indra/llcommon/llmemory.h | 836 ++--- indra/llcommon/llmetricperformancetester.cpp | 666 ++-- indra/llcommon/llmetricperformancetester.h | 430 +-- indra/llcommon/llmortician.cpp | 212 +- indra/llcommon/llmortician.h | 112 +- indra/llcommon/llmutex.cpp | 836 ++--- indra/llcommon/llmutex.h | 542 +-- indra/llcommon/llnametable.h | 206 +- indra/llcommon/llpriqueuemap.h | 290 +- indra/llcommon/llprocess.cpp | 2716 +++++++------- indra/llcommon/llqueuedthread.cpp | 1174 +++--- indra/llcommon/llqueuedthread.h | 356 +- indra/llcommon/llregistry.h | 706 ++-- indra/llcommon/llsdserialize.cpp | 4940 +++++++++++++------------- indra/llcommon/llsdutil.cpp | 2166 +++++------ indra/llcommon/llsdutil.h | 1348 +++---- indra/llcommon/llstacktrace.cpp | 336 +- indra/llcommon/llstl.h | 1426 ++++---- indra/llcommon/llstring.cpp | 3512 +++++++++--------- indra/llcommon/llstring.h | 4078 ++++++++++----------- indra/llcommon/llstringtable.h | 418 +-- indra/llcommon/llsys.cpp | 2828 +++++++-------- indra/llcommon/llsys.h | 348 +- indra/llcommon/llthread.cpp | 944 ++--- indra/llcommon/lltimer.cpp | 1216 +++---- indra/llcommon/lltimer.h | 376 +- indra/llcommon/lluri.cpp | 1516 ++++---- indra/llcommon/lluri.h | 386 +- indra/llcommon/lluuid.cpp | 2154 +++++------ indra/llcommon/lluuid.h | 388 +- indra/llcommon/llworkerthread.cpp | 796 ++--- indra/llcommon/llworkerthread.h | 402 +-- indra/llcommon/tests/llstring_test.cpp | 1742 ++++----- 54 files changed, 26611 insertions(+), 26611 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/StackWalker.h b/indra/llcommon/StackWalker.h index 28b10950e0..c76b07a739 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 - -// 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 + +// 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/llapp.cpp b/indra/llcommon/llapp.cpp index b354a60bb0..1388e81656 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -1,746 +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 - -#ifdef LL_DARWIN -#include -#include -#include -#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 -# include // 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::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 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: /.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 + +#ifdef LL_DARWIN +#include +#include +#include +#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 +# include // 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::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 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: /.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 79b6e03581..ad8912ca88 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 -#include "llcond.h" -#include "llrun.h" -#include "llsd.h" -#include -#include -// Forward declarations -class LLLiveFile; -#if LL_LINUX -#include -#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 addToEventTimer() 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 . - // One can imagine a wonderfully general bidirectional conversion system - // between any type derived from LLUnits::LLUnit and - // any std::chrono::duration -- but that doesn't yet exist. - template - bool sleep(const std::chrono::duration& 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 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 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 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 +#include "llcond.h" +#include "llrun.h" +#include "llsd.h" +#include +#include +// Forward declarations +class LLLiveFile; +#if LL_LINUX +#include +#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 addToEventTimer() 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 . + // One can imagine a wonderfully general bidirectional conversion system + // between any type derived from LLUnits::LLUnit and + // any std::chrono::duration -- but that doesn't yet exist. + template + bool sleep(const std::chrono::duration& 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 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 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 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 0aa68f28cb..b085f8f5dc 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, 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 -//******************************************************************************************************************************* -// +/** + * @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 3a43339ca3..00ff4d60b7 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 // Need PATH_MAX in APR headers... -#endif - -#include -#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 true 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 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 +/** + * @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 // Need PATH_MAX in APR headers... +#endif + +#include +#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 true 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 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 3041c1f354..fe8510468a 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, - public LLDictionary -{ - 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, + public LLDictionary +{ + 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 144b878db3..b8185a0c84 100644 --- a/indra/llcommon/llbase64.cpp +++ b/indra/llcommon/llbase64.cpp @@ -1,77 +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 - -#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(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(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; -} - +/** + * @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 + +#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(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(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 a64c4f1e54..4f21e65244 100644 --- a/indra/llcommon/llbase64.h +++ b/indra/llcommon/llbase64.h @@ -1,38 +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); - static std::string decodeAsString(const std::string& input); -}; - -#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 9705b69e11..3d5d30bd90 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(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(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(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(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 7a10662d3d..d22f26ff62 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 3e81bb1f79..41a101eb62 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 c376269981..d79d06e2a2 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 d6fd008740..a3bde47780 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 80fcaefad2..94aee26df6 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 -#ifdef __GNUC__ -# include -#endif // __GNUC__ -#include -#if !LL_WINDOWS -# include -# include -# include -#else -# include -#endif // !LL_WINDOWS -#include -#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 - -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{ "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 LevelMap; - typedef std::vector Recorders; - typedef std::vector 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 mUniqueLogMessages; - - LLError::FatalFunction mCrashFunction; - LLError::TimeFunction mTimeFunction; - - Recorders mRecorders; - LLMutex mRecorderMutex; - - int mShouldLogCallCounter; - - private: - SettingsConfig(); - }; - - typedef LLPointer 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(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 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 - std::pair, 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. Use a - // dynamic_pointer_cast to try to downcast to test if it's also a - // shared_ptr. - auto ptr = std::dynamic_pointer_cast(*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 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 - std::shared_ptr findRecorder() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - return findRecorderPos(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 - bool removeRecorder() - { - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLMutexLock lock(&s->mRecorderMutex); - auto found = findRecorderPos(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(); - - if (!file_name.empty()) - { - std::shared_ptr recordToFile(new RecordToFile(file_name)); - if (recordToFile->okay()) - { - addRecorder(recordToFile); - } - } - } - - std::string logFileName() - { - auto found = findRecorder(); - return found? found->getFilename() : std::string(); - } - - void logToStderr() - { - if (! findRecorder()) - { - RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime())); - addRecorder(recordToStdErr); - } - } - - void logToFixedBuffer(LLLineBuffer* fixedBuffer) - { - // remove any previous Recorder filling this role - removeRecorder(); - - 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 - 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(), 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(),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::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(), 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(), 5); - if (!lock.isLocked()) - { - return; - } - - if(sBuffer.size() > 511) - { - clear() ; - } - - sBuffer.push_back(out.str()); - } - - //static - void LLCallStacks::print() - { - LLMutexTrylock lock(getMutex(), 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 +#ifdef __GNUC__ +# include +#endif // __GNUC__ +#include +#if !LL_WINDOWS +# include +# include +# include +#else +# include +#endif // !LL_WINDOWS +#include +#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 + +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{ "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 LevelMap; + typedef std::vector Recorders; + typedef std::vector 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 mUniqueLogMessages; + + LLError::FatalFunction mCrashFunction; + LLError::TimeFunction mTimeFunction; + + Recorders mRecorders; + LLMutex mRecorderMutex; + + int mShouldLogCallCounter; + + private: + SettingsConfig(); + }; + + typedef LLPointer 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(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 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 + std::pair, 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. Use a + // dynamic_pointer_cast to try to downcast to test if it's also a + // shared_ptr. + auto ptr = std::dynamic_pointer_cast(*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 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 + std::shared_ptr findRecorder() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + return findRecorderPos(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 + bool removeRecorder() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + auto found = findRecorderPos(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(); + + if (!file_name.empty()) + { + std::shared_ptr recordToFile(new RecordToFile(file_name)); + if (recordToFile->okay()) + { + addRecorder(recordToFile); + } + } + } + + std::string logFileName() + { + auto found = findRecorder(); + return found? found->getFilename() : std::string(); + } + + void logToStderr() + { + if (! findRecorder()) + { + RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime())); + addRecorder(recordToStdErr); + } + } + + void logToFixedBuffer(LLLineBuffer* fixedBuffer) + { + // remove any previous Recorder filling this role + removeRecorder(); + + 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 + 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(), 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(),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::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(), 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(), 5); + if (!lock.isLocked()) + { + return; + } + + if(sBuffer.size() > 511) + { + clear() ; + } + + sBuffer.push_back(out.str()); + } + + //static + void LLCallStacks::print() + { + LLMutexTrylock lock(getMutex(), 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 0079104715..b9de854fda 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 -#include -#include -#include -#include - -#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 +#include +#include +#include +#include + +#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 20c5fe7a82..e0c2381807 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 -{ -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 - 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 - 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 - static LLEventTimer* run_after(F32 interval, const CALLABLE& callable); - -protected: - LLTimer mEventTimer; - F32 mPeriod; - -private: - template - class Generic; -}; - -template -class LLEventTimer::Generic: public LLEventTimer -{ -public: - // making TIME generic allows engaging either LLEventTimer constructor - template - 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 -LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable) -{ - // return false to schedule recurring calls - return new Generic(period, false, callable); -} - -template -LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable) -{ - // return true for one-shot callback - return new Generic(time, true, callable); -} - -template -LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable) -{ - // one-shot callback after specified interval - return new Generic(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 +{ +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 + 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 + 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 + static LLEventTimer* run_after(F32 interval, const CALLABLE& callable); + +protected: + LLTimer mEventTimer; + F32 mPeriod; + +private: + template + class Generic; +}; + +template +class LLEventTimer::Generic: public LLEventTimer +{ +public: + // making TIME generic allows engaging either LLEventTimer constructor + template + 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 +LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable) +{ + // return false to schedule recurring calls + return new Generic(period, false, callable); +} + +template +LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable) +{ + // return true for one-shot callback + return new Generic(time, true, callable); +} + +template +LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable) +{ + // one-shot callback after specified interval + return new Generic(interval, true, callable); +} + +#endif //LL_EVENTTIMER_H diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp index 8694129c77..a0080b57bb 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 b519a637e3..ba4f075b57 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 0909acb747..8ee0e08c69 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 - - -// forward declaration so LLKeyThrottleImpl can befriend it -template class LLKeyThrottle; - - -// Implementation utility class - use LLKeyThrottle, not this -template -class LLKeyThrottleImpl -{ - friend class LLKeyThrottle; -protected: - struct Entry { - U32 count; - bool blocked; - - Entry() : count(0), blocked(false) { } - }; - - typedef std::map 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) - { - 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::getTime(); - } - else - { - now = LLKeyThrottleImpl::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::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::EntryMap; - m.currMap = new typename LLKeyThrottleImpl::EntryMap; - - m.startTime = now; - } - } - - U32 prevCount = 0; - - typename LLKeyThrottleImpl::EntryMap::const_iterator prev = m.prevMap->find(id); - if (prev != m.prevMap->end()) - { - prevCount = prev->second.count; - } - - typename LLKeyThrottleImpl::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::getTime(); - } - else - { - now = LLKeyThrottleImpl::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::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::EntryMap; - m.currMap = new typename LLKeyThrottleImpl::EntryMap; - - m.startTime = now; - } - } - - U32 prevCount = 0; - bool prevBlocked = false; - - typename LLKeyThrottleImpl::EntryMap::const_iterator prev = m.prevMap->find(id); - if (prev != m.prevMap->end()) - { - prevCount = prev->second.count; - prevBlocked = prev->second.blocked; - } - - typename LLKeyThrottleImpl::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::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::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::getTime(); - } - else - { - m.intervalLength = (U64)interval; - m.startTime = LLKeyThrottleImpl::getFrame(); - } - - if ( m.intervalLength == 0 ) - { // Don't allow zero intervals - m.intervalLength = 1; - } - - delete m.prevMap; - m.prevMap = new typename LLKeyThrottleImpl::EntryMap; - delete m.currMap; - m.currMap = new typename LLKeyThrottleImpl::EntryMap; - } - -protected: - LLKeyThrottleImpl& 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 + + +// forward declaration so LLKeyThrottleImpl can befriend it +template class LLKeyThrottle; + + +// Implementation utility class - use LLKeyThrottle, not this +template +class LLKeyThrottleImpl +{ + friend class LLKeyThrottle; +protected: + struct Entry { + U32 count; + bool blocked; + + Entry() : count(0), blocked(false) { } + }; + + typedef std::map 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) + { + 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::getTime(); + } + else + { + now = LLKeyThrottleImpl::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::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::EntryMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + + m.startTime = now; + } + } + + U32 prevCount = 0; + + typename LLKeyThrottleImpl::EntryMap::const_iterator prev = m.prevMap->find(id); + if (prev != m.prevMap->end()) + { + prevCount = prev->second.count; + } + + typename LLKeyThrottleImpl::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::getTime(); + } + else + { + now = LLKeyThrottleImpl::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::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::EntryMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + + m.startTime = now; + } + } + + U32 prevCount = 0; + bool prevBlocked = false; + + typename LLKeyThrottleImpl::EntryMap::const_iterator prev = m.prevMap->find(id); + if (prev != m.prevMap->end()) + { + prevCount = prev->second.count; + prevBlocked = prev->second.blocked; + } + + typename LLKeyThrottleImpl::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::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::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::getTime(); + } + else + { + m.intervalLength = (U64)interval; + m.startTime = LLKeyThrottleImpl::getFrame(); + } + + if ( m.intervalLength == 0 ) + { // Don't allow zero intervals + m.intervalLength = 1; + } + + delete m.prevMap; + m.prevMap = new typename LLKeyThrottleImpl::EntryMap; + delete m.currMap; + m.currMap = new typename LLKeyThrottleImpl::EntryMap; + } + +protected: + LLKeyThrottleImpl& 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 3fe5f4bdb6..58de61a7e4 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/llmemory.cpp b/indra/llcommon/llmemory.cpp index 0e5b91c9b8..4b7d60d654 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 -#elif defined(LL_DARWIN) -# include -# include -# include -#include -#elif LL_LINUX -# include -# include -#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 sAllocatedMem("allocated_mem", "active memory in use by application"); -static LLTrace::SampleStatHandle 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(&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(&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 - -struct mem_info { - std::map 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(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 +#elif defined(LL_DARWIN) +# include +# include +# include +#include +#elif LL_LINUX +# include +# include +#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 sAllocatedMem("allocated_mem", "active memory in use by application"); +static LLTrace::SampleStatHandle 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(&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(&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 + +struct mem_info { + std::map 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(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 ea360881c6..2c3f66fab8 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 -#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 - -template T* LL_NEXT_ALIGNED_ADDRESS(T* address) -{ - return reinterpret_cast( - (uintptr_t(address) + 0xF) & ~0xF); -} - -template T* LL_NEXT_ALIGNED_ADDRESS_64(T* address) -{ - return reinterpret_cast( - (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 -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 -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 +#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 + +template T* LL_NEXT_ALIGNED_ADDRESS(T* address) +{ + return reinterpret_cast( + (uintptr_t(address) + 0xF) & ~0xF); +} + +template T* LL_NEXT_ALIGNED_ADDRESS_64(T* address) +{ + return reinterpret_cast( + (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 +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 +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 addee9fdbf..cc258e4609 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 8989e657ce..78abd53602 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 7ba023f052..578d72388c 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 - -std::list 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::iterator iter = sGraveyard.begin(); - std::list::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 + +std::list 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::iterator iter = sGraveyard.begin(); + std::list::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 4aea4e0292..b2d81fa1c5 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 - -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 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 + +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 sGraveyard; +}; + +#endif + diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index a7c2817e2f..7bdc391459 100644 --- a/indra/llcommon/llmutex.cpp +++ b/indra/llcommon/llmutex.cpp @@ -1,418 +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::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 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 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 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 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 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 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; - } -} - -//============================================================================ +/** + * @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 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 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 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 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 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 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 898ddab284..6e8cf9643b 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -1,271 +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 - -#include "mutex.h" -#include -#include -#include - -//============================================================================ - -//#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 -#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 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 void lock(); - - bool trylockShared(); - bool trylockExclusive(); - template bool trylock(); - - void unlockShared(); - void unlockExclusive(); - template void unlock(); - -private: - std::shared_mutex mSharedMutex; - mutable std::mutex mLockMutex; - std::unordered_map mLockingThreads; - bool mIsShared; - - using iterator = std::unordered_map::iterator; - using const_iterator = std::unordered_map::const_iterator; -}; - -template<> -inline void LLSharedMutex::lock() -{ - lockShared(); -} - -template<> -inline void LLSharedMutex::lock() -{ - lockExclusive(); -} - -template<> -inline bool LLSharedMutex::trylock() -{ - return trylockShared(); -} - -template<> -inline bool LLSharedMutex::trylock() -{ - return trylockExclusive(); -} - -template<> -inline void LLSharedMutex::unlock() -{ - unlockShared(); -} - -template<> -inline void LLSharedMutex::unlock() -{ - 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 -class LLSharedMutexLockTemplate -{ -public: - LLSharedMutexLockTemplate(LLSharedMutex* mutex) - : mSharedMutex(mutex) - { - if (mSharedMutex) - mSharedMutex->lock(); - } - - ~LLSharedMutexLockTemplate() - { - if (mSharedMutex) - mSharedMutex->unlock(); - } - -private: - LLSharedMutex* mSharedMutex; -}; - -using LLSharedMutexLock = LLSharedMutexLockTemplate; -using LLExclusiveMutexLock = LLSharedMutexLockTemplate; - -//============================================================================ - -// 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 not 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 + +#include "mutex.h" +#include +#include +#include + +//============================================================================ + +//#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 +#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 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 void lock(); + + bool trylockShared(); + bool trylockExclusive(); + template bool trylock(); + + void unlockShared(); + void unlockExclusive(); + template void unlock(); + +private: + std::shared_mutex mSharedMutex; + mutable std::mutex mLockMutex; + std::unordered_map mLockingThreads; + bool mIsShared; + + using iterator = std::unordered_map::iterator; + using const_iterator = std::unordered_map::const_iterator; +}; + +template<> +inline void LLSharedMutex::lock() +{ + lockShared(); +} + +template<> +inline void LLSharedMutex::lock() +{ + lockExclusive(); +} + +template<> +inline bool LLSharedMutex::trylock() +{ + return trylockShared(); +} + +template<> +inline bool LLSharedMutex::trylock() +{ + return trylockExclusive(); +} + +template<> +inline void LLSharedMutex::unlock() +{ + unlockShared(); +} + +template<> +inline void LLSharedMutex::unlock() +{ + 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 +class LLSharedMutexLockTemplate +{ +public: + LLSharedMutexLockTemplate(LLSharedMutex* mutex) + : mSharedMutex(mutex) + { + if (mSharedMutex) + mSharedMutex->lock(); + } + + ~LLSharedMutexLockTemplate() + { + if (mSharedMutex) + mSharedMutex->unlock(); + } + +private: + LLSharedMutex* mSharedMutex; +}; + +using LLSharedMutexLock = LLSharedMutexLockTemplate; +using LLExclusiveMutexLock = LLSharedMutexLockTemplate; + +//============================================================================ + +// 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 not 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 bcc6267ec5..0c4cc4c04d 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 - -#include "string_table.h" - -template -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 name_map_t; - typedef typename std::map::iterator iter_t; - typedef typename std::map::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 + +#include "string_table.h" + +template +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 name_map_t; + typedef typename std::map::iterator iter_t; + typedef typename std::map::const_iterator const_iter_t; + name_map_t mNameMap; +}; + +#endif diff --git a/indra/llcommon/llpriqueuemap.h b/indra/llcommon/llpriqueuemap.h index a0a0855109..2bdd39aac2 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 - -// -// 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 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 LLPriQueueMap -{ -public: - typedef typename std::map, DATA_TYPE>::iterator pqm_iter; - typedef std::pair, 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(priority, data)); - if (iter != mMap.end()) - { - LL_ERRS() << "Pushing already existing data onto queue!" << LL_ENDL; - } -#endif - mMap.insert(pqm_pair(LLPQMKey(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 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, 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 + +// +// 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 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 LLPriQueueMap +{ +public: + typedef typename std::map, DATA_TYPE>::iterator pqm_iter; + typedef std::pair, 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(priority, data)); + if (iter != mMap.end()) + { + LL_ERRS() << "Pushing already existing data onto queue!" << LL_ENDL; + } +#endif + mMap.insert(pqm_pair(LLPQMKey(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 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, 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 80413bb841..912e596c3f 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/***************************************************************************** -* 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::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(*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 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 - 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 - 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(*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 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 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(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(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 -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(&mPipes[slot]); - if (! ppipe) - { - error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name()); - return NULL; - } - - error.clear(); - return ppipe; -} - -template -PIPETYPE& LLProcess::getPipe(FILESLOT slot) -{ - std::string error; - PIPETYPE* wp = getPipePtr(error, slot); - if (! wp) - { - LLTHROW(NoPipe(error)); - } - return *wp; -} - -template -boost::optional LLProcess::getOptPipe(FILESLOT slot) -{ - std::string error; - PIPETYPE* wp = getPipePtr(error, slot); - if (! wp) - { - LL_DEBUGS("LLProcess") << error << LL_ENDL; - return boost::optional(); - } - return *wp; -} - -LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot) -{ - return getPipe(slot); -} - -boost::optional LLProcess::getOptWritePipe(FILESLOT slot) -{ - return getOptPipe(slot); -} - -LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot) -{ - return getPipe(slot); -} - -boost::optional LLProcess::getOptReadPipe(FILESLOT slot) -{ - return getOptPipe(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(result)); -} - -/***************************************************************************** -* Posix specific -*****************************************************************************/ -#else // Mac and linux - -#include -#include -#include -#include - -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/***************************************************************************** +* 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::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(*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 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 + 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 + 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(*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 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 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(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(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 +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(&mPipes[slot]); + if (! ppipe) + { + error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name()); + return NULL; + } + + error.clear(); + return ppipe; +} + +template +PIPETYPE& LLProcess::getPipe(FILESLOT slot) +{ + std::string error; + PIPETYPE* wp = getPipePtr(error, slot); + if (! wp) + { + LLTHROW(NoPipe(error)); + } + return *wp; +} + +template +boost::optional LLProcess::getOptPipe(FILESLOT slot) +{ + std::string error; + PIPETYPE* wp = getPipePtr(error, slot); + if (! wp) + { + LL_DEBUGS("LLProcess") << error << LL_ENDL; + return boost::optional(); + } + return *wp; +} + +LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot) +{ + return getPipe(slot); +} + +boost::optional LLProcess::getOptWritePipe(FILESLOT slot) +{ + return getOptPipe(slot); +} + +LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot) +{ + return getPipe(slot); +} + +boost::optional LLProcess::getOptReadPipe(FILESLOT slot) +{ + return getOptPipe(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(result)); +} + +/***************************************************************************** +* Posix specific +*****************************************************************************/ +#else // Mac and linux + +#include +#include +#include +#include + +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 4a909f601a..39e8113587 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 - -#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* 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(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(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 + +#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* 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(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(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 0f69f7640f..02d3a96fcc 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 -#include -#include -#include - -#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 - { - 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 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 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 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 +#include +#include +#include + +#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 + { + 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 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 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 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 977c8d1935..35335e1213 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 - -#include "llsingleton.h" -#include "llstl.h" - -template -struct LLRegistryDefaultComparator -{ - bool operator()(const T& lhs, const T& rhs) const - { - using std::less; - return less()(lhs, rhs); - } -}; - -template > -class LLRegistry -{ -public: - typedef LLRegistry 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; - public: - typedef std::map 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 scope_list_t; - typedef typename std::list::iterator scope_list_iterator_t; - typedef typename std::list::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 > -class LLRegistrySingleton - : public LLRegistry, - public LLSingleton -{ - // 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 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 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::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::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 + +#include "llsingleton.h" +#include "llstl.h" + +template +struct LLRegistryDefaultComparator +{ + bool operator()(const T& lhs, const T& rhs) const + { + using std::less; + return less()(lhs, rhs); + } +}; + +template > +class LLRegistry +{ +public: + typedef LLRegistry 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; + public: + typedef std::map 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 scope_list_t; + typedef typename std::list::iterator scope_list_iterator_t; + typedef typename std::list::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 > +class LLRegistrySingleton + : public LLRegistry, + public LLSingleton +{ + // 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 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 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::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::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/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 612f71e8bc..92d9392477 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 -#include "apr_base64.h" - -#include -#include - -#ifdef LL_USESYSTEMLIBS -# include -#else -# include "zlib-ng/zlib.h" // for davep's dirty little zip functions -#endif - -#if !LL_WINDOWS -#include // 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[] = ""; -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 -void format_using(const LLSD& data, std::ostream& ostr, - LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY) -{ - LLPointer f{ new Formatter }; - f->format(data, ostr, options); -} - -template -S32 parse_using(std::istream& istr, LLSD& data, size_t max_bytes, S32 max_depth=-1) -{ - LLPointer 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 f = NULL; - - switch (type) - { - case LLSD_BINARY: - str << "\n"; - f = new LLSDBinaryFormatter; - break; - - case LLSD_XML: - str << "\n"; - f = new LLSDXMLFormatter; - break; - - case LLSD_NOTATION: - str << "\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(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("(str, sd, max_bytes-inbuf) > 0); - } - else if (0 == LLStringUtil::compareInsensitive(header, LLSD_XML_HEADER)) - { - return (parse_using(str, sd, max_bytes-inbuf) > 0); - } - else if (0 == LLStringUtil::compareInsensitive(header, LLSD_NOTATION_HEADER)) - { - return (parse_using(str, sd, max_bytes-inbuf) > 0); - } - else // no header we recognize - { - LLPointer 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(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 wholemsg((max_bytes == size_t(SIZE_UNLIMITED))? 1024 : max_bytes); - prepend.read(wholemsg.data(), std::min(max_bytes, wholemsg.size())); - LLMemoryStream replay(reinterpret_cast(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 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 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 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: '!'
- * Boolean: '1' for true '0' for false
- * Integer: 'i' + 4 bytes network byte order
- * Real: 'r' + 8 bytes IEEE double
- * UUID: 'u' + 16 byte unsigned integer
- * String: 's' + 4 byte integer size + string
- * strings also secretly support the notation format - * Date: 'd' + 8 byte IEEE double for seconds since epoch
- * URI: 'l' + 4 byte integer size + string uri
- * Binary: 'b' + 4 byte integer size + binary data
- * Array: '[' + 4 byte integer size + all values + ']'
- * Map: '{' + 4 byte integer size every(key + value) + '}'
- * 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 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 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& 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& 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 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(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 in = std::unique_ptr(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 out; - if (!out) - { - out = std::unique_ptr(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(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 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 = ""; - 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 +#include "apr_base64.h" + +#include +#include + +#ifdef LL_USESYSTEMLIBS +# include +#else +# include "zlib-ng/zlib.h" // for davep's dirty little zip functions +#endif + +#if !LL_WINDOWS +#include // 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[] = ""; +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 +void format_using(const LLSD& data, std::ostream& ostr, + LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY) +{ + LLPointer f{ new Formatter }; + f->format(data, ostr, options); +} + +template +S32 parse_using(std::istream& istr, LLSD& data, size_t max_bytes, S32 max_depth=-1) +{ + LLPointer 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 f = NULL; + + switch (type) + { + case LLSD_BINARY: + str << "\n"; + f = new LLSDBinaryFormatter; + break; + + case LLSD_XML: + str << "\n"; + f = new LLSDXMLFormatter; + break; + + case LLSD_NOTATION: + str << "\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(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("(str, sd, max_bytes-inbuf) > 0); + } + else if (0 == LLStringUtil::compareInsensitive(header, LLSD_XML_HEADER)) + { + return (parse_using(str, sd, max_bytes-inbuf) > 0); + } + else if (0 == LLStringUtil::compareInsensitive(header, LLSD_NOTATION_HEADER)) + { + return (parse_using(str, sd, max_bytes-inbuf) > 0); + } + else // no header we recognize + { + LLPointer 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(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 wholemsg((max_bytes == size_t(SIZE_UNLIMITED))? 1024 : max_bytes); + prepend.read(wholemsg.data(), std::min(max_bytes, wholemsg.size())); + LLMemoryStream replay(reinterpret_cast(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 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 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 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: '!'
+ * Boolean: '1' for true '0' for false
+ * Integer: 'i' + 4 bytes network byte order
+ * Real: 'r' + 8 bytes IEEE double
+ * UUID: 'u' + 16 byte unsigned integer
+ * String: 's' + 4 byte integer size + string
+ * strings also secretly support the notation format + * Date: 'd' + 8 byte IEEE double for seconds since epoch
+ * URI: 'l' + 4 byte integer size + string uri
+ * Binary: 'b' + 4 byte integer size + binary data
+ * Array: '[' + 4 byte integer size + all values + ']'
+ * Map: '{' + 4 byte integer size every(key + value) + '}'
+ * 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 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 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& 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& 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 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(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 in = std::unique_ptr(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 out; + if (!out) + { + out = std::unique_ptr(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(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 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 = ""; + 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 4dd47ad1ac..dd3a58c26d 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 - -#if LL_WINDOWS -# define WIN32_LEAN_AND_MEAN -# include // for htonl -#elif LL_LINUX -# include -#elif LL_DARWIN -# include -#endif - -#include "llsdserialize.h" -#include "stringize.h" -#include "is_approx_equal_fraction.h" - -#include -#include -#include - -// U32 -LLSD ll_sd_from_U32(const U32 val) -{ - std::vector 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 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 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 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 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 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 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 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(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(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(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 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(""); - } - -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 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 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 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(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 + +#if LL_WINDOWS +# define WIN32_LEAN_AND_MEAN +# include // for htonl +#elif LL_LINUX +# include +#elif LL_DARWIN +# include +#endif + +#include "llsdserialize.h" +#include "stringize.h" +#include "is_approx_equal_fraction.h" + +#include +#include +#include + +// U32 +LLSD ll_sd_from_U32(const U32 val) +{ + std::vector 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 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 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 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 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 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 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 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(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(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(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 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(""); + } + +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 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 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 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(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 aa234e2f62..38bbe19ddd 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 -#include -#include // std::shared_ptr -#include -#include - -// 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 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 -void array_(LLSD& data, T0&& v0, Ts&&... vs) -{ - data.append(std::forward(v0)); - array_(data, std::forward(vs)...); -} - -// public interface -template -LLSD array(Ts&&... vs) -{ - LLSD data; - array_(data, std::forward(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 - * LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14) 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 -void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs) -{ - data[k0] = v0; - map_(data, std::forward(vs)...); -} - -// public interface -template -LLSD map(Ts&&... vs) -{ - LLSD data; - map_(data, std::forward(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(someLLSD), ...); - * @endcode - */ -template -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 is for when you don't already have the target parameter - * type in hand. Instantiate LLSDParam(your LLSD object), and the - * templated conversion operator will try to select a more specific LLSDParam - * specialization. - */ -template <> -class LLSDParam: public LLSDParamBase -{ -private: - LLSD value_; - // LLSDParam::operator T() works by instantiating an LLSDParam on - // demand. Returning that engages LLSDParam::operator T(), producing - // the desired result. But LLSDParam owns a std::string whose - // c_str() is returned by its operator const char*(). If we return a temp - // LLSDParam, 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 is presumably guaranteed to survive until the - // subject function has returned, so we must ensure that any constructed - // LLSDParam lives just as long as this LLSDParam does. Putting - // each LLSDParam 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 - // will be asked for at most ONE conversion. We could store a scalar - // std::unique_ptr and, when constructing an new LLSDParam, 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> 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 to convert; that - /// preserves the existing customization mechanism - template - operator T() const - { - // capture 'ptr' with the specific subclass type because converters_ - // only stores LLSDParamBase pointers - auto ptr{ std::make_shared>>(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: 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 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(yourLLSD). - */ -template <> -class LLSDParam: 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::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 -{ - 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 -auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) -{ - // call func(unpacked args), using generic LLSDParam to convert each - // entry in 'array' to the target parameter type - return std::forward(func)(LLSDParam(array[I])...); -} - -// use apply_n(function, LLSD) to call a specific arity of a variadic -// function with (that many) items from the passed LLSD array -template -auto apply_n(CALLABLE&& func, const LLSD& args) -{ - return apply_impl(std::forward(func), - apply_llsd_fix(ARITY, args), - std::make_index_sequence()); -} - -/** - * 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 -auto apply(CALLABLE&& func, const LLSD& args) -{ - // infer arity from the definition of func - constexpr auto arity = function_arity< - typename std::remove_reference::type>::value; - // now that we have a compile-time arity, apply_n() works - return apply_n(std::forward(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 +#include +#include // std::shared_ptr +#include +#include + +// 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 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 +void array_(LLSD& data, T0&& v0, Ts&&... vs) +{ + data.append(std::forward(v0)); + array_(data, std::forward(vs)...); +} + +// public interface +template +LLSD array(Ts&&... vs) +{ + LLSD data; + array_(data, std::forward(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 + * LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14) 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 +void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs) +{ + data[k0] = v0; + map_(data, std::forward(vs)...); +} + +// public interface +template +LLSD map(Ts&&... vs) +{ + LLSD data; + map_(data, std::forward(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(someLLSD), ...); + * @endcode + */ +template +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 is for when you don't already have the target parameter + * type in hand. Instantiate LLSDParam(your LLSD object), and the + * templated conversion operator will try to select a more specific LLSDParam + * specialization. + */ +template <> +class LLSDParam: public LLSDParamBase +{ +private: + LLSD value_; + // LLSDParam::operator T() works by instantiating an LLSDParam on + // demand. Returning that engages LLSDParam::operator T(), producing + // the desired result. But LLSDParam owns a std::string whose + // c_str() is returned by its operator const char*(). If we return a temp + // LLSDParam, 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 is presumably guaranteed to survive until the + // subject function has returned, so we must ensure that any constructed + // LLSDParam lives just as long as this LLSDParam does. Putting + // each LLSDParam 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 + // will be asked for at most ONE conversion. We could store a scalar + // std::unique_ptr and, when constructing an new LLSDParam, 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> 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 to convert; that + /// preserves the existing customization mechanism + template + operator T() const + { + // capture 'ptr' with the specific subclass type because converters_ + // only stores LLSDParamBase pointers + auto ptr{ std::make_shared>>(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: 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 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(yourLLSD). + */ +template <> +class LLSDParam: 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::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 +{ + 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 +auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) +{ + // call func(unpacked args), using generic LLSDParam to convert each + // entry in 'array' to the target parameter type + return std::forward(func)(LLSDParam(array[I])...); +} + +// use apply_n(function, LLSD) to call a specific arity of a variadic +// function with (that many) items from the passed LLSD array +template +auto apply_n(CALLABLE&& func, const LLSD& args) +{ + return apply_impl(std::forward(func), + apply_llsd_fix(ARITY, args), + std::make_index_sequence()); +} + +/** + * 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 +auto apply(CALLABLE&& func, const LLSD& args) +{ + // infer arity from the definition of func + constexpr auto arity = function_arity< + typename std::remove_reference::type>::value; + // now that we have a compile-time arity, apply_n() works + return apply_n(std::forward(func), args); +} + +} // namespace LL + +#endif // LL_LLSDUTIL_H diff --git a/indra/llcommon/llstacktrace.cpp b/indra/llcommon/llstacktrace.cpp index 05e71b8203..bda3579f60 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 -#include - -#include "llwin32headerslean.h" -#pragma warning (push) -#pragma warning (disable:4091) // a microsoft header has warnings. Very nice. -#include -#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& 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& 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& lines) -{ - return false; -} - -void ll_get_stack_trace_internal(std::vector& 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 +#include + +#include "llwin32headerslean.h" +#pragma warning (push) +#pragma warning (disable:4091) // a microsoft header has warnings. Very nice. +#include +#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& 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& 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& lines) +{ + return false; +} + +void ll_get_stack_trace_internal(std::vector& lines) +{ + +} + +#endif + diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index f2d268bb9a..67b4c141af 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 -#include -#include -#include -#include -#include -#include - -#ifdef LL_LINUX -// For strcmp -#include -#endif -// Use to compare the first element only of a pair -// e.g. typedef std::set, compare_pair > some_pair_set_t; -template -struct compare_pair_first -{ - bool operator()(const std::pair& a, const std::pair& b) const - { - return a.first < b.first; - } -}; - -template -struct compare_pair_greater -{ - bool operator()(const std::pair& a, const std::pair& 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 -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 void operator()(T* ptr) const - { - delete ptr; - } -}; -struct DeletePointerArray -{ - template 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 void operator()(T &ptr) const - { - delete ptr.second; - ptr.second = NULL; - } -}; -struct DeletePairedPointerArray -{ - template 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 map_type; -// map_type widget_map; -// ... // add elements -// // delete them all -// for_each(widget_map.begin(), -// widget_map.end(), -// llcompose1(DeletePointerFunctor(), -// llselect2nd())); - -template -struct DeletePointerFunctor -{ - bool operator()(T* ptr) const - { - delete ptr; - return true; - } -}; - -// See notes about DeleteArray for why you should consider avoiding this. -template -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 T* operator()(const T* ptr) const - { - return new T(*ptr); - } -}; - -template -void delete_and_clear(std::list& list) -{ - std::for_each(list.begin(), list.end(), DeletePointer()); - list.clear(); -} - -template -void delete_and_clear(std::vector& vector) -{ - std::for_each(vector.begin(), vector.end(), DeletePointer()); - vector.clear(); -} - -template -void delete_and_clear(std::set& set) -{ - std::for_each(set.begin(), set.end(), DeletePointer()); - set.clear(); -} - -template -void delete_and_clear(std::map& map) -{ - std::for_each(map.begin(), map.end(), DeletePairedPointer()); - map.clear(); -} - -template -void delete_and_clear(T*& ptr) -{ - delete ptr; - ptr = NULL; -} - - -template -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 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 -inline T* get_ptr_in_map(const std::map& inmap, const K& key) -{ - // Typedef here avoids warnings because of new c++ naming rules. - typedef typename std::map::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 -inline bool is_in_map(const std::map& 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 -inline T get_if_there(const std::map& inmap, const K& key, T default_value) -{ - // Typedef here avoids warnings because of new c++ naming rules. - typedef typename std::map::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::iterator iter = mList.begin(); iter != mList.end(); ) -// { -// if ((*iter)->isMarkedForRemoval()) -// iter = vector_replace_with_last(mList, iter); -// else -// ++iter; -// } -template -inline typename std::vector::iterator vector_replace_with_last(std::vector& invec, typename std::vector::iterator iter) -{ - typename std::vector::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 -inline bool vector_replace_with_last(std::vector& invec, const T& val) -{ - typename std::vector::iterator iter = std::find(invec.begin(), invec.end(), val); - if (iter != invec.end()) - { - typename std::vector::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 -inline T* vector_append(std::vector& 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 -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 -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 -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 -struct _LLSelect1st -{ - const auto& operator()(const _Pair& __x) const { - return __x.first; - } -}; - -template -struct _LLSelect2nd -{ - const auto& operator()(const _Pair& __x) const { - return __x.second; - } -}; - -template struct llselect1st : public _LLSelect1st<_Pair> {}; -template 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 ll_unary_compose -{ -protected: - _Operation1 __op1; - _Operation2 __op2; -public: - ll_unary_compose(const _Operation1& __x, const _Operation2& __y) - : __op1(__x), __op2(__y) {} - template - auto - operator()(const _Op2Arg& __x) const { - return __op1(__op2(__x)); - } -}; - -template -inline ll_unary_compose<_Operation1,_Operation2> -llcompose1(const _Operation1& __op1, const _Operation2& __op2) -{ - return ll_unary_compose<_Operation1,_Operation2>(__op1, __op2); -} - -template -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 - auto - operator()(const OP2ARG& __x) const { - return _M_op1(_M_op2(__x), _M_op3(__x)); - } -}; - -template -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 llbinder1st -{ -protected: - _Operation op; - _Arg1 value; -public: - llbinder1st(const _Operation& __x, const _Arg1& __y) - : op(__x), value(__y) {} - template - auto - operator()(const _Arg2& __x) const { - return op(value, __x); - } -}; - -template -inline auto -llbind1st(const _Operation& __oper, const _Tp& __x) -{ - return llbinder1st<_Operation, _Tp>(__oper, __x); -} - -template -class llbinder2nd -{ -protected: - _Operation op; - _Arg2 value; -public: - llbinder2nd(const _Operation& __x, - const _Arg2& __y) - : op(__x), value(__y) {} - template - auto - operator()(const _Arg1& __x) const { - return op(__x, value); - } -}; - -template -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 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 - { - bool operator()(const std::type_info* lhs, const std::type_info* rhs) const - { - return before(lhs, rhs); - } - }; - - template <> - struct less - { - 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 -struct ll_template_cast_impl -{ - T operator()(U) - { - return 0; - } -}; - -/** - * ll_template_cast(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 - * void somefunc(const REALCLASS& instance) - * { - * const SpecialClass* ptr = ll_template_cast(&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(&other); - * @endcode - * to say nothing of this: - * @code - * void function(int); - * SpecialClass* ptr = dynamic_cast(&function); - * @endcode - * ll_template_cast handles these kinds of cases by returning 0. - */ -template -T ll_template_cast(U value) -{ - return ll_template_cast_impl()(value); -} - -/** - * Implementation for ll_template_cast() (q.v.). - * - * Implementation for identical types: return same value. - */ -template -struct ll_template_cast_impl -{ - T operator()(T value) - { - return value; - } -}; - -/** - * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of - * type @c source, ll_template_cast(s) will return @c s -- - * presuming that @c source can be converted to @c dest by the normal rules of - * C++. - * - * By default, ll_template_cast(s) 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(&myFoo); - * @endcode - * - * Here @c fooptr will be 0 because &myFoo is of type Foo* - * -- @em not const Foo*. (Declaring const Foo myFoo; 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(&object); - * @endcode - * - * Here @c ptr will be 0 because &object is of type - * Subclass* rather than Base*. 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(&object); - * @endcode - * - * However, as noted earlier, this is easily fooled: - * @code - * const Base* ptr = ll_template_cast(&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 LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*) - * because that's not permitted by normal C++ assignment anyway.) - */ -#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \ -template <> \ -struct ll_template_cast_impl \ -{ \ - 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 +#include +#include +#include +#include +#include +#include + +#ifdef LL_LINUX +// For strcmp +#include +#endif +// Use to compare the first element only of a pair +// e.g. typedef std::set, compare_pair > some_pair_set_t; +template +struct compare_pair_first +{ + bool operator()(const std::pair& a, const std::pair& b) const + { + return a.first < b.first; + } +}; + +template +struct compare_pair_greater +{ + bool operator()(const std::pair& a, const std::pair& 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 +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 void operator()(T* ptr) const + { + delete ptr; + } +}; +struct DeletePointerArray +{ + template 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 void operator()(T &ptr) const + { + delete ptr.second; + ptr.second = NULL; + } +}; +struct DeletePairedPointerArray +{ + template 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 map_type; +// map_type widget_map; +// ... // add elements +// // delete them all +// for_each(widget_map.begin(), +// widget_map.end(), +// llcompose1(DeletePointerFunctor(), +// llselect2nd())); + +template +struct DeletePointerFunctor +{ + bool operator()(T* ptr) const + { + delete ptr; + return true; + } +}; + +// See notes about DeleteArray for why you should consider avoiding this. +template +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 T* operator()(const T* ptr) const + { + return new T(*ptr); + } +}; + +template +void delete_and_clear(std::list& list) +{ + std::for_each(list.begin(), list.end(), DeletePointer()); + list.clear(); +} + +template +void delete_and_clear(std::vector& vector) +{ + std::for_each(vector.begin(), vector.end(), DeletePointer()); + vector.clear(); +} + +template +void delete_and_clear(std::set& set) +{ + std::for_each(set.begin(), set.end(), DeletePointer()); + set.clear(); +} + +template +void delete_and_clear(std::map& map) +{ + std::for_each(map.begin(), map.end(), DeletePairedPointer()); + map.clear(); +} + +template +void delete_and_clear(T*& ptr) +{ + delete ptr; + ptr = NULL; +} + + +template +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 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 +inline T* get_ptr_in_map(const std::map& inmap, const K& key) +{ + // Typedef here avoids warnings because of new c++ naming rules. + typedef typename std::map::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 +inline bool is_in_map(const std::map& 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 +inline T get_if_there(const std::map& inmap, const K& key, T default_value) +{ + // Typedef here avoids warnings because of new c++ naming rules. + typedef typename std::map::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::iterator iter = mList.begin(); iter != mList.end(); ) +// { +// if ((*iter)->isMarkedForRemoval()) +// iter = vector_replace_with_last(mList, iter); +// else +// ++iter; +// } +template +inline typename std::vector::iterator vector_replace_with_last(std::vector& invec, typename std::vector::iterator iter) +{ + typename std::vector::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 +inline bool vector_replace_with_last(std::vector& invec, const T& val) +{ + typename std::vector::iterator iter = std::find(invec.begin(), invec.end(), val); + if (iter != invec.end()) + { + typename std::vector::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 +inline T* vector_append(std::vector& 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 +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 +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 +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 +struct _LLSelect1st +{ + const auto& operator()(const _Pair& __x) const { + return __x.first; + } +}; + +template +struct _LLSelect2nd +{ + const auto& operator()(const _Pair& __x) const { + return __x.second; + } +}; + +template struct llselect1st : public _LLSelect1st<_Pair> {}; +template 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 ll_unary_compose +{ +protected: + _Operation1 __op1; + _Operation2 __op2; +public: + ll_unary_compose(const _Operation1& __x, const _Operation2& __y) + : __op1(__x), __op2(__y) {} + template + auto + operator()(const _Op2Arg& __x) const { + return __op1(__op2(__x)); + } +}; + +template +inline ll_unary_compose<_Operation1,_Operation2> +llcompose1(const _Operation1& __op1, const _Operation2& __op2) +{ + return ll_unary_compose<_Operation1,_Operation2>(__op1, __op2); +} + +template +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 + auto + operator()(const OP2ARG& __x) const { + return _M_op1(_M_op2(__x), _M_op3(__x)); + } +}; + +template +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 llbinder1st +{ +protected: + _Operation op; + _Arg1 value; +public: + llbinder1st(const _Operation& __x, const _Arg1& __y) + : op(__x), value(__y) {} + template + auto + operator()(const _Arg2& __x) const { + return op(value, __x); + } +}; + +template +inline auto +llbind1st(const _Operation& __oper, const _Tp& __x) +{ + return llbinder1st<_Operation, _Tp>(__oper, __x); +} + +template +class llbinder2nd +{ +protected: + _Operation op; + _Arg2 value; +public: + llbinder2nd(const _Operation& __x, + const _Arg2& __y) + : op(__x), value(__y) {} + template + auto + operator()(const _Arg1& __x) const { + return op(__x, value); + } +}; + +template +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 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 + { + bool operator()(const std::type_info* lhs, const std::type_info* rhs) const + { + return before(lhs, rhs); + } + }; + + template <> + struct less + { + 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 +struct ll_template_cast_impl +{ + T operator()(U) + { + return 0; + } +}; + +/** + * ll_template_cast(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 + * void somefunc(const REALCLASS& instance) + * { + * const SpecialClass* ptr = ll_template_cast(&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(&other); + * @endcode + * to say nothing of this: + * @code + * void function(int); + * SpecialClass* ptr = dynamic_cast(&function); + * @endcode + * ll_template_cast handles these kinds of cases by returning 0. + */ +template +T ll_template_cast(U value) +{ + return ll_template_cast_impl()(value); +} + +/** + * Implementation for ll_template_cast() (q.v.). + * + * Implementation for identical types: return same value. + */ +template +struct ll_template_cast_impl +{ + T operator()(T value) + { + return value; + } +}; + +/** + * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of + * type @c source, ll_template_cast(s) will return @c s -- + * presuming that @c source can be converted to @c dest by the normal rules of + * C++. + * + * By default, ll_template_cast(s) 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(&myFoo); + * @endcode + * + * Here @c fooptr will be 0 because &myFoo is of type Foo* + * -- @em not const Foo*. (Declaring const Foo myFoo; 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(&object); + * @endcode + * + * Here @c ptr will be 0 because &object is of type + * Subclass* rather than Base*. 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(&object); + * @endcode + * + * However, as noted earlier, this is easily fooled: + * @code + * const Base* ptr = ll_template_cast(&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 LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*) + * because that's not permitted by normal C++ assignment anyway.) + */ +#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \ +template <> \ +struct ll_template_cast_impl \ +{ \ + DEST operator()(SOURCE wrapper) \ + { \ + return wrapper; \ + } \ +} + + +#endif // LL_LLSTL_H diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index b3fc9b484a..bbb6aa2c20 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 - -#if LL_WINDOWS -#include "llwin32headerslean.h" -#include // 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 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 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(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(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(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 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 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 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(&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(last_error) << LL_ENDL; - } - // return empty std::optional - return {}; -} - -#else // ! LL_WINDOWS - -std::optional llstring_getoptenv(const std::string& key) -{ - auto found = getenv(key.c_str()); - if (found) - { - // return populated std::optional - return std::make_optional(found); - } - else - { - // return empty std::optional - return {}; - } -} - -#endif // ! LL_WINDOWS - -long LLStringOps::sPacificTimeOffset = 0; -long LLStringOps::sLocalTimeOffset = 0; -bool LLStringOps::sPacificDaylightTime = 0; -std::map LLStringOps::datetimeToCodes; - -std::vector LLStringOps::sWeekDayList; -std::vector LLStringOps::sWeekDayShortList; -std::vector LLStringOps::sMonthList; -std::vector 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& 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::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& string, char replacement) - { - const char MIN = 0x20; - std::basic_string::size_type len = string.size(); - for(std::basic_string::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& str, - char replacement) - { - const char MIN = 0x20; - const char PIPE = 0x7c; - std::basic_string::size_type len = str.size(); - for(std::basic_string::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& string, char replacement) - { - const unsigned char MIN = 0x20; - std::basic_string::size_type len = string.size(); - for(std::basic_string::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& 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& 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 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 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 -void LLStringUtilBase::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 + +#if LL_WINDOWS +#include "llwin32headerslean.h" +#include // 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 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 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(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(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(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 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 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 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(&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(last_error) << LL_ENDL; + } + // return empty std::optional + return {}; +} + +#else // ! LL_WINDOWS + +std::optional llstring_getoptenv(const std::string& key) +{ + auto found = getenv(key.c_str()); + if (found) + { + // return populated std::optional + return std::make_optional(found); + } + else + { + // return empty std::optional + return {}; + } +} + +#endif // ! LL_WINDOWS + +long LLStringOps::sPacificTimeOffset = 0; +long LLStringOps::sLocalTimeOffset = 0; +bool LLStringOps::sPacificDaylightTime = 0; +std::map LLStringOps::datetimeToCodes; + +std::vector LLStringOps::sWeekDayList; +std::vector LLStringOps::sWeekDayShortList; +std::vector LLStringOps::sMonthList; +std::vector 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& 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::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& string, char replacement) + { + const char MIN = 0x20; + std::basic_string::size_type len = string.size(); + for(std::basic_string::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& str, + char replacement) + { + const char MIN = 0x20; + const char PIPE = 0x7c; + std::basic_string::size_type len = str.size(); + for(std::basic_string::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& string, char replacement) + { + const unsigned char MIN = 0x20; + std::basic_string::size_type len = string.size(); + for(std::basic_string::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& 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& 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 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 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 +void LLStringUtilBase::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 ac05dc3cd0..61d698687a 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -1,2039 +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 -#include -#include -#include -#include -#include // std::wcslen() -//#include -#include -#include -#include -#include -#include "llformat.h" - -#if LL_LINUX -#include -#include -#endif - -#include -#include - -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 - -namespace std -{ -template<> -struct char_traits -{ - 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(memchr(__s, __a, __n * sizeof(char_type))); } - - static char_type* - move(char_type* __s1, const char_type* __s2, size_t __n) - { return static_cast(memmove(__s1, __s2, __n * sizeof(char_type))); } - - static char_type* - copy(char_type* __s1, const char_type* __s2, size_t __n) - { return static_cast(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(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(__c); } - - static int_type - to_int_type(const char_type& __c) - { return static_cast(__c); } - - static bool - eq_int_type(const int_type& __c1, const int_type& __c2) - { return __c1 == __c2; } - - static int_type - eof() { return static_cast(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 datetimeToCodes; - -public: - static std::vector sWeekDayList; - static std::vector sWeekDayShortList; - static std::vector sMonthList; - static std::vector 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 LLStringUtilBase -{ -private: - static std::string sLocale; - -public: - typedef std::basic_string 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 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& tokens, - const string_type& delims); - /// like simple scan overload, but returns scanned vector - static std::vector 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& 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 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& 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 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 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& tokens); -}; - -template const std::basic_string LLStringUtilBase::null; -template std::string LLStringUtilBase::sLocale; - -typedef LLStringUtilBase LLStringUtil; -typedef LLStringUtilBase LLWStringUtil; -typedef std::basic_string 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 -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(from_value) API. -template -TO ll_convert(const FROM& in) -{ - return ll_convert_impl()(in); -} - -// degenerate case -template -struct ll_convert_impl -{ - T operator()(const T& in) const { return in; } -}; - -// simple construction from char* -template -struct ll_convert_impl -{ - T operator()(const typename T::value_type* in) const { return { in }; } -}; - -// specialize ll_convert_impl to return EXPR -#define ll_convert_alias(TO, FROM, EXPR) \ -template<> \ -struct ll_convert_impl \ -{ \ - /* param_type optimally passes both char* and string */ \ - TO operator()(typename boost::call_traits::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 -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(const wchar_t* zstr) { return std::wcslen(zstr); } -template <> -inline size_t ll_convert_length (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(const char*) -// and ll_convert(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 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 -STRING windows_message(unsigned long error) -{ - return ll_convert(windows_message(error)); -} - -/// There's only one real implementation -template<> -LL_COMMON_API std::wstring windows_message(unsigned long error); - -/// Get Windows message string, implicitly calling GetLastError() -template -STRING windows_message() { return windows_message(GetLastError()); } - -//@} - -LL_COMMON_API std::optional llstring_getoptenv(const std::string& key); - -#else // ! LL_WINDOWS - -LL_COMMON_API std::optional 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& 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& 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& 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 -std::vector::string_type> -LLStringUtilBase::getTokens(const string_type& instr, const string_type& delims) -{ - std::vector tokens; - getTokens(instr, tokens, delims); - return tokens; -} - -// static -template -std::vector::string_type> -LLStringUtilBase::getTokens(const string_type& instr, - const string_type& drop_delims, - const string_type& keep_delims, - const string_type& quotes) -{ - std::vector tokens; - getTokens(instr, tokens, drop_delims, keep_delims, quotes); - return tokens; -} - -// static -template -std::vector::string_type> -LLStringUtilBase::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 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 -struct InString -{ - typedef std::basic_string 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::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 InEscString: public InString -{ -public: - typedef InString 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::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::contains(mEscapes, *mIter) && - (mIter+1) != mEnd; - } - - const string_type mEscapes; - bool mIsEsc; -}; - -/// getTokens() implementation based on InString concept -template -void getTokens(INSTRING& instr, std::vector& 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 -void LLStringUtilBase::getTokens(const string_type& string, std::vector& 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 instring(string.begin(), string.end()); - LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes); -} - -// static -template -void LLStringUtilBase::getTokens(const string_type& string, std::vector& 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 > instrp; - if (escapes.empty()) - instrp.reset(new LLStringUtilBaseImpl::InString(string.begin(), string.end())); - else - instrp.reset(new LLStringUtilBaseImpl::InEscString(string.begin(), string.end(), escapes)); - LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes); -} - -// static -template -S32 LLStringUtilBase::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 -S32 LLStringUtilBase::compareStrings(const string_type& lhs, const string_type& rhs) -{ - return LLStringOps::collate(lhs.c_str(), rhs.c_str()); -} - -// static -template -S32 LLStringUtilBase::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::toUpper(lhs_string); - LLStringUtilBase::toUpper(rhs_string); - result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); - } - return result; -} - -//static -template -S32 LLStringUtilBase::compareInsensitive(const string_type& lhs, const string_type& rhs) -{ - string_type lhs_string(lhs); - string_type rhs_string(rhs); - LLStringUtilBase::toUpper(lhs_string); - LLStringUtilBase::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 -S32 LLStringUtilBase::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 -S32 LLStringUtilBase::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 -bool LLStringUtilBase::precedesDict( const string_type& a, const string_type& b ) -{ - if( a.size() && b.size() ) - { - return (LLStringUtilBase::compareDict(a.c_str(), b.c_str()) < 0); - } - else - { - return (!b.empty()); - } -} - -//static -template -void LLStringUtilBase::toUpper(string_type& string) -{ - if( !string.empty() ) - { - std::transform( - string.begin(), - string.end(), - string.begin(), - (T(*)(T)) &LLStringOps::toUpper); - } -} - -//static -template -void LLStringUtilBase::toLower(string_type& string) -{ - if( !string.empty() ) - { - std::transform( - string.begin(), - string.end(), - string.begin(), - (T(*)(T)) &LLStringOps::toLower); - } -} - -//static -template -void LLStringUtilBase::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 -void LLStringUtilBase::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 -void LLStringUtilBase::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 -void LLStringUtilBase::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 -void LLStringUtilBase::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 -void LLStringUtilBase::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 -void LLStringUtilBase::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 -void LLStringUtilBase::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 -void LLStringUtilBase::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 -std::basic_string LLStringUtilBase::capitalize(const string_type& str) -{ - string_type result(str); - capitalize(result); - return result; -} - -//static -template -void LLStringUtilBase::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 -bool LLStringUtilBase::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 -void LLStringUtilBase::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 -std::basic_string LLStringUtilBase::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 -void LLStringUtilBase::_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 -void LLStringUtilBase::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 -void LLStringUtilBase::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 -bool LLStringUtilBase::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 -bool LLStringUtilBase::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 -bool LLStringUtilBase::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 -auto LLStringUtilBase::getoptenv(const std::string& key) -> std::optional -{ - auto found(llstring_getoptenv(key)); - if (found) - { - // return populated std::optional - return { ll_convert(*found) }; - } - else - { - // empty std::optional - return {}; - } -} - -// static -template -auto LLStringUtilBase::getenv(const std::string& key, const string_type& dflt) -> string_type -{ - auto found(getoptenv(key)); - if (found) - { - return *found; - } - else - { - return dflt; - } -} - -template -bool LLStringUtilBase::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 -bool LLStringUtilBase::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 -bool LLStringUtilBase::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 -bool LLStringUtilBase::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 -bool LLStringUtilBase::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 -bool LLStringUtilBase::convertToU32(const string_type& string, U32& value) -{ - if( string.empty() ) - { - return false; - } - - string_type temp( string ); - trim(temp); - U32 v; - std::basic_istringstream i_stream((string_type)temp); - if(i_stream >> v) - { - value = v; - return true; - } - return false; -} - -template -bool LLStringUtilBase::convertToS32(const string_type& string, S32& value) -{ - if( string.empty() ) - { - return false; - } - - string_type temp( string ); - trim(temp); - S32 v; - std::basic_istringstream 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 -bool LLStringUtilBase::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 -bool LLStringUtilBase::convertToF64(const string_type& string, F64& value) -{ - if( string.empty() ) - { - return false; - } - - string_type temp( string ); - trim(temp); - F64 v; - std::basic_istringstream 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 -void LLStringUtilBase::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 +#include +#include +#include +#include +#include // std::wcslen() +//#include +#include +#include +#include +#include +#include "llformat.h" + +#if LL_LINUX +#include +#include +#endif + +#include +#include + +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 + +namespace std +{ +template<> +struct char_traits +{ + 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(memchr(__s, __a, __n * sizeof(char_type))); } + + static char_type* + move(char_type* __s1, const char_type* __s2, size_t __n) + { return static_cast(memmove(__s1, __s2, __n * sizeof(char_type))); } + + static char_type* + copy(char_type* __s1, const char_type* __s2, size_t __n) + { return static_cast(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(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(__c); } + + static int_type + to_int_type(const char_type& __c) + { return static_cast(__c); } + + static bool + eq_int_type(const int_type& __c1, const int_type& __c2) + { return __c1 == __c2; } + + static int_type + eof() { return static_cast(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 datetimeToCodes; + +public: + static std::vector sWeekDayList; + static std::vector sWeekDayShortList; + static std::vector sMonthList; + static std::vector 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 LLStringUtilBase +{ +private: + static std::string sLocale; + +public: + typedef std::basic_string 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 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& tokens, + const string_type& delims); + /// like simple scan overload, but returns scanned vector + static std::vector 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& 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 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& 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 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 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& tokens); +}; + +template const std::basic_string LLStringUtilBase::null; +template std::string LLStringUtilBase::sLocale; + +typedef LLStringUtilBase LLStringUtil; +typedef LLStringUtilBase LLWStringUtil; +typedef std::basic_string 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 +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(from_value) API. +template +TO ll_convert(const FROM& in) +{ + return ll_convert_impl()(in); +} + +// degenerate case +template +struct ll_convert_impl +{ + T operator()(const T& in) const { return in; } +}; + +// simple construction from char* +template +struct ll_convert_impl +{ + T operator()(const typename T::value_type* in) const { return { in }; } +}; + +// specialize ll_convert_impl to return EXPR +#define ll_convert_alias(TO, FROM, EXPR) \ +template<> \ +struct ll_convert_impl \ +{ \ + /* param_type optimally passes both char* and string */ \ + TO operator()(typename boost::call_traits::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 +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(const wchar_t* zstr) { return std::wcslen(zstr); } +template <> +inline size_t ll_convert_length (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(const char*) +// and ll_convert(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 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 +STRING windows_message(unsigned long error) +{ + return ll_convert(windows_message(error)); +} + +/// There's only one real implementation +template<> +LL_COMMON_API std::wstring windows_message(unsigned long error); + +/// Get Windows message string, implicitly calling GetLastError() +template +STRING windows_message() { return windows_message(GetLastError()); } + +//@} + +LL_COMMON_API std::optional llstring_getoptenv(const std::string& key); + +#else // ! LL_WINDOWS + +LL_COMMON_API std::optional 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& 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& 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& 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 +std::vector::string_type> +LLStringUtilBase::getTokens(const string_type& instr, const string_type& delims) +{ + std::vector tokens; + getTokens(instr, tokens, delims); + return tokens; +} + +// static +template +std::vector::string_type> +LLStringUtilBase::getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes) +{ + std::vector tokens; + getTokens(instr, tokens, drop_delims, keep_delims, quotes); + return tokens; +} + +// static +template +std::vector::string_type> +LLStringUtilBase::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 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 +struct InString +{ + typedef std::basic_string 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::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 InEscString: public InString +{ +public: + typedef InString 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::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::contains(mEscapes, *mIter) && + (mIter+1) != mEnd; + } + + const string_type mEscapes; + bool mIsEsc; +}; + +/// getTokens() implementation based on InString concept +template +void getTokens(INSTRING& instr, std::vector& 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 +void LLStringUtilBase::getTokens(const string_type& string, std::vector& 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 instring(string.begin(), string.end()); + LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes); +} + +// static +template +void LLStringUtilBase::getTokens(const string_type& string, std::vector& 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 > instrp; + if (escapes.empty()) + instrp.reset(new LLStringUtilBaseImpl::InString(string.begin(), string.end())); + else + instrp.reset(new LLStringUtilBaseImpl::InEscString(string.begin(), string.end(), escapes)); + LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes); +} + +// static +template +S32 LLStringUtilBase::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 +S32 LLStringUtilBase::compareStrings(const string_type& lhs, const string_type& rhs) +{ + return LLStringOps::collate(lhs.c_str(), rhs.c_str()); +} + +// static +template +S32 LLStringUtilBase::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::toUpper(lhs_string); + LLStringUtilBase::toUpper(rhs_string); + result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); + } + return result; +} + +//static +template +S32 LLStringUtilBase::compareInsensitive(const string_type& lhs, const string_type& rhs) +{ + string_type lhs_string(lhs); + string_type rhs_string(rhs); + LLStringUtilBase::toUpper(lhs_string); + LLStringUtilBase::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 +S32 LLStringUtilBase::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 +S32 LLStringUtilBase::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 +bool LLStringUtilBase::precedesDict( const string_type& a, const string_type& b ) +{ + if( a.size() && b.size() ) + { + return (LLStringUtilBase::compareDict(a.c_str(), b.c_str()) < 0); + } + else + { + return (!b.empty()); + } +} + +//static +template +void LLStringUtilBase::toUpper(string_type& string) +{ + if( !string.empty() ) + { + std::transform( + string.begin(), + string.end(), + string.begin(), + (T(*)(T)) &LLStringOps::toUpper); + } +} + +//static +template +void LLStringUtilBase::toLower(string_type& string) +{ + if( !string.empty() ) + { + std::transform( + string.begin(), + string.end(), + string.begin(), + (T(*)(T)) &LLStringOps::toLower); + } +} + +//static +template +void LLStringUtilBase::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 +void LLStringUtilBase::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 +void LLStringUtilBase::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 +void LLStringUtilBase::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 +void LLStringUtilBase::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 +void LLStringUtilBase::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 +void LLStringUtilBase::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 +void LLStringUtilBase::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 +void LLStringUtilBase::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 +std::basic_string LLStringUtilBase::capitalize(const string_type& str) +{ + string_type result(str); + capitalize(result); + return result; +} + +//static +template +void LLStringUtilBase::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 +bool LLStringUtilBase::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 +void LLStringUtilBase::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 +std::basic_string LLStringUtilBase::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 +void LLStringUtilBase::_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 +void LLStringUtilBase::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 +void LLStringUtilBase::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 +bool LLStringUtilBase::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 +bool LLStringUtilBase::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 +bool LLStringUtilBase::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 +auto LLStringUtilBase::getoptenv(const std::string& key) -> std::optional +{ + auto found(llstring_getoptenv(key)); + if (found) + { + // return populated std::optional + return { ll_convert(*found) }; + } + else + { + // empty std::optional + return {}; + } +} + +// static +template +auto LLStringUtilBase::getenv(const std::string& key, const string_type& dflt) -> string_type +{ + auto found(getoptenv(key)); + if (found) + { + return *found; + } + else + { + return dflt; + } +} + +template +bool LLStringUtilBase::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 +bool LLStringUtilBase::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 +bool LLStringUtilBase::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 +bool LLStringUtilBase::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 +bool LLStringUtilBase::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 +bool LLStringUtilBase::convertToU32(const string_type& string, U32& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + U32 v; + std::basic_istringstream i_stream((string_type)temp); + if(i_stream >> v) + { + value = v; + return true; + } + return false; +} + +template +bool LLStringUtilBase::convertToS32(const string_type& string, S32& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + S32 v; + std::basic_istringstream 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 +bool LLStringUtilBase::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 +bool LLStringUtilBase::convertToF64(const string_type& string, F64& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + F64 v; + std::basic_istringstream 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 +void LLStringUtilBase::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 a9b38fb14b..97f95369e5 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 -#include - -#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 string_hash_t; -#else - typedef __gnu_cxx::hash_multimap string_hash_t; -#endif - string_hash_t mStringHash; -#else - typedef std::list 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<= (3<<(i-1))) - tablesize = (1<<(i+1)); - else - tablesize = (1< > 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 +#include + +#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 string_hash_t; +#else + typedef __gnu_cxx::hash_multimap string_hash_t; +#endif + string_hash_t mStringHash; +#else + typedef std::list 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<= (3<<(i-1))) + tablesize = (1<<(i+1)); + else + tablesize = (1< > string_set_t; + string_set_t* mStringList; // [mTableSize] +}; + + +#endif diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 02fd0733e5..496ec0d869 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 -#ifdef LL_USESYSTEMLIBS -# include -#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 -#include -#include -#include -#include -#include -#include -#include "llfasttimer.h" - -using namespace llsd; - -#if LL_WINDOWS -# include "llwin32headerslean.h" -# include // GetPerformanceInfo() et al. -# include -#elif LL_DARWIN -# include "llsys_objc.h" -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -#elif LL_LINUX -# include -# include -# include -# include -# include -const char MEMINFO_FILE[] = "/proc/meminfo"; -# include -#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(&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 - void add(const LLSD::String& name, const T& value, - typename boost::enable_if >::type* = 0) - { - mStats[name] = LLSD::Integer(value); - } - - // Store every floating-point type as LLSD::Real. - template - void add(const LLSD::String& name, const T& value, - typename boost::enable_if >::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() + " "); - - // 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(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(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::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::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 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 +#ifdef LL_USESYSTEMLIBS +# include +#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 +#include +#include +#include +#include +#include +#include +#include "llfasttimer.h" + +using namespace llsd; + +#if LL_WINDOWS +# include "llwin32headerslean.h" +# include // GetPerformanceInfo() et al. +# include +#elif LL_DARWIN +# include "llsys_objc.h" +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#elif LL_LINUX +# include +# include +# include +# include +# include +const char MEMINFO_FILE[] = "/proc/meminfo"; +# include +#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(&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 + void add(const LLSD::String& name, const T& value, + typename boost::enable_if >::type* = 0) + { + mStats[name] = LLSD::Integer(value); + } + + // Store every floating-point type as LLSD::Real. + template + void add(const LLSD::String& name, const T& value, + typename boost::enable_if >::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() + " "); + + // 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(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(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::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::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 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 3ef1e2b528..f97d49eeb1 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 -#include - -class LL_COMMON_API LLOSInfo : public LLSingleton -{ - 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; -
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 +#include + +class LL_COMMON_API LLOSInfo : public LLSingleton +{ + 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; +
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/llthread.cpp b/indra/llcommon/llthread.cpp index deb1df640c..cf1b51e0aa 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 -#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 +#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/lltimer.cpp b/indra/llcommon/lltimer.cpp index 8a8451b927..a3e871661c 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -1,608 +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 -#include - -#if LL_WINDOWS -# include "llwin32headerslean.h" -#elif LL_LINUX || LL_DARWIN -# include -# include -#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(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); -} - - +/** + * @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 +#include + +#if LL_WINDOWS +# include "llwin32headerslean.h" +#elif LL_LINUX || LL_DARWIN +# include +# include +#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(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 60eb007595..d79f250585 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 -#endif -#include - -#include "stdtypes.h" - -#include -#include -// 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 +#endif +#include + +#include "stdtypes.h" + +#include +#include +// 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 b4664318cc..df82276cd2 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 - -#include "lluuid.h" - -// system includes -#include -#include -#include - -// 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(static_cast(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 - 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 > tokenizer; - boost::char_separator 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 + +#include "lluuid.h" + +// system includes +#include +#include +#include + +// 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(static_cast(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 + 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 > tokenizer; + boost::char_separator 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 cc59c6b370..37ee0a0ac7 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 - -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 + +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 f3821de71b..3b37365ec7 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 -#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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - - // 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define HAVE_NETINET_IN_H -#ifdef HAVE_NETINET_IN_H -#include -#if !LL_DARWIN -#include -#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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + // 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_NETINET_IN_H +#ifdef HAVE_NETINET_IN_H +#include +#if !LL_DARWIN +#include +#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 526a79f3a7..68c4b05fdc 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 -#include -#include -#include "stdtypes.h" -#include "llpreprocessor.h" -#include - -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::value, "LLUUID must be trivial copy"); -static_assert(std::is_trivially_move_assignable::value, "LLUUID must be trivial move"); -static_assert(std::is_standard_layout::value, "LLUUID must be a standard layout type"); - -typedef std::vector uuid_vec_t; -typedef std::set uuid_set_t; - -// Helper structure for ordering lluuids in stl containers. eg: -// std::map 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 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 - { - 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 +#include +#include +#include "stdtypes.h" +#include "llpreprocessor.h" +#include + +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::value, "LLUUID must be trivial copy"); +static_assert(std::is_trivially_move_assignable::value, "LLUUID must be trivial move"); +static_assert(std::is_standard_layout::value, "LLUUID must be a standard layout type"); + +typedef std::vector uuid_vec_t; +typedef std::set uuid_set_t; + +// Helper structure for ordering lluuids in stl containers. eg: +// std::map 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 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 + { + 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 22a922c94b..b751c95679 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 delete_list; - std::vector 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); - } -} - -//============================================================================ - +/** + * @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 delete_list; + std::vector 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 803fff78d9..2a68584ab3 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 -#include -#include -#include -#include - -#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 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 +/** + * @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 +#include +#include +#include +#include + +#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 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 89ef5b3450..b18712b8e9 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 -#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_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& 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& 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 +#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_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& 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& 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^")); + } +} -- cgit v1.2.3