diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 21:25:21 +0200 |
---|---|---|
committer | Andrey Lihatskiy <alihatskiy@productengine.com> | 2024-05-22 22:40:26 +0300 |
commit | e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 (patch) | |
tree | 1bb897489ce524986f6196201c10ac0d8861aa5f /indra/llcommon | |
parent | 069ea06848f766466f1a281144c82a0f2bd79f3a (diff) |
Fix line endlings
Diffstat (limited to 'indra/llcommon')
54 files changed, 26611 insertions, 26611 deletions
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 <windows.h>
-
-// special defines for VC5/6 (if no actual PSDK is installed):
-#if _MSC_VER < 1300
-typedef unsigned __int64 DWORD64, *PDWORD64;
-#if defined(_WIN64)
-typedef unsigned __int64 SIZE_T, *PSIZE_T;
-#else
-typedef unsigned long SIZE_T, *PSIZE_T;
-#endif
-#endif // _MSC_VER < 1300
-
-class StackWalkerInternal; // forward
-class StackWalker
-{
-public:
- typedef enum StackWalkOptions
- {
- // No addition info will be retrived
- // (only the address is available)
- RetrieveNone = 0,
-
- // Try to get the symbol-name
- RetrieveSymbol = 1,
-
- // Try to get the line for this symbol
- RetrieveLine = 2,
-
- // Try to retrieve the module-infos
- RetrieveModuleInfo = 4,
-
- // Also retrieve the version for the DLL/EXE
- RetrieveFileVersion = 8,
-
- // Contains all the abouve
- RetrieveVerbose = 0xF,
-
- // Generate a "good" symbol-search-path
- SymBuildPath = 0x10,
-
- // Also use the public Microsoft-Symbol-Server
- SymUseSymSrv = 0x20,
-
- // Contains all the abouve "Sym"-options
- SymAll = 0x30,
-
- // Contains all options (default)
- OptionsAll = 0x3F
- } StackWalkOptions;
-
- StackWalker(
- bool verbose = true,
- int options = OptionsAll, // 'int' is by design, to combine the enum-flags
- LPCSTR szSymPath = NULL,
- DWORD dwProcessId = GetCurrentProcessId(),
- HANDLE hProcess = GetCurrentProcess()
- );
- StackWalker(DWORD dwProcessId, HANDLE hProcess);
- virtual ~StackWalker();
-
- typedef BOOL (__stdcall *PReadProcessMemoryRoutine)(
- HANDLE hProcess,
- DWORD64 qwBaseAddress,
- PVOID lpBuffer,
- DWORD nSize,
- LPDWORD lpNumberOfBytesRead,
- LPVOID pUserData // optional data, which was passed in "ShowCallstack"
- );
-
- bool LoadModules();
-
- bool ShowCallstack(
- bool verbose,
- HANDLE hThread = GetCurrentThread(),
- const CONTEXT *context = NULL,
- PReadProcessMemoryRoutine readMemoryFunction = NULL,
- LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback
- );
-
-#if _MSC_VER >= 1300
-// due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public"
-// in older compilers in order to use it... starting with VC7 we can declare it as "protected"
-protected:
-#endif
- enum { STACKWALK_MAX_NAMELEN = 4096 }; // max name length for found symbols
-
-protected:
- // Entry for each Callstack-Entry
- typedef struct CallstackEntry
- {
- DWORD64 offset; // if 0, we have no valid entry
- CHAR name[STACKWALK_MAX_NAMELEN];
- CHAR undName[STACKWALK_MAX_NAMELEN];
- CHAR undFullName[STACKWALK_MAX_NAMELEN];
- DWORD64 offsetFromSmybol;
- DWORD offsetFromLine;
- DWORD lineNumber;
- CHAR lineFileName[STACKWALK_MAX_NAMELEN];
- DWORD symType;
- LPCSTR symTypeString;
- CHAR moduleName[STACKWALK_MAX_NAMELEN];
- DWORD64 baseOfImage;
- CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
- } CallstackEntry;
-
- enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
-
- virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
- virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);
- virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry);
- virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
- virtual void OnOutput(LPCSTR szText);
-
- StackWalkerInternal *m_sw;
- HANDLE m_hProcess;
- DWORD m_dwProcessId;
- bool m_modulesLoaded;
- LPSTR m_szSymPath;
-
- bool m_verbose;
- int m_options;
- int m_MaxRecursionCount;
-
- static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);
-
- friend StackWalkerInternal;
-}; // class StackWalker
-
-
-// The "ugly" assembler-implementation is needed for systems before XP
-// If you have a new PSDK and you only compile for XP and later, then you can use
-// the "RtlCaptureContext"
-// Currently there is no define which determines the PSDK-Version...
-// So we just use the compiler-version (and assumes that the PSDK is
-// the one which was installed by the VS-IDE)
-
-// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later...
-// But I currently use it in x64/IA64 environments...
-//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400)
-
-#if defined(_M_IX86)
-#ifdef CURRENT_THREAD_VIA_EXCEPTION
-// TODO: The following is not a "good" implementation,
-// because the callstack is only valid in the "__except" block...
-#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
- do { \
- memset(&c, 0, sizeof(CONTEXT)); \
- EXCEPTION_POINTERS *pExp = NULL; \
- __try { \
- throw 0; \
- } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \
- if (pExp != NULL) \
- memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \
- c.ContextFlags = contextFlags; \
- } while(0);
-#else
-// The following should be enough for walking the callstack...
-#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
- do { \
- memset(&c, 0, sizeof(CONTEXT)); \
- c.ContextFlags = contextFlags; \
- __asm call x \
- __asm x: pop eax \
- __asm mov c.Eip, eax \
- __asm mov c.Ebp, ebp \
- __asm mov c.Esp, esp \
- } while(0);
-#endif
-
-#else
-
-// The following is defined for x86 (XP and higher), x64 and IA64:
-#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \
- do { \
- memset(&c, 0, sizeof(CONTEXT)); \
- c.ContextFlags = contextFlags; \
- RtlCaptureContext(&c); \
-} while(0);
-#endif
-
-#endif // LL_WINDOWS
+/********************************************************************** + * + * StackWalker.h + * + * + * + * $LicenseInfo:firstyear=2016&license=bsd$ + * + * Linden notes: Small modifications from the original source at https://stackwalker.codeplex.com/ + * + * LICENSE (http://www.opensource.org/licenses/bsd-license.php) + * + * Copyright (c) 2005-2009, Jochen Kalmbach + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of Jochen Kalmbach nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * **********************************************************************/ + +#if LL_WINDOWS + +// #pragma once is supported starting with _MCS_VER 1000, +// so we need not to check the version (because we only support _MSC_VER >= 1100)! +#pragma once + +#include <windows.h> + +// special defines for VC5/6 (if no actual PSDK is installed): +#if _MSC_VER < 1300 +typedef unsigned __int64 DWORD64, *PDWORD64; +#if defined(_WIN64) +typedef unsigned __int64 SIZE_T, *PSIZE_T; +#else +typedef unsigned long SIZE_T, *PSIZE_T; +#endif +#endif // _MSC_VER < 1300 + +class StackWalkerInternal; // forward +class StackWalker +{ +public: + typedef enum StackWalkOptions + { + // No addition info will be retrived + // (only the address is available) + RetrieveNone = 0, + + // Try to get the symbol-name + RetrieveSymbol = 1, + + // Try to get the line for this symbol + RetrieveLine = 2, + + // Try to retrieve the module-infos + RetrieveModuleInfo = 4, + + // Also retrieve the version for the DLL/EXE + RetrieveFileVersion = 8, + + // Contains all the abouve + RetrieveVerbose = 0xF, + + // Generate a "good" symbol-search-path + SymBuildPath = 0x10, + + // Also use the public Microsoft-Symbol-Server + SymUseSymSrv = 0x20, + + // Contains all the abouve "Sym"-options + SymAll = 0x30, + + // Contains all options (default) + OptionsAll = 0x3F + } StackWalkOptions; + + StackWalker( + bool verbose = true, + int options = OptionsAll, // 'int' is by design, to combine the enum-flags + LPCSTR szSymPath = NULL, + DWORD dwProcessId = GetCurrentProcessId(), + HANDLE hProcess = GetCurrentProcess() + ); + StackWalker(DWORD dwProcessId, HANDLE hProcess); + virtual ~StackWalker(); + + typedef BOOL (__stdcall *PReadProcessMemoryRoutine)( + HANDLE hProcess, + DWORD64 qwBaseAddress, + PVOID lpBuffer, + DWORD nSize, + LPDWORD lpNumberOfBytesRead, + LPVOID pUserData // optional data, which was passed in "ShowCallstack" + ); + + bool LoadModules(); + + bool ShowCallstack( + bool verbose, + HANDLE hThread = GetCurrentThread(), + const CONTEXT *context = NULL, + PReadProcessMemoryRoutine readMemoryFunction = NULL, + LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback + ); + +#if _MSC_VER >= 1300 +// due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public" +// in older compilers in order to use it... starting with VC7 we can declare it as "protected" +protected: +#endif + enum { STACKWALK_MAX_NAMELEN = 4096 }; // max name length for found symbols + +protected: + // Entry for each Callstack-Entry + typedef struct CallstackEntry + { + DWORD64 offset; // if 0, we have no valid entry + CHAR name[STACKWALK_MAX_NAMELEN]; + CHAR undName[STACKWALK_MAX_NAMELEN]; + CHAR undFullName[STACKWALK_MAX_NAMELEN]; + DWORD64 offsetFromSmybol; + DWORD offsetFromLine; + DWORD lineNumber; + CHAR lineFileName[STACKWALK_MAX_NAMELEN]; + DWORD symType; + LPCSTR symTypeString; + CHAR moduleName[STACKWALK_MAX_NAMELEN]; + DWORD64 baseOfImage; + CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; + } CallstackEntry; + + enum CallstackEntryType {firstEntry, nextEntry, lastEntry}; + + virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName); + virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion); + virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry); + virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr); + virtual void OnOutput(LPCSTR szText); + + StackWalkerInternal *m_sw; + HANDLE m_hProcess; + DWORD m_dwProcessId; + bool m_modulesLoaded; + LPSTR m_szSymPath; + + bool m_verbose; + int m_options; + int m_MaxRecursionCount; + + static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead); + + friend StackWalkerInternal; +}; // class StackWalker + + +// The "ugly" assembler-implementation is needed for systems before XP +// If you have a new PSDK and you only compile for XP and later, then you can use +// the "RtlCaptureContext" +// Currently there is no define which determines the PSDK-Version... +// So we just use the compiler-version (and assumes that the PSDK is +// the one which was installed by the VS-IDE) + +// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later... +// But I currently use it in x64/IA64 environments... +//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400) + +#if defined(_M_IX86) +#ifdef CURRENT_THREAD_VIA_EXCEPTION +// TODO: The following is not a "good" implementation, +// because the callstack is only valid in the "__except" block... +#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ + do { \ + memset(&c, 0, sizeof(CONTEXT)); \ + EXCEPTION_POINTERS *pExp = NULL; \ + __try { \ + throw 0; \ + } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \ + if (pExp != NULL) \ + memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + } while(0); +#else +// The following should be enough for walking the callstack... +#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ + do { \ + memset(&c, 0, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + __asm call x \ + __asm x: pop eax \ + __asm mov c.Eip, eax \ + __asm mov c.Ebp, ebp \ + __asm mov c.Esp, esp \ + } while(0); +#endif + +#else + +// The following is defined for x86 (XP and higher), x64 and IA64: +#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ + do { \ + memset(&c, 0, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + RtlCaptureContext(&c); \ +} while(0); +#endif + +#endif // LL_WINDOWS diff --git a/indra/llcommon/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 <cstdlib>
-
-#ifdef LL_DARWIN
-#include <sys/types.h>
-#include <unistd.h>
-#include <sys/sysctl.h>
-#endif
-
-#include "llcommon.h"
-#include "llapr.h"
-#include "llerrorcontrol.h"
-#include "llframetimer.h"
-#include "lllivefile.h"
-#include "llmemory.h"
-#include "llstl.h" // for DeletePointer()
-#include "llstring.h"
-#include "lleventtimer.h"
-#include "stringize.h"
-#include "llcleanup.h"
-#include "llevents.h"
-#include "llsdutil.h"
-
-//
-// Signal handling
-#ifndef LL_WINDOWS
-# include <signal.h>
-# include <unistd.h> // for fork()
-void setup_signals();
-void default_unix_signal_handler(int signum, siginfo_t *info, void *);
-
-#if LL_LINUX
-#else
-// Called by breakpad exception handler after the minidump has been generated.
-bool unix_post_minidump_callback(const char *dump_dir,
- const char *minidump_id,
- void *context, bool succeeded);
-#endif
-
-# if LL_DARWIN
-/* OSX doesn't support SIGRT* */
-S32 LL_SMACKDOWN_SIGNAL = SIGUSR1;
-S32 LL_HEARTBEAT_SIGNAL = SIGUSR2;
-# else // linux or (assumed) other similar unixoid
-/* We want reliable delivery of our signals - SIGRT* is it. */
-/* Old LinuxThreads versions eat SIGRTMIN+0 to SIGRTMIN+2, avoid those. */
-/* Note that SIGRTMIN/SIGRTMAX may expand to a glibc function call with a
- nonconstant result so these are not consts and cannot be used in constant-
- expressions. SIGRTMAX may return -1 on rare broken setups. */
-S32 LL_SMACKDOWN_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-1) : SIGUSR1;
-S32 LL_HEARTBEAT_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-0) : SIGUSR2;
-# endif // LL_DARWIN
-#endif // !LL_WINDOWS
-
-// the static application instance
-LLApp* LLApp::sApplication = NULL;
-
-// Allows the generation of core files for post mortem under gdb
-// and disables crashlogger
-bool LLApp::sDisableCrashlogger = false;
-
-// Local flag for whether or not to do logging in signal handlers.
-//static
-bool LLApp::sLogInSignal = false;
-
-// static
-// Keeps track of application status
-LLScalarCond<LLApp::EAppStatus> LLApp::sStatus{LLApp::APP_STATUS_STOPPED};
-LLAppErrorHandler LLApp::sErrorHandler = NULL;
-
-
-LLApp::LLApp()
-{
- // Set our status to running
- setStatus(APP_STATUS_RUNNING);
-
- LLCommon::initClass();
-
- // initialize the options structure. We need to make this an array
- // because the structured data will not auto-allocate if we
- // reference an invalid location with the [] operator.
- mOptions = LLSD::emptyArray();
- LLSD sd;
- for(int i = 0; i < PRIORITY_COUNT; ++i)
- {
- mOptions.append(sd);
- }
-
- // Make sure we clean up APR when we exit
- // Don't need to do this if we're cleaning up APR in the destructor
- //atexit(ll_cleanup_apr);
-
- // Set the application to this instance.
- sApplication = this;
-
- // initialize the buffer to write the minidump filename to
- // (this is used to avoid allocating memory in the crash handler)
- memset(mMinidumpPath, 0, MAX_MINDUMP_PATH_LENGTH);
- mCrashReportPipeStr = L"\\\\.\\pipe\\LLCrashReporterPipe";
-}
-
-
-LLApp::~LLApp()
-{
-
- // reclaim live file memory
- std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer());
- mLiveFiles.clear();
-
- setStopped();
-
- SUBSYSTEM_CLEANUP_DBG(LLCommon);
-}
-
-// static
-LLApp* LLApp::instance()
-{
- return sApplication;
-}
-
-
-LLSD LLApp::getOption(const std::string& name) const
-{
- LLSD rv;
- LLSD::array_const_iterator iter = mOptions.beginArray();
- LLSD::array_const_iterator end = mOptions.endArray();
- for(; iter != end; ++iter)
- {
- rv = (*iter)[name];
- if(rv.isDefined()) break;
- }
- return rv;
-}
-
-bool LLApp::parseCommandOptions(int argc, char** argv)
-{
- LLSD commands;
- std::string name;
- std::string value;
- for(int ii = 1; ii < argc; ++ii)
- {
- if(argv[ii][0] != '-')
- {
- LL_INFOS() << "Did not find option identifier while parsing token: "
- << argv[ii] << LL_ENDL;
- return false;
- }
- int offset = 1;
- if(argv[ii][1] == '-') ++offset;
- name.assign(&argv[ii][offset]);
- if(((ii+1) >= argc) || (argv[ii+1][0] == '-'))
- {
- // we found another option after this one or we have
- // reached the end. simply record that this option was
- // found and continue.
- int flag = name.compare("logfile");
- if (0 == flag)
- {
- commands[name] = "log";
- }
- else
- {
- commands[name] = true;
- }
-
- continue;
- }
- ++ii;
- value.assign(argv[ii]);
-
-#if LL_WINDOWS
- //Windows changed command line parsing. Deal with it.
- S32 slen = value.length() - 1;
- S32 start = 0;
- S32 end = slen;
- if (argv[ii][start]=='"')start++;
- if (argv[ii][end]=='"')end--;
- if (start!=0 || end!=slen)
- {
- value = value.substr (start,end);
- }
-#endif
-
- commands[name] = value;
- }
- setOptionData(PRIORITY_COMMAND_LINE, commands);
- return true;
-}
-
-bool LLApp::parseCommandOptions(int argc, wchar_t** wargv)
-{
- LLSD commands;
- std::string name;
- std::string value;
- for(int ii = 1; ii < argc; ++ii)
- {
- if(wargv[ii][0] != '-')
- {
- LL_INFOS() << "Did not find option identifier while parsing token: "
- << wargv[ii] << LL_ENDL;
- return false;
- }
- int offset = 1;
- if(wargv[ii][1] == '-') ++offset;
-
-#if LL_WINDOWS
- name.assign(utf16str_to_utf8str(&wargv[ii][offset]));
-#else
- name.assign(wstring_to_utf8str(&wargv[ii][offset]));
-#endif
- if(((ii+1) >= argc) || (wargv[ii+1][0] == '-'))
- {
- // we found another option after this one or we have
- // reached the end. simply record that this option was
- // found and continue.
- int flag = name.compare("logfile");
- if (0 == flag)
- {
- commands[name] = "log";
- }
- else
- {
- commands[name] = true;
- }
-
- continue;
- }
- ++ii;
-
-#if LL_WINDOWS
- value.assign(utf16str_to_utf8str((wargv[ii])));
-#else
- value.assign(wstring_to_utf8str((wargv[ii])));
-#endif
-
-#if LL_WINDOWS
- //Windows changed command line parsing. Deal with it.
- S32 slen = value.length() - 1;
- S32 start = 0;
- S32 end = slen;
- if (wargv[ii][start]=='"')start++;
- if (wargv[ii][end]=='"')end--;
- if (start!=0 || end!=slen)
- {
- value = value.substr (start,end);
- }
-#endif
-
- commands[name] = value;
- }
- setOptionData(PRIORITY_COMMAND_LINE, commands);
- return true;
-}
-
-void LLApp::manageLiveFile(LLLiveFile* livefile)
-{
- if(!livefile) return;
- livefile->checkAndReload();
- livefile->addToEventTimer();
- mLiveFiles.push_back(livefile);
-}
-
-bool LLApp::setOptionData(OptionPriority level, LLSD data)
-{
- if((level < 0)
- || (level >= PRIORITY_COUNT)
- || (data.type() != LLSD::TypeMap))
- {
- return false;
- }
- mOptions[level] = data;
- return true;
-}
-
-LLSD LLApp::getOptionData(OptionPriority level)
-{
- if((level < 0) || (level >= PRIORITY_COUNT))
- {
- return LLSD();
- }
- return mOptions[level];
-}
-
-void LLApp::stepFrame()
-{
- LLFrameTimer::updateFrameTime();
- LLFrameTimer::updateFrameCount();
- LLEventTimer::updateClass();
- mRunner.run();
-}
-
-void LLApp::setupErrorHandling(bool second_instance)
-{
- // Error handling is done by starting up an error handling thread, which just sleeps and
- // occasionally checks to see if the app is in an error state, and sees if it needs to be run.
-
-#if LL_WINDOWS
-
-#else // ! LL_WINDOWS
-
-#if ! defined(LL_BUGSPLAT)
- //
- // Start up signal handling.
- //
- // There are two different classes of signals. Synchronous signals are delivered to a specific
- // thread, asynchronous signals can be delivered to any thread (in theory)
- //
- setup_signals();
-#endif // ! LL_BUGSPLAT
-
-#endif // ! LL_WINDOWS
-}
-
-void LLApp::setErrorHandler(LLAppErrorHandler handler)
-{
- LLApp::sErrorHandler = handler;
-}
-
-// static
-void LLApp::runErrorHandler()
-{
- if (LLApp::sErrorHandler)
- {
- LLApp::sErrorHandler();
- }
-
- //LL_INFOS() << "App status now STOPPED" << LL_ENDL;
- LLApp::setStopped();
-}
-
-namespace
-{
-
-static std::map<LLApp::EAppStatus, const char*> statusDesc
-{
- { LLApp::APP_STATUS_RUNNING, "running" },
- { LLApp::APP_STATUS_QUITTING, "quitting" },
- { LLApp::APP_STATUS_STOPPED, "stopped" },
- { LLApp::APP_STATUS_ERROR, "error" }
-};
-
-} // anonymous namespace
-
-// static
-void LLApp::setStatus(EAppStatus status)
-{
- // notify everyone waiting on sStatus any time its value changes
- sStatus.set_all(status);
-
- // This can also happen very late in the application lifecycle -- don't
- // resurrect a deleted LLSingleton
- if (! LLEventPumps::wasDeleted())
- {
- // notify interested parties of status change
- LLSD statsd;
- auto found = statusDesc.find(status);
- if (found != statusDesc.end())
- {
- statsd = found->second;
- }
- else
- {
- // unknown status? at least report value
- statsd = LLSD::Integer(status);
- }
- LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd));
- }
-}
-
-
-// static
-void LLApp::setError()
-{
- // set app status to ERROR
- setStatus(APP_STATUS_ERROR);
-}
-
-void LLApp::setDebugFileNames(const std::string &path)
-{
- mStaticDebugFileName = path + "static_debug_info.log";
- mDynamicDebugFileName = path + "dynamic_debug_info.log";
-}
-
-void LLApp::writeMiniDump()
-{
-}
-
-// static
-void LLApp::setQuitting()
-{
- if (!isExiting())
- {
- // If we're already exiting, we don't want to reset our state back to quitting.
- LL_INFOS() << "Setting app state to QUITTING" << LL_ENDL;
- setStatus(APP_STATUS_QUITTING);
- }
-}
-
-
-// static
-void LLApp::setStopped()
-{
- setStatus(APP_STATUS_STOPPED);
-}
-
-
-// static
-bool LLApp::isStopped()
-{
- return (APP_STATUS_STOPPED == sStatus.get());
-}
-
-
-// static
-bool LLApp::isRunning()
-{
- return (APP_STATUS_RUNNING == sStatus.get());
-}
-
-
-// static
-bool LLApp::isError()
-{
- return (APP_STATUS_ERROR == sStatus.get());
-}
-
-
-// static
-bool LLApp::isQuitting()
-{
- return (APP_STATUS_QUITTING == sStatus.get());
-}
-
-// static
-bool LLApp::isExiting()
-{
- return isQuitting() || isError();
-}
-
-void LLApp::disableCrashlogger()
-{
- sDisableCrashlogger = true;
-}
-
-// static
-bool LLApp::isCrashloggerDisabled()
-{
- return sDisableCrashlogger;
-}
-
-// static
-int LLApp::getPid()
-{
-#if LL_WINDOWS
- return GetCurrentProcessId();
-#else
- return getpid();
-#endif
-}
-
-#ifndef LL_WINDOWS
-void setup_signals()
-{
- //
- // Set up signal handlers that may result in program termination
- //
- struct sigaction act;
- act.sa_sigaction = default_unix_signal_handler;
- sigemptyset( &act.sa_mask );
- act.sa_flags = SA_SIGINFO;
-
- // Synchronous signals
-# ifndef LL_BUGSPLAT
- sigaction(SIGABRT, &act, NULL);
-# endif
- sigaction(SIGALRM, &act, NULL);
- sigaction(SIGBUS, &act, NULL);
- sigaction(SIGFPE, &act, NULL);
- sigaction(SIGHUP, &act, NULL);
- sigaction(SIGILL, &act, NULL);
- sigaction(SIGPIPE, &act, NULL);
- sigaction(SIGSEGV, &act, NULL);
- sigaction(SIGSYS, &act, NULL);
-
- sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL);
- sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL);
-
- // Asynchronous signals that are normally ignored
-#ifndef LL_IGNORE_SIGCHLD
- sigaction(SIGCHLD, &act, NULL);
-#endif // LL_IGNORE_SIGCHLD
- sigaction(SIGUSR2, &act, NULL);
-
- // Asynchronous signals that result in attempted graceful exit
- sigaction(SIGHUP, &act, NULL);
- sigaction(SIGTERM, &act, NULL);
- sigaction(SIGINT, &act, NULL);
-
- // Asynchronous signals that result in core
- sigaction(SIGQUIT, &act, NULL);
-
-}
-
-void clear_signals()
-{
- struct sigaction act;
- act.sa_handler = SIG_DFL;
- sigemptyset( &act.sa_mask );
- act.sa_flags = SA_SIGINFO;
-
- // Synchronous signals
-# ifndef LL_BUGSPLAT
- sigaction(SIGABRT, &act, NULL);
-# endif
- sigaction(SIGALRM, &act, NULL);
- sigaction(SIGBUS, &act, NULL);
- sigaction(SIGFPE, &act, NULL);
- sigaction(SIGHUP, &act, NULL);
- sigaction(SIGILL, &act, NULL);
- sigaction(SIGPIPE, &act, NULL);
- sigaction(SIGSEGV, &act, NULL);
- sigaction(SIGSYS, &act, NULL);
-
- sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL);
- sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL);
-
- // Asynchronous signals that are normally ignored
-#ifndef LL_IGNORE_SIGCHLD
- sigaction(SIGCHLD, &act, NULL);
-#endif // LL_IGNORE_SIGCHLD
-
- // Asynchronous signals that result in attempted graceful exit
- sigaction(SIGHUP, &act, NULL);
- sigaction(SIGTERM, &act, NULL);
- sigaction(SIGINT, &act, NULL);
-
- // Asynchronous signals that result in core
- sigaction(SIGUSR2, &act, NULL);
- sigaction(SIGQUIT, &act, NULL);
-}
-
-
-
-void default_unix_signal_handler(int signum, siginfo_t *info, void *)
-{
- // Unix implementation of synchronous signal handler
- // This runs in the thread that threw the signal.
- // We do the somewhat sketchy operation of blocking in here until the error handler
- // has gracefully stopped the app.
-
- if (LLApp::sLogInSignal)
- {
- LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL;
- }
-
-
- switch (signum)
- {
- case SIGCHLD:
- if (LLApp::sLogInSignal)
- {
- LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL;
- }
-
- return;
- case SIGABRT:
- // Note that this handler is not set for SIGABRT when using Bugsplat
- // Abort just results in termination of the app, no funky error handling.
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Got SIGABRT, terminating" << LL_ENDL;
- }
- clear_signals();
- raise(signum);
- return;
- case SIGINT:
- case SIGHUP:
- case SIGTERM:
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL;
- }
- // Graceful exit
- // Just set our state to quitting, not error
- if (LLApp::isQuitting() || LLApp::isError())
- {
- // We're already trying to die, just ignore this signal
- if (LLApp::sLogInSignal)
- {
- LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL;
- }
- return;
- }
- LLApp::setQuitting();
- return;
- case SIGALRM:
- case SIGPIPE:
- case SIGUSR2:
- default:
- if (signum == LL_SMACKDOWN_SIGNAL ||
- signum == SIGBUS ||
- signum == SIGILL ||
- signum == SIGFPE ||
- signum == SIGSEGV ||
- signum == SIGQUIT)
- {
- if (signum == LL_SMACKDOWN_SIGNAL)
- {
- // Smackdown treated just like any other app termination, for now
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Handling smackdown signal!" << LL_ENDL;
- }
- else
- {
- // Don't log anything, even errors - this is because this signal could happen anywhere.
- LLError::setDefaultLevel(LLError::LEVEL_NONE);
- }
-
- // Change the signal that we reraise to SIGABRT, so we generate a core dump.
- signum = SIGABRT;
- }
-
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL;
- }
- if (LLApp::isError())
- {
- // Received second fatal signal while handling first, just die right now
- // Set the signal handlers back to default before handling the signal - this makes the next signal wipe out the app.
- clear_signals();
-
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Got another fatal signal while in the error handler, die now!" << LL_ENDL;
- }
- raise(signum);
- return;
- }
-
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - Flagging error status and waiting for shutdown" << LL_ENDL;
- }
-
- if (LLApp::isCrashloggerDisabled()) // Don't gracefully handle any signal, crash and core for a gdb post mortem
- {
- clear_signals();
- LL_WARNS() << "Fatal signal received, not handling the crash here, passing back to operating system" << LL_ENDL;
- raise(signum);
- return;
- }
-
- // Flag status to ERROR
- LLApp::setError();
-
- if (LLApp::sLogInSignal)
- {
- LL_WARNS() << "Signal handler - App is stopped, reraising signal" << LL_ENDL;
- }
- clear_signals();
- raise(signum);
- return;
- } else {
- if (LLApp::sLogInSignal)
- {
- LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL;
- }
- }
- }
-}
-
-bool unix_post_minidump_callback(const char *dump_dir,
- const char *minidump_id,
- void *context, bool succeeded)
-{
- // Copy minidump file path into fixed buffer in the app instance to avoid
- // heap allocations in a crash handler.
-
- // path format: <dump_dir>/<minidump_id>.dmp
- auto dirPathLength = strlen(dump_dir);
- auto idLength = strlen(minidump_id);
-
- // The path must not be truncated.
- llassert((dirPathLength + idLength + 5) <= LLApp::MAX_MINDUMP_PATH_LENGTH);
-
- char * path = LLApp::instance()->getMiniDumpFilename();
- auto remaining = LLApp::MAX_MINDUMP_PATH_LENGTH;
- strncpy(path, dump_dir, remaining);
- remaining -= dirPathLength;
- path += dirPathLength;
- if (remaining > 0 && dirPathLength > 0 && path[-1] != '/')
- {
- *path++ = '/';
- --remaining;
- }
- if (remaining > 0)
- {
- strncpy(path, minidump_id, remaining);
- remaining -= idLength;
- path += idLength;
- strncpy(path, ".dmp", remaining);
- }
-
- LL_INFOS("CRASHREPORT") << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << LL_ENDL;
- LLApp::runErrorHandler();
-
-#ifndef LL_RELEASE_FOR_DOWNLOAD
- clear_signals();
- return false;
-#else
- return true;
-#endif
-}
-#endif // !WINDOWS
-
+/** + * @file llapp.cpp + * @brief Implementation of the LLApp class. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llapp.h" + +#include <cstdlib> + +#ifdef LL_DARWIN +#include <sys/types.h> +#include <unistd.h> +#include <sys/sysctl.h> +#endif + +#include "llcommon.h" +#include "llapr.h" +#include "llerrorcontrol.h" +#include "llframetimer.h" +#include "lllivefile.h" +#include "llmemory.h" +#include "llstl.h" // for DeletePointer() +#include "llstring.h" +#include "lleventtimer.h" +#include "stringize.h" +#include "llcleanup.h" +#include "llevents.h" +#include "llsdutil.h" + +// +// Signal handling +#ifndef LL_WINDOWS +# include <signal.h> +# include <unistd.h> // for fork() +void setup_signals(); +void default_unix_signal_handler(int signum, siginfo_t *info, void *); + +#if LL_LINUX +#else +// Called by breakpad exception handler after the minidump has been generated. +bool unix_post_minidump_callback(const char *dump_dir, + const char *minidump_id, + void *context, bool succeeded); +#endif + +# if LL_DARWIN +/* OSX doesn't support SIGRT* */ +S32 LL_SMACKDOWN_SIGNAL = SIGUSR1; +S32 LL_HEARTBEAT_SIGNAL = SIGUSR2; +# else // linux or (assumed) other similar unixoid +/* We want reliable delivery of our signals - SIGRT* is it. */ +/* Old LinuxThreads versions eat SIGRTMIN+0 to SIGRTMIN+2, avoid those. */ +/* Note that SIGRTMIN/SIGRTMAX may expand to a glibc function call with a + nonconstant result so these are not consts and cannot be used in constant- + expressions. SIGRTMAX may return -1 on rare broken setups. */ +S32 LL_SMACKDOWN_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-1) : SIGUSR1; +S32 LL_HEARTBEAT_SIGNAL = (SIGRTMAX >= 0) ? (SIGRTMAX-0) : SIGUSR2; +# endif // LL_DARWIN +#endif // !LL_WINDOWS + +// the static application instance +LLApp* LLApp::sApplication = NULL; + +// Allows the generation of core files for post mortem under gdb +// and disables crashlogger +bool LLApp::sDisableCrashlogger = false; + +// Local flag for whether or not to do logging in signal handlers. +//static +bool LLApp::sLogInSignal = false; + +// static +// Keeps track of application status +LLScalarCond<LLApp::EAppStatus> LLApp::sStatus{LLApp::APP_STATUS_STOPPED}; +LLAppErrorHandler LLApp::sErrorHandler = NULL; + + +LLApp::LLApp() +{ + // Set our status to running + setStatus(APP_STATUS_RUNNING); + + LLCommon::initClass(); + + // initialize the options structure. We need to make this an array + // because the structured data will not auto-allocate if we + // reference an invalid location with the [] operator. + mOptions = LLSD::emptyArray(); + LLSD sd; + for(int i = 0; i < PRIORITY_COUNT; ++i) + { + mOptions.append(sd); + } + + // Make sure we clean up APR when we exit + // Don't need to do this if we're cleaning up APR in the destructor + //atexit(ll_cleanup_apr); + + // Set the application to this instance. + sApplication = this; + + // initialize the buffer to write the minidump filename to + // (this is used to avoid allocating memory in the crash handler) + memset(mMinidumpPath, 0, MAX_MINDUMP_PATH_LENGTH); + mCrashReportPipeStr = L"\\\\.\\pipe\\LLCrashReporterPipe"; +} + + +LLApp::~LLApp() +{ + + // reclaim live file memory + std::for_each(mLiveFiles.begin(), mLiveFiles.end(), DeletePointer()); + mLiveFiles.clear(); + + setStopped(); + + SUBSYSTEM_CLEANUP_DBG(LLCommon); +} + +// static +LLApp* LLApp::instance() +{ + return sApplication; +} + + +LLSD LLApp::getOption(const std::string& name) const +{ + LLSD rv; + LLSD::array_const_iterator iter = mOptions.beginArray(); + LLSD::array_const_iterator end = mOptions.endArray(); + for(; iter != end; ++iter) + { + rv = (*iter)[name]; + if(rv.isDefined()) break; + } + return rv; +} + +bool LLApp::parseCommandOptions(int argc, char** argv) +{ + LLSD commands; + std::string name; + std::string value; + for(int ii = 1; ii < argc; ++ii) + { + if(argv[ii][0] != '-') + { + LL_INFOS() << "Did not find option identifier while parsing token: " + << argv[ii] << LL_ENDL; + return false; + } + int offset = 1; + if(argv[ii][1] == '-') ++offset; + name.assign(&argv[ii][offset]); + if(((ii+1) >= argc) || (argv[ii+1][0] == '-')) + { + // we found another option after this one or we have + // reached the end. simply record that this option was + // found and continue. + int flag = name.compare("logfile"); + if (0 == flag) + { + commands[name] = "log"; + } + else + { + commands[name] = true; + } + + continue; + } + ++ii; + value.assign(argv[ii]); + +#if LL_WINDOWS + //Windows changed command line parsing. Deal with it. + S32 slen = value.length() - 1; + S32 start = 0; + S32 end = slen; + if (argv[ii][start]=='"')start++; + if (argv[ii][end]=='"')end--; + if (start!=0 || end!=slen) + { + value = value.substr (start,end); + } +#endif + + commands[name] = value; + } + setOptionData(PRIORITY_COMMAND_LINE, commands); + return true; +} + +bool LLApp::parseCommandOptions(int argc, wchar_t** wargv) +{ + LLSD commands; + std::string name; + std::string value; + for(int ii = 1; ii < argc; ++ii) + { + if(wargv[ii][0] != '-') + { + LL_INFOS() << "Did not find option identifier while parsing token: " + << wargv[ii] << LL_ENDL; + return false; + } + int offset = 1; + if(wargv[ii][1] == '-') ++offset; + +#if LL_WINDOWS + name.assign(utf16str_to_utf8str(&wargv[ii][offset])); +#else + name.assign(wstring_to_utf8str(&wargv[ii][offset])); +#endif + if(((ii+1) >= argc) || (wargv[ii+1][0] == '-')) + { + // we found another option after this one or we have + // reached the end. simply record that this option was + // found and continue. + int flag = name.compare("logfile"); + if (0 == flag) + { + commands[name] = "log"; + } + else + { + commands[name] = true; + } + + continue; + } + ++ii; + +#if LL_WINDOWS + value.assign(utf16str_to_utf8str((wargv[ii]))); +#else + value.assign(wstring_to_utf8str((wargv[ii]))); +#endif + +#if LL_WINDOWS + //Windows changed command line parsing. Deal with it. + S32 slen = value.length() - 1; + S32 start = 0; + S32 end = slen; + if (wargv[ii][start]=='"')start++; + if (wargv[ii][end]=='"')end--; + if (start!=0 || end!=slen) + { + value = value.substr (start,end); + } +#endif + + commands[name] = value; + } + setOptionData(PRIORITY_COMMAND_LINE, commands); + return true; +} + +void LLApp::manageLiveFile(LLLiveFile* livefile) +{ + if(!livefile) return; + livefile->checkAndReload(); + livefile->addToEventTimer(); + mLiveFiles.push_back(livefile); +} + +bool LLApp::setOptionData(OptionPriority level, LLSD data) +{ + if((level < 0) + || (level >= PRIORITY_COUNT) + || (data.type() != LLSD::TypeMap)) + { + return false; + } + mOptions[level] = data; + return true; +} + +LLSD LLApp::getOptionData(OptionPriority level) +{ + if((level < 0) || (level >= PRIORITY_COUNT)) + { + return LLSD(); + } + return mOptions[level]; +} + +void LLApp::stepFrame() +{ + LLFrameTimer::updateFrameTime(); + LLFrameTimer::updateFrameCount(); + LLEventTimer::updateClass(); + mRunner.run(); +} + +void LLApp::setupErrorHandling(bool second_instance) +{ + // Error handling is done by starting up an error handling thread, which just sleeps and + // occasionally checks to see if the app is in an error state, and sees if it needs to be run. + +#if LL_WINDOWS + +#else // ! LL_WINDOWS + +#if ! defined(LL_BUGSPLAT) + // + // Start up signal handling. + // + // There are two different classes of signals. Synchronous signals are delivered to a specific + // thread, asynchronous signals can be delivered to any thread (in theory) + // + setup_signals(); +#endif // ! LL_BUGSPLAT + +#endif // ! LL_WINDOWS +} + +void LLApp::setErrorHandler(LLAppErrorHandler handler) +{ + LLApp::sErrorHandler = handler; +} + +// static +void LLApp::runErrorHandler() +{ + if (LLApp::sErrorHandler) + { + LLApp::sErrorHandler(); + } + + //LL_INFOS() << "App status now STOPPED" << LL_ENDL; + LLApp::setStopped(); +} + +namespace +{ + +static std::map<LLApp::EAppStatus, const char*> statusDesc +{ + { LLApp::APP_STATUS_RUNNING, "running" }, + { LLApp::APP_STATUS_QUITTING, "quitting" }, + { LLApp::APP_STATUS_STOPPED, "stopped" }, + { LLApp::APP_STATUS_ERROR, "error" } +}; + +} // anonymous namespace + +// static +void LLApp::setStatus(EAppStatus status) +{ + // notify everyone waiting on sStatus any time its value changes + sStatus.set_all(status); + + // This can also happen very late in the application lifecycle -- don't + // resurrect a deleted LLSingleton + if (! LLEventPumps::wasDeleted()) + { + // notify interested parties of status change + LLSD statsd; + auto found = statusDesc.find(status); + if (found != statusDesc.end()) + { + statsd = found->second; + } + else + { + // unknown status? at least report value + statsd = LLSD::Integer(status); + } + LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd)); + } +} + + +// static +void LLApp::setError() +{ + // set app status to ERROR + setStatus(APP_STATUS_ERROR); +} + +void LLApp::setDebugFileNames(const std::string &path) +{ + mStaticDebugFileName = path + "static_debug_info.log"; + mDynamicDebugFileName = path + "dynamic_debug_info.log"; +} + +void LLApp::writeMiniDump() +{ +} + +// static +void LLApp::setQuitting() +{ + if (!isExiting()) + { + // If we're already exiting, we don't want to reset our state back to quitting. + LL_INFOS() << "Setting app state to QUITTING" << LL_ENDL; + setStatus(APP_STATUS_QUITTING); + } +} + + +// static +void LLApp::setStopped() +{ + setStatus(APP_STATUS_STOPPED); +} + + +// static +bool LLApp::isStopped() +{ + return (APP_STATUS_STOPPED == sStatus.get()); +} + + +// static +bool LLApp::isRunning() +{ + return (APP_STATUS_RUNNING == sStatus.get()); +} + + +// static +bool LLApp::isError() +{ + return (APP_STATUS_ERROR == sStatus.get()); +} + + +// static +bool LLApp::isQuitting() +{ + return (APP_STATUS_QUITTING == sStatus.get()); +} + +// static +bool LLApp::isExiting() +{ + return isQuitting() || isError(); +} + +void LLApp::disableCrashlogger() +{ + sDisableCrashlogger = true; +} + +// static +bool LLApp::isCrashloggerDisabled() +{ + return sDisableCrashlogger; +} + +// static +int LLApp::getPid() +{ +#if LL_WINDOWS + return GetCurrentProcessId(); +#else + return getpid(); +#endif +} + +#ifndef LL_WINDOWS +void setup_signals() +{ + // + // Set up signal handlers that may result in program termination + // + struct sigaction act; + act.sa_sigaction = default_unix_signal_handler; + sigemptyset( &act.sa_mask ); + act.sa_flags = SA_SIGINFO; + + // Synchronous signals +# ifndef LL_BUGSPLAT + sigaction(SIGABRT, &act, NULL); +# endif + sigaction(SIGALRM, &act, NULL); + sigaction(SIGBUS, &act, NULL); + sigaction(SIGFPE, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGILL, &act, NULL); + sigaction(SIGPIPE, &act, NULL); + sigaction(SIGSEGV, &act, NULL); + sigaction(SIGSYS, &act, NULL); + + sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL); + sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); + + // Asynchronous signals that are normally ignored +#ifndef LL_IGNORE_SIGCHLD + sigaction(SIGCHLD, &act, NULL); +#endif // LL_IGNORE_SIGCHLD + sigaction(SIGUSR2, &act, NULL); + + // Asynchronous signals that result in attempted graceful exit + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + + // Asynchronous signals that result in core + sigaction(SIGQUIT, &act, NULL); + +} + +void clear_signals() +{ + struct sigaction act; + act.sa_handler = SIG_DFL; + sigemptyset( &act.sa_mask ); + act.sa_flags = SA_SIGINFO; + + // Synchronous signals +# ifndef LL_BUGSPLAT + sigaction(SIGABRT, &act, NULL); +# endif + sigaction(SIGALRM, &act, NULL); + sigaction(SIGBUS, &act, NULL); + sigaction(SIGFPE, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGILL, &act, NULL); + sigaction(SIGPIPE, &act, NULL); + sigaction(SIGSEGV, &act, NULL); + sigaction(SIGSYS, &act, NULL); + + sigaction(LL_HEARTBEAT_SIGNAL, &act, NULL); + sigaction(LL_SMACKDOWN_SIGNAL, &act, NULL); + + // Asynchronous signals that are normally ignored +#ifndef LL_IGNORE_SIGCHLD + sigaction(SIGCHLD, &act, NULL); +#endif // LL_IGNORE_SIGCHLD + + // Asynchronous signals that result in attempted graceful exit + sigaction(SIGHUP, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + + // Asynchronous signals that result in core + sigaction(SIGUSR2, &act, NULL); + sigaction(SIGQUIT, &act, NULL); +} + + + +void default_unix_signal_handler(int signum, siginfo_t *info, void *) +{ + // Unix implementation of synchronous signal handler + // This runs in the thread that threw the signal. + // We do the somewhat sketchy operation of blocking in here until the error handler + // has gracefully stopped the app. + + if (LLApp::sLogInSignal) + { + LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL; + } + + + switch (signum) + { + case SIGCHLD: + if (LLApp::sLogInSignal) + { + LL_INFOS() << "Signal handler - Got SIGCHLD from " << info->si_pid << LL_ENDL; + } + + return; + case SIGABRT: + // Note that this handler is not set for SIGABRT when using Bugsplat + // Abort just results in termination of the app, no funky error handling. + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Got SIGABRT, terminating" << LL_ENDL; + } + clear_signals(); + raise(signum); + return; + case SIGINT: + case SIGHUP: + case SIGTERM: + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Got SIGINT, HUP, or TERM, exiting gracefully" << LL_ENDL; + } + // Graceful exit + // Just set our state to quitting, not error + if (LLApp::isQuitting() || LLApp::isError()) + { + // We're already trying to die, just ignore this signal + if (LLApp::sLogInSignal) + { + LL_INFOS() << "Signal handler - Already trying to quit, ignoring signal!" << LL_ENDL; + } + return; + } + LLApp::setQuitting(); + return; + case SIGALRM: + case SIGPIPE: + case SIGUSR2: + default: + if (signum == LL_SMACKDOWN_SIGNAL || + signum == SIGBUS || + signum == SIGILL || + signum == SIGFPE || + signum == SIGSEGV || + signum == SIGQUIT) + { + if (signum == LL_SMACKDOWN_SIGNAL) + { + // Smackdown treated just like any other app termination, for now + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Handling smackdown signal!" << LL_ENDL; + } + else + { + // Don't log anything, even errors - this is because this signal could happen anywhere. + LLError::setDefaultLevel(LLError::LEVEL_NONE); + } + + // Change the signal that we reraise to SIGABRT, so we generate a core dump. + signum = SIGABRT; + } + + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Handling fatal signal!" << LL_ENDL; + } + if (LLApp::isError()) + { + // Received second fatal signal while handling first, just die right now + // Set the signal handlers back to default before handling the signal - this makes the next signal wipe out the app. + clear_signals(); + + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Got another fatal signal while in the error handler, die now!" << LL_ENDL; + } + raise(signum); + return; + } + + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - Flagging error status and waiting for shutdown" << LL_ENDL; + } + + if (LLApp::isCrashloggerDisabled()) // Don't gracefully handle any signal, crash and core for a gdb post mortem + { + clear_signals(); + LL_WARNS() << "Fatal signal received, not handling the crash here, passing back to operating system" << LL_ENDL; + raise(signum); + return; + } + + // Flag status to ERROR + LLApp::setError(); + + if (LLApp::sLogInSignal) + { + LL_WARNS() << "Signal handler - App is stopped, reraising signal" << LL_ENDL; + } + clear_signals(); + raise(signum); + return; + } else { + if (LLApp::sLogInSignal) + { + LL_INFOS() << "Signal handler - Unhandled signal " << signum << ", ignoring!" << LL_ENDL; + } + } + } +} + +bool unix_post_minidump_callback(const char *dump_dir, + const char *minidump_id, + void *context, bool succeeded) +{ + // Copy minidump file path into fixed buffer in the app instance to avoid + // heap allocations in a crash handler. + + // path format: <dump_dir>/<minidump_id>.dmp + auto dirPathLength = strlen(dump_dir); + auto idLength = strlen(minidump_id); + + // The path must not be truncated. + llassert((dirPathLength + idLength + 5) <= LLApp::MAX_MINDUMP_PATH_LENGTH); + + char * path = LLApp::instance()->getMiniDumpFilename(); + auto remaining = LLApp::MAX_MINDUMP_PATH_LENGTH; + strncpy(path, dump_dir, remaining); + remaining -= dirPathLength; + path += dirPathLength; + if (remaining > 0 && dirPathLength > 0 && path[-1] != '/') + { + *path++ = '/'; + --remaining; + } + if (remaining > 0) + { + strncpy(path, minidump_id, remaining); + remaining -= idLength; + path += idLength; + strncpy(path, ".dmp", remaining); + } + + LL_INFOS("CRASHREPORT") << "generated minidump: " << LLApp::instance()->getMiniDumpFilename() << LL_ENDL; + LLApp::runErrorHandler(); + +#ifndef LL_RELEASE_FOR_DOWNLOAD + clear_signals(); + return false; +#else + return true; +#endif +} +#endif // !WINDOWS + diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index 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 <map>
-#include "llcond.h"
-#include "llrun.h"
-#include "llsd.h"
-#include <atomic>
-#include <chrono>
-// Forward declarations
-class LLLiveFile;
-#if LL_LINUX
-#include <signal.h>
-#endif
-
-typedef void (*LLAppErrorHandler)();
-
-#if !LL_WINDOWS
-extern S32 LL_SMACKDOWN_SIGNAL;
-extern S32 LL_HEARTBEAT_SIGNAL;
-
-// Clear all of the signal handlers (which we want to do for the child process when we fork
-void clear_signals();
-
-#endif
-
-class LL_COMMON_API LLApp
-{
-public:
- typedef enum e_app_status
- {
- APP_STATUS_RUNNING, // The application is currently running - the default status
- APP_STATUS_QUITTING, // The application is currently quitting - threads should listen for this and clean up
- APP_STATUS_STOPPED, // The application is no longer running - tells the error thread it can exit
- APP_STATUS_ERROR // The application had a fatal error occur - tells the error thread to run
- } EAppStatus;
-
-
- LLApp();
- virtual ~LLApp();
-
- /**
- * @brief Return the static app instance if one was created.
- */
- static LLApp* instance();
-
- /** @name Runtime options */
- //@{
- /**
- * @brief Enumeration to specify option priorities in highest to
- * lowest order.
- */
- enum OptionPriority
- {
- PRIORITY_RUNTIME_OVERRIDE,
- PRIORITY_COMMAND_LINE,
- PRIORITY_SPECIFIC_CONFIGURATION,
- PRIORITY_GENERAL_CONFIGURATION,
- PRIORITY_DEFAULT,
- PRIORITY_COUNT
- };
-
- /**
- * @brief Get the application option at the highest priority.
- *
- * If the return value is undefined, the option does not exist.
- * @param name The name of the option.
- * @return Returns the option data.
- */
- LLSD getOption(const std::string& name) const;
-
- /**
- * @brief Parse ASCII command line options and insert them into
- * application command line options.
- *
- * The name inserted into the option will have leading option
- * identifiers (a minus or double minus) stripped. All options
- * with values will be stored as a string, while all options
- * without values will be stored as true.
- * @param argc The argc passed into main().
- * @param argv The argv passed into main().
- * @return Returns true if the parse succeeded.
- */
- bool parseCommandOptions(int argc, char** argv);
-
- /**
- * @brief Parse Unicode command line options and insert them into
- * application command line options.
- *
- * The name inserted into the option will have leading option
- * identifiers (a minus or double minus) stripped. All options
- * with values will be stored as a string, while all options
- * without values will be stored as true.
- * @param argc The argc passed into main().
- * @param wargv The wargv passed into main().
- * @return Returns true if the parse succeeded.
- */
- bool parseCommandOptions(int argc, wchar_t** wargv);
-
- /**
- * @brief Keep track of live files automatically.
- *
- * *TODO: it currently uses the <code>addToEventTimer()</code> API
- * instead of the runner. I should probalby use the runner.
- *
- * *NOTE: DO NOT add the livefile instance to any kind of check loop.
- *
- * @param livefile A valid instance of an LLLiveFile. This LLApp
- * instance will delete the livefile instance.
- */
- void manageLiveFile(LLLiveFile* livefile);
-
- /**
- * @brief Set the options at the specified priority.
- *
- * This function completely replaces the options at the priority
- * level with the data specified. This function will make sure
- * level and data might be valid before doing the replace.
- * @param level The priority level of the data.
- * @param data The data to set.
- * @return Returns true if the option was set.
- */
- bool setOptionData(OptionPriority level, LLSD data);
-
- /**
- * @brief Get the option data at the specified priority.
- *
- * This method is probably not so useful except when merging
- * information.
- * @param level The priority level of the data.
- * @return Returns The data (if any) at the level priority.
- */
- LLSD getOptionData(OptionPriority level);
- //@}
-
-
-
- //
- // Main application logic
- //
- virtual bool init() = 0; // Override to do application initialization
-
- //
- // cleanup()
- //
- // It's currently assumed that the cleanup() method will only get
- // called from the main thread or the error handling thread, as it will
- // likely do thread shutdown, among other things.
- //
- virtual bool cleanup() = 0; // Override to do application cleanup
-
- //
- // frame()
- //
- // Pass control to the application for a single frame. Returns 'done'
- // flag: if frame() returns false, it expects to be called again.
- //
- virtual bool frame() = 0; // Override for application body logic
-
- //
- // Crash logging
- //
- void disableCrashlogger(); // Let the OS handle the crashes
- static bool isCrashloggerDisabled(); // Get the here above set value
-
- //
- // Application status
- //
- static void setQuitting(); // Set status to QUITTING, the app is now shutting down
- static void setStopped(); // Set status to STOPPED, the app is done running and should exit
- static void setError(); // Set status to ERROR, the error handler should run
- static bool isStopped();
- static bool isRunning();
- static bool isQuitting();
- static bool isError();
- static bool isExiting(); // Either quitting or error (app is exiting, cleanly or not)
- static int getPid();
-
- //
- // Sleep for specified time while still running
- //
- // For use by a coroutine or thread that performs some maintenance on a
- // periodic basis. (See also LLEventTimer.) This method supports the
- // pattern of an "infinite" loop that sleeps for some time, performs some
- // action, then sleeps again. The trouble with literally sleeping a worker
- // thread is that it could potentially sleep right through attempted
- // application shutdown. This method avoids that by returning false as
- // soon as the application status changes away from APP_STATUS_RUNNING
- // (isRunning()).
- //
- // sleep() returns true if it sleeps undisturbed for the entire specified
- // duration. The idea is that you can code 'while sleep(duration) ...',
- // which will break the loop once shutdown begins.
- //
- // Since any time-based LLUnit should be implicitly convertible to
- // F32Milliseconds, accept that specific type as a proxy.
- static bool sleep(F32Milliseconds duration);
- // Allow any duration defined in terms of <chrono>.
- // One can imagine a wonderfully general bidirectional conversion system
- // between any type derived from LLUnits::LLUnit<T, LLUnits::Seconds> and
- // any std::chrono::duration -- but that doesn't yet exist.
- template <typename Rep, typename Period>
- bool sleep(const std::chrono::duration<Rep, Period>& duration)
- {
- // wait_for_unequal() has the opposite bool return convention
- return ! sStatus.wait_for_unequal(duration, APP_STATUS_RUNNING);
- }
-
- /** @name Error handling methods */
- //@{
- /**
- * @brief Do our generic platform-specific error-handling setup --
- * signals on unix, structured exceptions on windows.
- *
- * DO call this method if your app will either spawn children or be
- * spawned by a launcher.
- * Call just after app object construction.
- * (Otherwise your app will crash when getting signals,
- * and will not core dump.)
- *
- * DO NOT call this method if your application has specialized
- * error handling code.
- */
- void setupErrorHandling(bool mSecondInstance=false);
-
- void setErrorHandler(LLAppErrorHandler handler);
- static void runErrorHandler(); // run shortly after we detect an error
- //@}
-
- // the maximum length of the minidump filename returned by getMiniDumpFilename()
- static const U32 MAX_MINDUMP_PATH_LENGTH = 256;
-
- // change the directory where Breakpad minidump files are written to
- void setDebugFileNames(const std::string &path);
-
- // Return the Google Breakpad minidump filename after a crash.
- char *getMiniDumpFilename() { return mMinidumpPath; }
- std::string* getStaticDebugFile() { return &mStaticDebugFileName; }
- std::string* getDynamicDebugFile() { return &mDynamicDebugFileName; }
-
- // Write out a Google Breakpad minidump file.
- void writeMiniDump();
-
-
- /**
- * @brief Get a reference to the application runner
- *
- * Please use the runner with caution. Since the Runner usage
- * pattern is not yet clear, this method just gives access to it
- * to add and remove runnables.
- * @return Returns the application runner. Do not save the
- * pointer past the caller's stack frame.
- */
- LLRunner& getRunner() { return mRunner; }
-
-#ifdef LL_WINDOWS
- virtual void reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { }
-#endif
-
-public:
- typedef std::map<std::string, std::string> string_map;
- string_map mOptionMap; // Contains all command-line options and arguments in a map
-
-protected:
-
- static void setStatus(EAppStatus status); // Use this to change the application status.
- static LLScalarCond<EAppStatus> sStatus; // Reflects current application status
- static bool sDisableCrashlogger; // Let the OS handle crashes for us.
- std::wstring mCrashReportPipeStr; //Name of pipe to use for crash reporting.
-
- std::string mDumpPath; //output path for google breakpad. Dependency workaround.
-
- /**
- * @brief This method is called once a frame to do once a frame tasks.
- */
- void stepFrame();
-
-private:
- // Contains the filename of the minidump file after a crash.
- char mMinidumpPath[MAX_MINDUMP_PATH_LENGTH];
-
- std::string mStaticDebugFileName;
- std::string mDynamicDebugFileName;
-
- // *NOTE: On Windows, we need a routine to reset the structured
- // exception handler when some evil driver has taken it over for
- // their own purposes
- typedef int(*signal_handler_func)(int signum);
- static LLAppErrorHandler sErrorHandler;
-
- // This is the application level runnable scheduler.
- LLRunner mRunner;
-
- /** @name Runtime option implementation */
- //@{
-
- // The application options.
- LLSD mOptions;
-
- // The live files for this application
- std::vector<LLLiveFile*> mLiveFiles;
- //@}
-
-private:
- // the static application instance if it was created.
- static LLApp* sApplication;
-
-#if !LL_WINDOWS
- friend void default_unix_signal_handler(int signum, siginfo_t *info, void *);
-#endif
-
-public:
- static bool sLogInSignal;
-};
-
-#endif // LL_LLAPP_H
+/** + * @file llapp.h + * @brief Declaration of the LLApp class. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAPP_H +#define LL_LLAPP_H + +#include <map> +#include "llcond.h" +#include "llrun.h" +#include "llsd.h" +#include <atomic> +#include <chrono> +// Forward declarations +class LLLiveFile; +#if LL_LINUX +#include <signal.h> +#endif + +typedef void (*LLAppErrorHandler)(); + +#if !LL_WINDOWS +extern S32 LL_SMACKDOWN_SIGNAL; +extern S32 LL_HEARTBEAT_SIGNAL; + +// Clear all of the signal handlers (which we want to do for the child process when we fork +void clear_signals(); + +#endif + +class LL_COMMON_API LLApp +{ +public: + typedef enum e_app_status + { + APP_STATUS_RUNNING, // The application is currently running - the default status + APP_STATUS_QUITTING, // The application is currently quitting - threads should listen for this and clean up + APP_STATUS_STOPPED, // The application is no longer running - tells the error thread it can exit + APP_STATUS_ERROR // The application had a fatal error occur - tells the error thread to run + } EAppStatus; + + + LLApp(); + virtual ~LLApp(); + + /** + * @brief Return the static app instance if one was created. + */ + static LLApp* instance(); + + /** @name Runtime options */ + //@{ + /** + * @brief Enumeration to specify option priorities in highest to + * lowest order. + */ + enum OptionPriority + { + PRIORITY_RUNTIME_OVERRIDE, + PRIORITY_COMMAND_LINE, + PRIORITY_SPECIFIC_CONFIGURATION, + PRIORITY_GENERAL_CONFIGURATION, + PRIORITY_DEFAULT, + PRIORITY_COUNT + }; + + /** + * @brief Get the application option at the highest priority. + * + * If the return value is undefined, the option does not exist. + * @param name The name of the option. + * @return Returns the option data. + */ + LLSD getOption(const std::string& name) const; + + /** + * @brief Parse ASCII command line options and insert them into + * application command line options. + * + * The name inserted into the option will have leading option + * identifiers (a minus or double minus) stripped. All options + * with values will be stored as a string, while all options + * without values will be stored as true. + * @param argc The argc passed into main(). + * @param argv The argv passed into main(). + * @return Returns true if the parse succeeded. + */ + bool parseCommandOptions(int argc, char** argv); + + /** + * @brief Parse Unicode command line options and insert them into + * application command line options. + * + * The name inserted into the option will have leading option + * identifiers (a minus or double minus) stripped. All options + * with values will be stored as a string, while all options + * without values will be stored as true. + * @param argc The argc passed into main(). + * @param wargv The wargv passed into main(). + * @return Returns true if the parse succeeded. + */ + bool parseCommandOptions(int argc, wchar_t** wargv); + + /** + * @brief Keep track of live files automatically. + * + * *TODO: it currently uses the <code>addToEventTimer()</code> API + * instead of the runner. I should probalby use the runner. + * + * *NOTE: DO NOT add the livefile instance to any kind of check loop. + * + * @param livefile A valid instance of an LLLiveFile. This LLApp + * instance will delete the livefile instance. + */ + void manageLiveFile(LLLiveFile* livefile); + + /** + * @brief Set the options at the specified priority. + * + * This function completely replaces the options at the priority + * level with the data specified. This function will make sure + * level and data might be valid before doing the replace. + * @param level The priority level of the data. + * @param data The data to set. + * @return Returns true if the option was set. + */ + bool setOptionData(OptionPriority level, LLSD data); + + /** + * @brief Get the option data at the specified priority. + * + * This method is probably not so useful except when merging + * information. + * @param level The priority level of the data. + * @return Returns The data (if any) at the level priority. + */ + LLSD getOptionData(OptionPriority level); + //@} + + + + // + // Main application logic + // + virtual bool init() = 0; // Override to do application initialization + + // + // cleanup() + // + // It's currently assumed that the cleanup() method will only get + // called from the main thread or the error handling thread, as it will + // likely do thread shutdown, among other things. + // + virtual bool cleanup() = 0; // Override to do application cleanup + + // + // frame() + // + // Pass control to the application for a single frame. Returns 'done' + // flag: if frame() returns false, it expects to be called again. + // + virtual bool frame() = 0; // Override for application body logic + + // + // Crash logging + // + void disableCrashlogger(); // Let the OS handle the crashes + static bool isCrashloggerDisabled(); // Get the here above set value + + // + // Application status + // + static void setQuitting(); // Set status to QUITTING, the app is now shutting down + static void setStopped(); // Set status to STOPPED, the app is done running and should exit + static void setError(); // Set status to ERROR, the error handler should run + static bool isStopped(); + static bool isRunning(); + static bool isQuitting(); + static bool isError(); + static bool isExiting(); // Either quitting or error (app is exiting, cleanly or not) + static int getPid(); + + // + // Sleep for specified time while still running + // + // For use by a coroutine or thread that performs some maintenance on a + // periodic basis. (See also LLEventTimer.) This method supports the + // pattern of an "infinite" loop that sleeps for some time, performs some + // action, then sleeps again. The trouble with literally sleeping a worker + // thread is that it could potentially sleep right through attempted + // application shutdown. This method avoids that by returning false as + // soon as the application status changes away from APP_STATUS_RUNNING + // (isRunning()). + // + // sleep() returns true if it sleeps undisturbed for the entire specified + // duration. The idea is that you can code 'while sleep(duration) ...', + // which will break the loop once shutdown begins. + // + // Since any time-based LLUnit should be implicitly convertible to + // F32Milliseconds, accept that specific type as a proxy. + static bool sleep(F32Milliseconds duration); + // Allow any duration defined in terms of <chrono>. + // One can imagine a wonderfully general bidirectional conversion system + // between any type derived from LLUnits::LLUnit<T, LLUnits::Seconds> and + // any std::chrono::duration -- but that doesn't yet exist. + template <typename Rep, typename Period> + bool sleep(const std::chrono::duration<Rep, Period>& duration) + { + // wait_for_unequal() has the opposite bool return convention + return ! sStatus.wait_for_unequal(duration, APP_STATUS_RUNNING); + } + + /** @name Error handling methods */ + //@{ + /** + * @brief Do our generic platform-specific error-handling setup -- + * signals on unix, structured exceptions on windows. + * + * DO call this method if your app will either spawn children or be + * spawned by a launcher. + * Call just after app object construction. + * (Otherwise your app will crash when getting signals, + * and will not core dump.) + * + * DO NOT call this method if your application has specialized + * error handling code. + */ + void setupErrorHandling(bool mSecondInstance=false); + + void setErrorHandler(LLAppErrorHandler handler); + static void runErrorHandler(); // run shortly after we detect an error + //@} + + // the maximum length of the minidump filename returned by getMiniDumpFilename() + static const U32 MAX_MINDUMP_PATH_LENGTH = 256; + + // change the directory where Breakpad minidump files are written to + void setDebugFileNames(const std::string &path); + + // Return the Google Breakpad minidump filename after a crash. + char *getMiniDumpFilename() { return mMinidumpPath; } + std::string* getStaticDebugFile() { return &mStaticDebugFileName; } + std::string* getDynamicDebugFile() { return &mDynamicDebugFileName; } + + // Write out a Google Breakpad minidump file. + void writeMiniDump(); + + + /** + * @brief Get a reference to the application runner + * + * Please use the runner with caution. Since the Runner usage + * pattern is not yet clear, this method just gives access to it + * to add and remove runnables. + * @return Returns the application runner. Do not save the + * pointer past the caller's stack frame. + */ + LLRunner& getRunner() { return mRunner; } + +#ifdef LL_WINDOWS + virtual void reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { } +#endif + +public: + typedef std::map<std::string, std::string> string_map; + string_map mOptionMap; // Contains all command-line options and arguments in a map + +protected: + + static void setStatus(EAppStatus status); // Use this to change the application status. + static LLScalarCond<EAppStatus> sStatus; // Reflects current application status + static bool sDisableCrashlogger; // Let the OS handle crashes for us. + std::wstring mCrashReportPipeStr; //Name of pipe to use for crash reporting. + + std::string mDumpPath; //output path for google breakpad. Dependency workaround. + + /** + * @brief This method is called once a frame to do once a frame tasks. + */ + void stepFrame(); + +private: + // Contains the filename of the minidump file after a crash. + char mMinidumpPath[MAX_MINDUMP_PATH_LENGTH]; + + std::string mStaticDebugFileName; + std::string mDynamicDebugFileName; + + // *NOTE: On Windows, we need a routine to reset the structured + // exception handler when some evil driver has taken it over for + // their own purposes + typedef int(*signal_handler_func)(int signum); + static LLAppErrorHandler sErrorHandler; + + // This is the application level runnable scheduler. + LLRunner mRunner; + + /** @name Runtime option implementation */ + //@{ + + // The application options. + LLSD mOptions; + + // The live files for this application + std::vector<LLLiveFile*> mLiveFiles; + //@} + +private: + // the static application instance if it was created. + static LLApp* sApplication; + +#if !LL_WINDOWS + friend void default_unix_signal_handler(int signum, siginfo_t *info, void *); +#endif + +public: + static bool sLogInSignal; +}; + +#endif // LL_LLAPP_H diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index 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 <sys/param.h> // Need PATH_MAX in APR headers...
-#endif
-
-#include <boost/noncopyable.hpp>
-#include "llwin32headerslean.h"
-#include "apr_thread_proc.h"
-#include "apr_getopt.h"
-#include "apr_signal.h"
-
-#include "llstring.h"
-
-#include "mutex.h"
-
-struct apr_dso_handle_t;
-/**
- * @brief Function which appropriately logs error or remains quiet on
- * APR_SUCCESS.
- * @return Returns <code>true</code> if status is an error condition.
- */
-#define ll_apr_warn_status(status) _ll_apr_warn_status(status, __FILE__, __LINE__)
-bool LL_COMMON_API _ll_apr_warn_status(apr_status_t status, const char* file, int line);
-
-#define ll_apr_assert_status(status) _ll_apr_assert_status(status, __FILE__, __LINE__)
-void LL_COMMON_API _ll_apr_assert_status(apr_status_t status, const char* file, int line);
-
-extern "C" LL_COMMON_API apr_pool_t* gAPRPoolp; // Global APR memory pool
-
-/**
- * @brief initialize the common apr constructs -- apr itself, the
- * global pool, and a mutex.
- */
-void LL_COMMON_API ll_init_apr();
-
-/**
- * @brief Cleanup those common apr constructs.
- */
-void LL_COMMON_API ll_cleanup_apr();
-
-bool LL_COMMON_API ll_apr_is_initialized();
-
-
-//
-//LL apr_pool
-//manage apr_pool_t, destroy allocated apr_pool in the destruction function.
-//
-class LL_COMMON_API LLAPRPool
-{
-public:
- LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true) ;
- virtual ~LLAPRPool() ;
-
- virtual apr_pool_t* getAPRPool() ;
- apr_status_t getStatus() {return mStatus ; }
-
-protected:
- void releaseAPRPool() ;
- void createAPRPool() ;
-
-protected:
- apr_pool_t* mPool ; //pointing to an apr_pool
- apr_pool_t* mParent ; //parent pool
- apr_size_t mMaxSize ; //max size of mPool, mPool should return memory to system if allocated memory beyond this limit. However it seems not to work.
- apr_status_t mStatus ; //status when creating the pool
- bool mReleasePoolFlag ; //if set, mPool is destroyed when LLAPRPool is deleted. default value is true.
-};
-
-//
-//volatile LL apr_pool
-//which clears memory automatically.
-//so it can not hold static data or data after memory is cleared
-//
-class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool
-{
-public:
- LLVolatileAPRPool(bool is_local = true, apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true);
- virtual ~LLVolatileAPRPool();
-
- /*virtual*/ apr_pool_t* getAPRPool() ; //define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool().
- apr_pool_t* getVolatileAPRPool() ;
- void clearVolatileAPRPool() ;
-
- bool isFull() ;
-
-private:
- S32 mNumActiveRef ; //number of active pointers pointing to the apr_pool.
- S32 mNumTotalRef ; //number of total pointers pointing to the apr_pool since last creating.
-
- std::unique_ptr<std::mutex> mMutexp;
-} ;
-
-// File IO convenience functions.
-// Returns NULL if the file fails to open, sets *sizep to file size if not NULL
-// abbreviated flags
-#define LL_APR_R (APR_READ) // "r"
-#define LL_APR_W (APR_CREATE|APR_TRUNCATE|APR_WRITE) // "w"
-#define LL_APR_A (APR_CREATE|APR_WRITE|APR_APPEND) // "w"
-#define LL_APR_RB (APR_READ|APR_BINARY) // "rb"
-#define LL_APR_WB (APR_CREATE|APR_TRUNCATE|APR_WRITE|APR_BINARY) // "wb"
-#define LL_APR_AB (APR_CREATE|APR_WRITE|APR_BINARY|APR_APPEND)
-#define LL_APR_RPB (APR_READ|APR_WRITE|APR_BINARY) // "r+b"
-#define LL_APR_WPB (APR_CREATE|APR_TRUNCATE|APR_READ|APR_WRITE|APR_BINARY) // "w+b"
-
-//
-//apr_file manager
-//which: 1)only keeps one file open;
-// 2)closes the open file in the destruction function
-// 3)informs the apr_pool to clean the memory when the file is closed.
-//Note: please close an open file at the earliest convenience.
-// especially do not put some time-costly operations between open() and close().
-// otherwise it might lock the APRFilePool.
-//there are two different apr_pools the APRFile can use:
-// 1, a temporary pool passed to an APRFile function, which is used within this function and only once.
-// 2, a global pool.
-//
-
-class LL_COMMON_API LLAPRFile : boost::noncopyable
-{
- // make this non copyable since a copy closes the file
-private:
- apr_file_t* mFile ;
- LLVolatileAPRPool *mCurrentFilePoolp ; //currently in use apr_pool, could be one of them: sAPRFilePoolp, or a temp pool.
-
-public:
- LLAPRFile() ;
- LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL);
- ~LLAPRFile() ;
-
- apr_status_t open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL, S32* sizep = NULL);
- apr_status_t open(const std::string& filename, apr_int32_t flags, bool use_global_pool); //use gAPRPoolp.
- apr_status_t close() ;
-
- // Returns actual offset, -1 if seek fails
- S32 seek(apr_seek_where_t where, S32 offset);
- apr_status_t eof() { return apr_file_eof(mFile);}
-
- // Returns bytes read/written, 0 if read/write fails:
- S32 read(void* buf, S32 nbytes);
- S32 write(const void* buf, S32 nbytes);
-
- apr_file_t* getFileHandle() {return mFile;}
-
-//
-//*******************************************************************************************************************************
-//static components
-//
-public:
- static LLVolatileAPRPool *sAPRFilePoolp ; //a global apr_pool for APRFile, which is used only when local pool does not exist.
-
-private:
- static apr_file_t* open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags);
- static apr_status_t close(apr_file_t* file) ;
- static S32 seek(apr_file_t* file, apr_seek_where_t where, S32 offset);
-public:
- // returns false if failure:
- static bool remove(const std::string& filename, LLVolatileAPRPool* pool = NULL);
- static bool rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool = NULL);
- static bool isExist(const std::string& filename, LLVolatileAPRPool* pool = NULL, apr_int32_t flags = APR_READ);
- static S32 size(const std::string& filename, LLVolatileAPRPool* pool = NULL);
- static bool makeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL);
- static bool removeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL);
-
- // Returns bytes read/written, 0 if read/write fails:
- static S32 readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL);
- static S32 writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); // offset<0 means append
-//*******************************************************************************************************************************
-};
-
-
-#endif // LL_LLAPR_H
+/** + * @file llapr.h + * @author Phoenix + * @date 2004-11-28 + * @brief Helper functions for using the apache portable runtime library. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAPR_H +#define LL_LLAPR_H + +#if LL_LINUX +#include <sys/param.h> // Need PATH_MAX in APR headers... +#endif + +#include <boost/noncopyable.hpp> +#include "llwin32headerslean.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_signal.h" + +#include "llstring.h" + +#include "mutex.h" + +struct apr_dso_handle_t; +/** + * @brief Function which appropriately logs error or remains quiet on + * APR_SUCCESS. + * @return Returns <code>true</code> if status is an error condition. + */ +#define ll_apr_warn_status(status) _ll_apr_warn_status(status, __FILE__, __LINE__) +bool LL_COMMON_API _ll_apr_warn_status(apr_status_t status, const char* file, int line); + +#define ll_apr_assert_status(status) _ll_apr_assert_status(status, __FILE__, __LINE__) +void LL_COMMON_API _ll_apr_assert_status(apr_status_t status, const char* file, int line); + +extern "C" LL_COMMON_API apr_pool_t* gAPRPoolp; // Global APR memory pool + +/** + * @brief initialize the common apr constructs -- apr itself, the + * global pool, and a mutex. + */ +void LL_COMMON_API ll_init_apr(); + +/** + * @brief Cleanup those common apr constructs. + */ +void LL_COMMON_API ll_cleanup_apr(); + +bool LL_COMMON_API ll_apr_is_initialized(); + + +// +//LL apr_pool +//manage apr_pool_t, destroy allocated apr_pool in the destruction function. +// +class LL_COMMON_API LLAPRPool +{ +public: + LLAPRPool(apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true) ; + virtual ~LLAPRPool() ; + + virtual apr_pool_t* getAPRPool() ; + apr_status_t getStatus() {return mStatus ; } + +protected: + void releaseAPRPool() ; + void createAPRPool() ; + +protected: + apr_pool_t* mPool ; //pointing to an apr_pool + apr_pool_t* mParent ; //parent pool + apr_size_t mMaxSize ; //max size of mPool, mPool should return memory to system if allocated memory beyond this limit. However it seems not to work. + apr_status_t mStatus ; //status when creating the pool + bool mReleasePoolFlag ; //if set, mPool is destroyed when LLAPRPool is deleted. default value is true. +}; + +// +//volatile LL apr_pool +//which clears memory automatically. +//so it can not hold static data or data after memory is cleared +// +class LL_COMMON_API LLVolatileAPRPool : public LLAPRPool +{ +public: + LLVolatileAPRPool(bool is_local = true, apr_pool_t *parent = NULL, apr_size_t size = 0, bool releasePoolFlag = true); + virtual ~LLVolatileAPRPool(); + + /*virtual*/ apr_pool_t* getAPRPool() ; //define this virtual function to avoid any mistakenly calling LLAPRPool::getAPRPool(). + apr_pool_t* getVolatileAPRPool() ; + void clearVolatileAPRPool() ; + + bool isFull() ; + +private: + S32 mNumActiveRef ; //number of active pointers pointing to the apr_pool. + S32 mNumTotalRef ; //number of total pointers pointing to the apr_pool since last creating. + + std::unique_ptr<std::mutex> mMutexp; +} ; + +// File IO convenience functions. +// Returns NULL if the file fails to open, sets *sizep to file size if not NULL +// abbreviated flags +#define LL_APR_R (APR_READ) // "r" +#define LL_APR_W (APR_CREATE|APR_TRUNCATE|APR_WRITE) // "w" +#define LL_APR_A (APR_CREATE|APR_WRITE|APR_APPEND) // "w" +#define LL_APR_RB (APR_READ|APR_BINARY) // "rb" +#define LL_APR_WB (APR_CREATE|APR_TRUNCATE|APR_WRITE|APR_BINARY) // "wb" +#define LL_APR_AB (APR_CREATE|APR_WRITE|APR_BINARY|APR_APPEND) +#define LL_APR_RPB (APR_READ|APR_WRITE|APR_BINARY) // "r+b" +#define LL_APR_WPB (APR_CREATE|APR_TRUNCATE|APR_READ|APR_WRITE|APR_BINARY) // "w+b" + +// +//apr_file manager +//which: 1)only keeps one file open; +// 2)closes the open file in the destruction function +// 3)informs the apr_pool to clean the memory when the file is closed. +//Note: please close an open file at the earliest convenience. +// especially do not put some time-costly operations between open() and close(). +// otherwise it might lock the APRFilePool. +//there are two different apr_pools the APRFile can use: +// 1, a temporary pool passed to an APRFile function, which is used within this function and only once. +// 2, a global pool. +// + +class LL_COMMON_API LLAPRFile : boost::noncopyable +{ + // make this non copyable since a copy closes the file +private: + apr_file_t* mFile ; + LLVolatileAPRPool *mCurrentFilePoolp ; //currently in use apr_pool, could be one of them: sAPRFilePoolp, or a temp pool. + +public: + LLAPRFile() ; + LLAPRFile(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL); + ~LLAPRFile() ; + + apr_status_t open(const std::string& filename, apr_int32_t flags, LLVolatileAPRPool* pool = NULL, S32* sizep = NULL); + apr_status_t open(const std::string& filename, apr_int32_t flags, bool use_global_pool); //use gAPRPoolp. + apr_status_t close() ; + + // Returns actual offset, -1 if seek fails + S32 seek(apr_seek_where_t where, S32 offset); + apr_status_t eof() { return apr_file_eof(mFile);} + + // Returns bytes read/written, 0 if read/write fails: + S32 read(void* buf, S32 nbytes); + S32 write(const void* buf, S32 nbytes); + + apr_file_t* getFileHandle() {return mFile;} + +// +//******************************************************************************************************************************* +//static components +// +public: + static LLVolatileAPRPool *sAPRFilePoolp ; //a global apr_pool for APRFile, which is used only when local pool does not exist. + +private: + static apr_file_t* open(const std::string& filename, apr_pool_t* apr_pool, apr_int32_t flags); + static apr_status_t close(apr_file_t* file) ; + static S32 seek(apr_file_t* file, apr_seek_where_t where, S32 offset); +public: + // returns false if failure: + static bool remove(const std::string& filename, LLVolatileAPRPool* pool = NULL); + static bool rename(const std::string& filename, const std::string& newname, LLVolatileAPRPool* pool = NULL); + static bool isExist(const std::string& filename, LLVolatileAPRPool* pool = NULL, apr_int32_t flags = APR_READ); + static S32 size(const std::string& filename, LLVolatileAPRPool* pool = NULL); + static bool makeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); + static bool removeDir(const std::string& dirname, LLVolatileAPRPool* pool = NULL); + + // Returns bytes read/written, 0 if read/write fails: + static S32 readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); + static S32 writeEx(const std::string& filename, const void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool = NULL); // offset<0 means append +//******************************************************************************************************************************* +}; + + +#endif // LL_LLAPR_H diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 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<LLAssetDictionary>,
- public LLDictionary<LLAssetType::EType, AssetEntry>
-{
- LLSINGLETON(LLAssetDictionary);
-};
-
-LLAssetDictionary::LLAssetDictionary()
-{
- // DESCRIPTION TYPE NAME HUMAN NAME CAN LINK? CAN FETCH? CAN KNOW?
- // |--------------------|-----------|-------------------|-----------|-----------|---------|
- addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", true, false, true));
- addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", true, true, true));
- addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", true, false, false));
- addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", true, true, true));
- addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", true, false, false));
- addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", true, true, true));
- addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", true, false, false));
- addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", true, false, true));
- addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", true, false, false));
- addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", true, false, false));
- addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", true, false, false));
- addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", true, false, false));
- addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", true, true, true));
- addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", true, false, false));
- addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", true, false, false));
- addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", true, false, false));
- addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", true, true, true));
- addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", true, true, true));
- addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", false, false, false));
-
- addEntry(LLAssetType::AT_LINK, new AssetEntry("LINK", "link", "sym link", false, false, true));
- addEntry(LLAssetType::AT_LINK_FOLDER, new AssetEntry("FOLDER_LINK", "link_f", "sym folder link", false, false, true));
- addEntry(LLAssetType::AT_MESH, new AssetEntry("MESH", "mesh", "mesh", false, false, false));
- addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false));
- addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false));
- addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true));
- addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true));
- addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false));
- addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, false, false, false));
-
-};
-
-const std::string LLAssetType::BADLOOKUP("llassettype_bad_lookup");
-
-// static
-LLAssetType::EType LLAssetType::getType(const std::string& desc_name)
-{
- std::string s = desc_name;
- LLStringUtil::toUpper(s);
- return LLAssetDictionary::getInstance()->lookup(s);
-}
-
-// static
-const std::string &LLAssetType::getDesc(LLAssetType::EType asset_type)
-{
- const AssetEntry *entry = LLAssetDictionary::getInstance()->lookup(asset_type);
- if (entry)
- {
- return entry->mName;
- }
- else
- {
- return BADLOOKUP;
- }
-}
-
-// static
-const char *LLAssetType::lookup(LLAssetType::EType asset_type)
-{
- const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
- const AssetEntry *entry = dict->lookup(asset_type);
- if (entry)
- {
- return entry->mTypeName;
- }
- else
- {
- return BADLOOKUP.c_str();
- }
-}
-
-// static
-LLAssetType::EType LLAssetType::lookup(const char* name)
-{
- return lookup(ll_safe_string(name));
-}
-
-// static
-LLAssetType::EType LLAssetType::lookup(const std::string& type_name)
-{
- const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
- for (const LLAssetDictionary::value_type& pair : *dict)
- {
- const AssetEntry *entry = pair.second;
- if (type_name == entry->mTypeName)
- {
- return pair.first;
- }
- }
- return AT_UNKNOWN;
-}
-
-// static
-const char *LLAssetType::lookupHumanReadable(LLAssetType::EType asset_type)
-{
- const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
- const AssetEntry *entry = dict->lookup(asset_type);
- if (entry)
- {
- return entry->mHumanName;
- }
- else
- {
- return BADLOOKUP.c_str();
- }
-}
-
-// static
-LLAssetType::EType LLAssetType::lookupHumanReadable(const char* name)
-{
- return lookupHumanReadable(ll_safe_string(name));
-}
-
-// static
-LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_name)
-{
- const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
- for (const LLAssetDictionary::value_type& pair : *dict)
- {
- const AssetEntry *entry = pair.second;
- if (entry->mHumanName && (readable_name == entry->mHumanName))
- {
- return pair.first;
- }
- }
- return AT_NONE;
-}
-
-// static
-bool LLAssetType::lookupCanLink(EType asset_type)
-{
- const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
- const AssetEntry *entry = dict->lookup(asset_type);
- if (entry)
- {
- return entry->mCanLink;
- }
- return false;
-}
-
-// static
-// Not adding this to dictionary since we probably will only have these two types
-bool LLAssetType::lookupIsLinkType(EType asset_type)
-{
- if (asset_type == AT_LINK || asset_type == AT_LINK_FOLDER)
- {
- return true;
- }
- return false;
-}
-
-// static
-bool LLAssetType::lookupIsAssetFetchByIDAllowed(EType asset_type)
-{
- const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
- const AssetEntry *entry = dict->lookup(asset_type);
- if (entry)
- {
- return entry->mCanFetch;
- }
- return false;
-}
-
-// static
-bool LLAssetType::lookupIsAssetIDKnowable(EType asset_type)
-{
- const LLAssetDictionary *dict = LLAssetDictionary::getInstance();
- const AssetEntry *entry = dict->lookup(asset_type);
- if (entry)
- {
- return entry->mCanKnow;
- }
- return false;
-}
+/** + * @file llassettype.cpp + * @brief Implementatino of LLAssetType functionality. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llassettype.h" +#include "lldictionary.h" +#include "llmemory.h" +#include "llsingleton.h" + +///---------------------------------------------------------------------------- +/// Class LLAssetType +///---------------------------------------------------------------------------- +struct AssetEntry : public LLDictionaryEntry +{ + AssetEntry(const char *desc_name, + const char *type_name, // 8 character limit! + const char *human_name, // for decoding to human readable form; put any and as many printable characters you want in each one + bool can_link, // can you create a link to this type? + bool can_fetch, // can you fetch this asset by ID? + bool can_know) // can you see this asset's ID? + : + LLDictionaryEntry(desc_name), + mTypeName(type_name), + mHumanName(human_name), + mCanLink(can_link), + mCanFetch(can_fetch), + mCanKnow(can_know) + { + llassert(strlen(mTypeName) <= 8); + } + + const char *mTypeName; + const char *mHumanName; + bool mCanLink; + bool mCanFetch; + bool mCanKnow; +}; + +class LLAssetDictionary : public LLSingleton<LLAssetDictionary>, + public LLDictionary<LLAssetType::EType, AssetEntry> +{ + LLSINGLETON(LLAssetDictionary); +}; + +LLAssetDictionary::LLAssetDictionary() +{ + // DESCRIPTION TYPE NAME HUMAN NAME CAN LINK? CAN FETCH? CAN KNOW? + // |--------------------|-----------|-------------------|-----------|-----------|---------| + addEntry(LLAssetType::AT_TEXTURE, new AssetEntry("TEXTURE", "texture", "texture", true, false, true)); + addEntry(LLAssetType::AT_SOUND, new AssetEntry("SOUND", "sound", "sound", true, true, true)); + addEntry(LLAssetType::AT_CALLINGCARD, new AssetEntry("CALLINGCARD", "callcard", "calling card", true, false, false)); + addEntry(LLAssetType::AT_LANDMARK, new AssetEntry("LANDMARK", "landmark", "landmark", true, true, true)); + addEntry(LLAssetType::AT_SCRIPT, new AssetEntry("SCRIPT", "script", "legacy script", true, false, false)); + addEntry(LLAssetType::AT_CLOTHING, new AssetEntry("CLOTHING", "clothing", "clothing", true, true, true)); + addEntry(LLAssetType::AT_OBJECT, new AssetEntry("OBJECT", "object", "object", true, false, false)); + addEntry(LLAssetType::AT_NOTECARD, new AssetEntry("NOTECARD", "notecard", "note card", true, false, true)); + addEntry(LLAssetType::AT_CATEGORY, new AssetEntry("CATEGORY", "category", "folder", true, false, false)); + addEntry(LLAssetType::AT_LSL_TEXT, new AssetEntry("LSL_TEXT", "lsltext", "lsl2 script", true, false, false)); + addEntry(LLAssetType::AT_LSL_BYTECODE, new AssetEntry("LSL_BYTECODE", "lslbyte", "lsl bytecode", true, false, false)); + addEntry(LLAssetType::AT_TEXTURE_TGA, new AssetEntry("TEXTURE_TGA", "txtr_tga", "tga texture", true, false, false)); + addEntry(LLAssetType::AT_BODYPART, new AssetEntry("BODYPART", "bodypart", "body part", true, true, true)); + addEntry(LLAssetType::AT_SOUND_WAV, new AssetEntry("SOUND_WAV", "snd_wav", "sound", true, false, false)); + addEntry(LLAssetType::AT_IMAGE_TGA, new AssetEntry("IMAGE_TGA", "img_tga", "targa image", true, false, false)); + addEntry(LLAssetType::AT_IMAGE_JPEG, new AssetEntry("IMAGE_JPEG", "jpeg", "jpeg image", true, false, false)); + addEntry(LLAssetType::AT_ANIMATION, new AssetEntry("ANIMATION", "animatn", "animation", true, true, true)); + addEntry(LLAssetType::AT_GESTURE, new AssetEntry("GESTURE", "gesture", "gesture", true, true, true)); + addEntry(LLAssetType::AT_SIMSTATE, new AssetEntry("SIMSTATE", "simstate", "simstate", false, false, false)); + + addEntry(LLAssetType::AT_LINK, new AssetEntry("LINK", "link", "sym link", false, false, true)); + addEntry(LLAssetType::AT_LINK_FOLDER, new AssetEntry("FOLDER_LINK", "link_f", "sym folder link", false, false, true)); + addEntry(LLAssetType::AT_MESH, new AssetEntry("MESH", "mesh", "mesh", false, false, false)); + addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false)); + addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false)); + addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true)); + addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true)); + addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); + addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, false, false, false)); + +}; + +const std::string LLAssetType::BADLOOKUP("llassettype_bad_lookup"); + +// static +LLAssetType::EType LLAssetType::getType(const std::string& desc_name) +{ + std::string s = desc_name; + LLStringUtil::toUpper(s); + return LLAssetDictionary::getInstance()->lookup(s); +} + +// static +const std::string &LLAssetType::getDesc(LLAssetType::EType asset_type) +{ + const AssetEntry *entry = LLAssetDictionary::getInstance()->lookup(asset_type); + if (entry) + { + return entry->mName; + } + else + { + return BADLOOKUP; + } +} + +// static +const char *LLAssetType::lookup(LLAssetType::EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mTypeName; + } + else + { + return BADLOOKUP.c_str(); + } +} + +// static +LLAssetType::EType LLAssetType::lookup(const char* name) +{ + return lookup(ll_safe_string(name)); +} + +// static +LLAssetType::EType LLAssetType::lookup(const std::string& type_name) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + for (const LLAssetDictionary::value_type& pair : *dict) + { + const AssetEntry *entry = pair.second; + if (type_name == entry->mTypeName) + { + return pair.first; + } + } + return AT_UNKNOWN; +} + +// static +const char *LLAssetType::lookupHumanReadable(LLAssetType::EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mHumanName; + } + else + { + return BADLOOKUP.c_str(); + } +} + +// static +LLAssetType::EType LLAssetType::lookupHumanReadable(const char* name) +{ + return lookupHumanReadable(ll_safe_string(name)); +} + +// static +LLAssetType::EType LLAssetType::lookupHumanReadable(const std::string& readable_name) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + for (const LLAssetDictionary::value_type& pair : *dict) + { + const AssetEntry *entry = pair.second; + if (entry->mHumanName && (readable_name == entry->mHumanName)) + { + return pair.first; + } + } + return AT_NONE; +} + +// static +bool LLAssetType::lookupCanLink(EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mCanLink; + } + return false; +} + +// static +// Not adding this to dictionary since we probably will only have these two types +bool LLAssetType::lookupIsLinkType(EType asset_type) +{ + if (asset_type == AT_LINK || asset_type == AT_LINK_FOLDER) + { + return true; + } + return false; +} + +// static +bool LLAssetType::lookupIsAssetFetchByIDAllowed(EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mCanFetch; + } + return false; +} + +// static +bool LLAssetType::lookupIsAssetIDKnowable(EType asset_type) +{ + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + const AssetEntry *entry = dict->lookup(asset_type); + if (entry) + { + return entry->mCanKnow; + } + return false; +} diff --git a/indra/llcommon/llbase64.cpp b/indra/llcommon/llbase64.cpp index 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 <string>
-
-#include "apr_base64.h"
-
-
-// static
-std::string LLBase64::encode(const U8* input, size_t input_size)
-{
- std::string output;
- if (input
- && input_size > 0)
- {
- // Yes, it returns int.
- int b64_buffer_length = apr_base64_encode_len(narrow<size_t>(input_size));
- char* b64_buffer = new char[b64_buffer_length];
-
- // This is faster than apr_base64_encode() if you know
- // you're not on an EBCDIC machine. Also, the output is
- // null terminated, even though the documentation doesn't
- // specify. See apr_base64.c for details. JC
- b64_buffer_length = apr_base64_encode_binary(
- b64_buffer,
- input,
- narrow<size_t>(input_size));
- output.assign(b64_buffer);
- delete[] b64_buffer;
- }
- return output;
-}
-
-std::string LLBase64::decodeAsString(const std::string &input)
-{
- int b64_buffer_length = apr_base64_decode_len(input.c_str());
- char* b64_buffer = new char[b64_buffer_length];
-
- // This is faster than apr_base64_encode() if you know
- // you're not on an EBCDIC machine. Also, the output is
- // null terminated, even though the documentation doesn't
- // specify. See apr_base64.c for details. JC
- b64_buffer_length = apr_base64_decode(b64_buffer, input.c_str());
- std::string res;
- res.assign(b64_buffer);
- delete[] b64_buffer;
- return res;
-}
-
+/** + * @file llbase64.cpp + * @brief Wrapper for apr base64 encoding that returns a std::string + * @author James Cook + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llbase64.h" + +#include <string> + +#include "apr_base64.h" + + +// static +std::string LLBase64::encode(const U8* input, size_t input_size) +{ + std::string output; + if (input + && input_size > 0) + { + // Yes, it returns int. + int b64_buffer_length = apr_base64_encode_len(narrow<size_t>(input_size)); + char* b64_buffer = new char[b64_buffer_length]; + + // This is faster than apr_base64_encode() if you know + // you're not on an EBCDIC machine. Also, the output is + // null terminated, even though the documentation doesn't + // specify. See apr_base64.c for details. JC + b64_buffer_length = apr_base64_encode_binary( + b64_buffer, + input, + narrow<size_t>(input_size)); + output.assign(b64_buffer); + delete[] b64_buffer; + } + return output; +} + +std::string LLBase64::decodeAsString(const std::string &input) +{ + int b64_buffer_length = apr_base64_decode_len(input.c_str()); + char* b64_buffer = new char[b64_buffer_length]; + + // This is faster than apr_base64_encode() if you know + // you're not on an EBCDIC machine. Also, the output is + // null terminated, even though the documentation doesn't + // specify. See apr_base64.c for details. JC + b64_buffer_length = apr_base64_decode(b64_buffer, input.c_str()); + std::string res; + res.assign(b64_buffer); + delete[] b64_buffer; + return res; +} + diff --git a/indra/llcommon/llbase64.h b/indra/llcommon/llbase64.h index 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<OnIdleCallbackOneTime*>(data);
- self->call();
- delete self;
- }
- void call()
- {
- mCallable();
- }
-private:
- nullary_func_t mCallable;
-};
-
-void doOnIdleOneTime(nullary_func_t callable)
-{
- OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable);
- gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor);
-}
-
-// Shim class to allow generic boost functions to be run as
-// recurring idle callbacks. Callable should return true when done,
-// false to continue getting called.
-class OnIdleCallbackRepeating
-{
-public:
- OnIdleCallbackRepeating(bool_func_t callable):
- mCallable(callable)
- {
- }
- // Will keep getting called until the callable returns true.
- static void onIdle(void *data)
- {
- OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data);
- bool done = self->call();
- if (done)
- {
- gIdleCallbacks.deleteFunction(onIdle, data);
- delete self;
- }
- }
- bool call()
- {
- return mCallable();
- }
-private:
- bool_func_t mCallable;
-};
-
-void doOnIdleRepeating(bool_func_t callable)
-{
- OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable);
- gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor);
-}
-
-class NullaryFuncEventTimer: public LLEventTimer
-{
-public:
- NullaryFuncEventTimer(nullary_func_t callable, F32 seconds):
- LLEventTimer(seconds),
- mCallable(callable)
- {
- }
-
-private:
- bool tick()
- {
- mCallable();
- return true;
- }
-
- nullary_func_t mCallable;
-};
-
-// Call a given callable once after specified interval.
-void doAfterInterval(nullary_func_t callable, F32 seconds)
-{
- new NullaryFuncEventTimer(callable, seconds);
-}
-
-class BoolFuncEventTimer: public LLEventTimer
-{
-public:
- BoolFuncEventTimer(bool_func_t callable, F32 seconds):
- LLEventTimer(seconds),
- mCallable(callable)
- {
- }
-private:
- bool tick()
- {
- return mCallable();
- }
-
- bool_func_t mCallable;
-};
-
-// Call a given callable every specified number of seconds, until it returns true.
-void doPeriodically(bool_func_t callable, F32 seconds)
-{
- new BoolFuncEventTimer(callable, seconds);
-}
+/** + * @file llcallbacklist.cpp + * @brief A simple list of callback functions to call. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llcallbacklist.h" +#include "lleventtimer.h" +#include "llerrorlegacy.h" + +// Globals +// +LLCallbackList gIdleCallbacks; + +// +// Member functions +// + +LLCallbackList::LLCallbackList() +{ + // nothing +} + +LLCallbackList::~LLCallbackList() +{ +} + + +void LLCallbackList::addFunction( callback_t func, void *data) +{ + if (!func) + { + return; + } + + // only add one callback per func/data pair + // + if (containsFunction(func, data)) + { + return; + } + + callback_pair_t t(func, data); + mCallbackList.push_back(t); +} + +bool LLCallbackList::containsFunction( callback_t func, void *data) +{ + callback_pair_t t(func, data); + callback_list_t::iterator iter = find(func,data); + if (iter != mCallbackList.end()) + { + return true; + } + else + { + return false; + } +} + + +bool LLCallbackList::deleteFunction( callback_t func, void *data) +{ + callback_list_t::iterator iter = find(func,data); + if (iter != mCallbackList.end()) + { + mCallbackList.erase(iter); + return true; + } + else + { + return false; + } +} + +inline +LLCallbackList::callback_list_t::iterator +LLCallbackList::find(callback_t func, void *data) +{ + callback_pair_t t(func, data); + return std::find(mCallbackList.begin(), mCallbackList.end(), t); +} + +void LLCallbackList::deleteAllFunctions() +{ + mCallbackList.clear(); +} + + +void LLCallbackList::callFunctions() +{ + for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) + { + callback_list_t::iterator curiter = iter++; + curiter->first(curiter->second); + } +} + +// Shim class to allow arbitrary boost::bind +// expressions to be run as one-time idle callbacks. +class OnIdleCallbackOneTime +{ +public: + OnIdleCallbackOneTime(nullary_func_t callable): + mCallable(callable) + { + } + static void onIdle(void *data) + { + gIdleCallbacks.deleteFunction(onIdle, data); + OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data); + self->call(); + delete self; + } + void call() + { + mCallable(); + } +private: + nullary_func_t mCallable; +}; + +void doOnIdleOneTime(nullary_func_t callable) +{ + OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); + gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); +} + +// Shim class to allow generic boost functions to be run as +// recurring idle callbacks. Callable should return true when done, +// false to continue getting called. +class OnIdleCallbackRepeating +{ +public: + OnIdleCallbackRepeating(bool_func_t callable): + mCallable(callable) + { + } + // Will keep getting called until the callable returns true. + static void onIdle(void *data) + { + OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data); + bool done = self->call(); + if (done) + { + gIdleCallbacks.deleteFunction(onIdle, data); + delete self; + } + } + bool call() + { + return mCallable(); + } +private: + bool_func_t mCallable; +}; + +void doOnIdleRepeating(bool_func_t callable) +{ + OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); + gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); +} + +class NullaryFuncEventTimer: public LLEventTimer +{ +public: + NullaryFuncEventTimer(nullary_func_t callable, F32 seconds): + LLEventTimer(seconds), + mCallable(callable) + { + } + +private: + bool tick() + { + mCallable(); + return true; + } + + nullary_func_t mCallable; +}; + +// Call a given callable once after specified interval. +void doAfterInterval(nullary_func_t callable, F32 seconds) +{ + new NullaryFuncEventTimer(callable, seconds); +} + +class BoolFuncEventTimer: public LLEventTimer +{ +public: + BoolFuncEventTimer(bool_func_t callable, F32 seconds): + LLEventTimer(seconds), + mCallable(callable) + { + } +private: + bool tick() + { + return mCallable(); + } + + bool_func_t mCallable; +}; + +// Call a given callable every specified number of seconds, until it returns true. +void doPeriodically(bool_func_t callable, F32 seconds) +{ + new BoolFuncEventTimer(callable, seconds); +} diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp index 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 <cctype>
-#ifdef __GNUC__
-# include <cxxabi.h>
-#endif // __GNUC__
-#include <sstream>
-#if !LL_WINDOWS
-# include <syslog.h>
-# include <unistd.h>
-# include <sys/stat.h>
-#else
-# include <io.h>
-#endif // !LL_WINDOWS
-#include <vector>
-#include "string.h"
-
-#include "llapp.h"
-#include "llapr.h"
-#include "llfile.h"
-#include "lllivefile.h"
-#include "llsd.h"
-#include "llsdserialize.h"
-#include "llsingleton.h"
-#include "llstl.h"
-#include "lltimer.h"
-
-// On Mac, got:
-// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define
-// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if
-// _Unwind_Backtrace is available without `_GNU_SOURCE`."
-#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
-#include <boost/stacktrace.hpp>
-
-namespace {
-#if LL_WINDOWS
- void debugger_print(const std::string& s)
- {
- // Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C
- // which works just fine under the windows debugger, but can cause users who
- // have enabled SEHOP exception chain validation to crash due to interactions
- // between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707
- //
- if (IsDebuggerPresent())
- {
- // Need UTF16 for Unicode OutputDebugString
- //
- if (s.size())
- {
- OutputDebugString(utf8str_to_utf16str(s).c_str());
- OutputDebugString(TEXT("\n"));
- }
- }
- }
-#else
- class RecordToSyslog : public LLError::Recorder
- {
- public:
- RecordToSyslog(const std::string& identity)
- : mIdentity(identity)
- {
- openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0);
- // we need to set the string from a local copy of the string
- // since apparanetly openlog expects the const char* to remain
- // valid even after it returns (presumably until closelog)
- }
-
- ~RecordToSyslog()
- {
- closelog();
- }
-
- virtual bool enabled() override
- {
- return LLError::getEnabledLogTypesMask() & 0x01;
- }
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- int syslogPriority = LOG_CRIT;
- switch (level) {
- case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break;
- case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break;
- case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break;
- case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break;
- default: syslogPriority = LOG_CRIT;
- }
-
- syslog(syslogPriority, "%s", message.c_str());
- }
- private:
- std::string mIdentity;
- };
-#endif
-
- class RecordToFile : public LLError::Recorder
- {
- public:
- RecordToFile(const std::string& filename):
- mName(filename)
- {
- mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app);
- if (!mFile)
- {
- LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
- }
- else
- {
- if (!LLError::getAlwaysFlush())
- {
- mFile.sync_with_stdio(false);
- }
- }
- }
-
- ~RecordToFile()
- {
- mFile.close();
- }
-
- virtual bool enabled() override
- {
-#ifdef LL_RELEASE_FOR_DOWNLOAD
- return 1;
-#else
- return LLError::getEnabledLogTypesMask() & 0x02;
-#endif
- }
-
- bool okay() const { return mFile.good(); }
-
- std::string getFilename() const { return mName; }
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- if (LLError::getAlwaysFlush())
- {
- mFile << message << std::endl;
- }
- else
- {
- mFile << message << "\n";
- }
- }
-
- private:
- const std::string mName;
- llofstream mFile;
- };
-
-
- class RecordToStderr : public LLError::Recorder
- {
- public:
- RecordToStderr(bool timestamp) : mUseANSI(checkANSI())
- {
- this->showMultiline(true);
- }
-
- virtual bool enabled() override
- {
- return LLError::getEnabledLogTypesMask() & 0x04;
- }
-
- LL_FORCE_INLINE std::string createBoldANSI()
- {
- std::string ansi_code;
- ansi_code += '\033';
- ansi_code += "[";
- ansi_code += "1";
- ansi_code += "m";
-
- return ansi_code;
- }
-
- LL_FORCE_INLINE std::string createResetANSI()
- {
- std::string ansi_code;
- ansi_code += '\033';
- ansi_code += "[";
- ansi_code += "0";
- ansi_code += "m";
-
- return ansi_code;
- }
-
- LL_FORCE_INLINE std::string createANSI(const std::string& color)
- {
- std::string ansi_code;
- ansi_code += '\033';
- ansi_code += "[";
- ansi_code += "38;5;";
- ansi_code += color;
- ansi_code += "m";
-
- return ansi_code;
- }
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- // The default colors for error, warn and debug are now a bit more pastel
- // and easier to read on the default (black) terminal background but you
- // now have the option to set the color of each via an environment variables:
- // LL_ANSI_ERROR_COLOR_CODE (default is red)
- // LL_ANSI_WARN_COLOR_CODE (default is blue)
- // LL_ANSI_DEBUG_COLOR_CODE (default is magenta)
- // The list of color codes can be found in many places but I used this page:
- // https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors
- // (Note: you may need to restart Visual Studio to pick environment changes)
- char* val = nullptr;
- std::string s_ansi_error_code = "160";
- if ((val = getenv("LL_ANSI_ERROR_COLOR_CODE")) != nullptr) s_ansi_error_code = std::string(val);
- std::string s_ansi_warn_code = "33";
- if ((val = getenv("LL_ANSI_WARN_COLOR_CODE")) != nullptr) s_ansi_warn_code = std::string(val);
- std::string s_ansi_debug_code = "177";
- if ((val = getenv("LL_ANSI_DEBUG_COLOR_CODE")) != nullptr) s_ansi_debug_code = std::string(val);
-
- static std::string s_ansi_error = createANSI(s_ansi_error_code); // default is red
- static std::string s_ansi_warn = createANSI(s_ansi_warn_code); // default is blue
- static std::string s_ansi_debug = createANSI(s_ansi_debug_code); // default is magenta
-
- if (mUseANSI)
- {
- writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error :
- (level == LLError::LEVEL_WARN) ? s_ansi_warn :
- s_ansi_debug, message);
- }
- else
- {
- LL_PROFILE_ZONE_NAMED("fprintf");
- fprintf(stderr, "%s\n", message.c_str());
- }
- }
-
- private:
- bool mUseANSI;
-
- LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message)
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- static std::string s_ansi_bold = createBoldANSI(); // bold text
- static std::string s_ansi_reset = createResetANSI(); // reset
- // ANSI color code escape sequence, message, and reset in one fprintf call
- // Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries.
- fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() );
- }
-
- static bool checkANSI(void)
- {
- // Check whether it's okay to use ANSI; if stderr is
- // a tty then we assume yes. Can be turned off with
- // the LL_NO_ANSI_COLOR env var.
- return (0 != isatty(2)) &&
- (NULL == getenv("LL_NO_ANSI_COLOR"));
- }
- };
-
- class RecordToFixedBuffer : public LLError::Recorder
- {
- public:
- RecordToFixedBuffer(LLLineBuffer* buffer)
- : mBuffer(buffer)
- {
- this->showMultiline(true);
- this->showTags(false);
- this->showLocation(false);
- }
-
- virtual bool enabled() override
- {
- return LLError::getEnabledLogTypesMask() & 0x08;
- }
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- mBuffer->addLine(message);
- }
-
- private:
- LLLineBuffer* mBuffer;
- };
-
-#if LL_WINDOWS
- class RecordToWinDebug: public LLError::Recorder
- {
- public:
- RecordToWinDebug()
- {
- this->showMultiline(true);
- this->showTags(false);
- this->showLocation(false);
- }
-
- virtual bool enabled() override
- {
- return LLError::getEnabledLogTypesMask() & 0x10;
- }
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- debugger_print(message);
- }
- };
-#endif
-}
-
-
-namespace
-{
- std::string className(const std::type_info& type)
- {
- return LLError::Log::demangle(type.name());
- }
-} // anonymous
-
-namespace LLError
-{
- std::string Log::demangle(const char* mangled)
- {
-#ifdef __GNUC__
- // GCC: type_info::name() returns a mangled class name,st demangle
- // passing nullptr, 0 forces allocation of a unique buffer we can free
- // fixing MAINT-8724 on OSX 10.14
- int status = -1;
- char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status);
- std::string result(name ? name : mangled);
- free(name);
- return result;
-
-#elif LL_WINDOWS
- // Visual Studio: type_info::name() includes the text "class " at the start
- std::string name = mangled;
- for (const auto& prefix : std::vector<std::string>{ "class ", "struct " })
- {
- if (0 == name.compare(0, prefix.length(), prefix))
- {
- return name.substr(prefix.length());
- }
- }
- // huh, that's odd, we should see one or the other prefix -- but don't
- // try to log unless logging is already initialized
- // in Python, " or ".join(vector) -- but in C++, a PITB
- LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '"
- << name << "'" << LL_ENDL;
- return name;
-
-#else // neither GCC nor Visual Studio
- return mangled;
-#endif
- }
-} // LLError
-
-namespace
-{
- std::string functionName(const std::string& preprocessor_name)
- {
-#if LL_WINDOWS
- // DevStudio: the __FUNCTION__ macro string includes
- // the type and/or namespace prefixes
-
- std::string::size_type p = preprocessor_name.rfind(':');
- if (p == std::string::npos)
- {
- return preprocessor_name;
- }
- return preprocessor_name.substr(p + 1);
-
-#else
- return preprocessor_name;
-#endif
- }
-
-
- class LogControlFile : public LLLiveFile
- {
- LOG_CLASS(LogControlFile);
-
- public:
- static LogControlFile& fromDirectory(const std::string& user_dir, const std::string& app_dir);
-
- virtual bool loadFile();
-
- private:
- LogControlFile(const std::string &filename)
- : LLLiveFile(filename)
- { }
- };
-
- LogControlFile& LogControlFile::fromDirectory(const std::string& user_dir, const std::string& app_dir)
- {
- // NB: We have no abstraction in llcommon for the "proper"
- // delimiter but it turns out that "/" works on all three platforms
-
- std::string file = user_dir + "/logcontrol-dev.xml";
-
- llstat stat_info;
- if (LLFile::stat(file, &stat_info)) {
- // NB: stat returns non-zero if it can't read the file, for example
- // if it doesn't exist. LLFile has no better abstraction for
- // testing for file existence.
-
- file = app_dir + "/logcontrol.xml";
- }
- return * new LogControlFile(file);
- // NB: This instance is never freed
- }
-
- bool LogControlFile::loadFile()
- {
- LLSD configuration;
-
- {
- llifstream file(filename().c_str());
- if (!file.is_open())
- {
- LL_WARNS() << filename() << " failed to open file; not changing configuration" << LL_ENDL;
- return false;
- }
-
- if (LLSDSerialize::fromXML(configuration, file) == LLSDParser::PARSE_FAILURE)
- {
- LL_WARNS() << filename() << " parcing error; not changing configuration" << LL_ENDL;
- return false;
- }
-
- if (! configuration || !configuration.isMap())
- {
- LL_WARNS() << filename() << " missing, ill-formed, or simply undefined"
- " content; not changing configuration"
- << LL_ENDL;
- return false;
- }
- }
-
- LLError::configure(configuration);
- LL_INFOS("LogControlFile") << "logging reconfigured from " << filename() << LL_ENDL;
- return true;
- }
-
-
- typedef std::map<std::string, LLError::ELevel> LevelMap;
- typedef std::vector<LLError::RecorderPtr> Recorders;
- typedef std::vector<LLError::CallSite*> CallSiteVector;
-
- class SettingsConfig : public LLRefCount
- {
- friend class Globals;
-
- public:
- virtual ~SettingsConfig();
-
- LLError::ELevel mDefaultLevel;
-
- bool mLogAlwaysFlush;
-
- U32 mEnabledLogTypesMask;
-
- LevelMap mFunctionLevelMap;
- LevelMap mClassLevelMap;
- LevelMap mFileLevelMap;
- LevelMap mTagLevelMap;
- std::map<std::string, unsigned int> mUniqueLogMessages;
-
- LLError::FatalFunction mCrashFunction;
- LLError::TimeFunction mTimeFunction;
-
- Recorders mRecorders;
- LLMutex mRecorderMutex;
-
- int mShouldLogCallCounter;
-
- private:
- SettingsConfig();
- };
-
- typedef LLPointer<SettingsConfig> SettingsConfigPtr;
-
- SettingsConfig::SettingsConfig()
- : LLRefCount(),
- mDefaultLevel(LLError::LEVEL_DEBUG),
- mLogAlwaysFlush(true),
- mEnabledLogTypesMask(255),
- mFunctionLevelMap(),
- mClassLevelMap(),
- mFileLevelMap(),
- mTagLevelMap(),
- mUniqueLogMessages(),
- mCrashFunction(NULL),
- mTimeFunction(NULL),
- mRecorders(),
- mRecorderMutex(),
- mShouldLogCallCounter(0)
- {
- }
-
- SettingsConfig::~SettingsConfig()
- {
- mRecorders.clear();
- }
-
- class Globals
- {
- public:
- static Globals* getInstance();
- protected:
- Globals();
- public:
- std::string mFatalMessage;
-
- void addCallSite(LLError::CallSite&);
- void invalidateCallSites();
-
- SettingsConfigPtr getSettingsConfig();
-
- void resetSettingsConfig();
- LLError::SettingsStoragePtr saveAndResetSettingsConfig();
- void restore(LLError::SettingsStoragePtr pSettingsStorage);
- private:
- CallSiteVector callSites;
- SettingsConfigPtr mSettingsConfig;
- };
-
- Globals::Globals()
- :
- callSites(),
- mSettingsConfig(new SettingsConfig())
- {
- }
-
-
- Globals* Globals::getInstance()
- {
- // According to C++11 Function-Local Initialization
- // of static variables is supposed to be thread safe
- // without risk of deadlocks.
- static Globals inst;
-
- return &inst;
- }
-
- void Globals::addCallSite(LLError::CallSite& site)
- {
- callSites.push_back(&site);
- }
-
- void Globals::invalidateCallSites()
- {
- for (LLError::CallSite* site : callSites)
- {
- site->invalidate();
- }
-
- callSites.clear();
- }
-
- SettingsConfigPtr Globals::getSettingsConfig()
- {
- return mSettingsConfig;
- }
-
- void Globals::resetSettingsConfig()
- {
- invalidateCallSites();
- mSettingsConfig = new SettingsConfig();
- }
-
- LLError::SettingsStoragePtr Globals::saveAndResetSettingsConfig()
- {
- LLError::SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get());
- resetSettingsConfig();
- return oldSettingsConfig;
- }
-
- void Globals::restore(LLError::SettingsStoragePtr pSettingsStorage)
- {
- invalidateCallSites();
- SettingsConfigPtr newSettingsConfig(dynamic_cast<SettingsConfig *>(pSettingsStorage.get()));
- mSettingsConfig = newSettingsConfig;
- }
-}
-
-namespace LLError
-{
- CallSite::CallSite(ELevel level,
- const char* file,
- int line,
- const std::type_info& class_info,
- const char* function,
- bool printOnce,
- const char** tags,
- size_t tag_count)
- : mLevel(level),
- mFile(file),
- mLine(line),
- mClassInfo(class_info),
- mFunction(function),
- mCached(false),
- mShouldLog(false),
- mPrintOnce(printOnce),
- mTags(new const char* [tag_count]),
- mTagCount(tag_count)
- {
- switch (mLevel)
- {
- case LEVEL_DEBUG: mLevelString = "DEBUG"; break;
- case LEVEL_INFO: mLevelString = "INFO"; break;
- case LEVEL_WARN: mLevelString = "WARNING"; break;
- case LEVEL_ERROR: mLevelString = "ERROR"; break;
- default: mLevelString = "XXX"; break;
- };
-
- mLocationString = llformat("%s(%d)", abbreviateFile(mFile).c_str(), mLine);
-#if LL_WINDOWS
- // DevStudio: __FUNCTION__ already includes the full class name
-#else
-#if LL_LINUX
- // gross, but typeid comparison seems to always fail here with gcc4.1
- if (0 != strcmp(mClassInfo.name(), typeid(NoClassInfo).name()))
-#else
- if (mClassInfo != typeid(NoClassInfo))
-#endif // LL_LINUX
- {
- mFunctionString = className(mClassInfo) + "::";
- }
-#endif
- mFunctionString += std::string(mFunction);
-
- for (int i = 0; i < tag_count; i++)
- {
- if (strchr(tags[i], ' '))
- {
- LL_ERRS() << "Space is not allowed in a log tag at " << mLocationString << LL_ENDL;
- }
- mTags[i] = tags[i];
- }
-
- mTagString.append("#");
- // always construct a tag sequence; will be just a single # if no tag
- for (size_t i = 0; i < mTagCount; i++)
- {
- mTagString.append(mTags[i]);
- mTagString.append("#");
- }
- }
-
- CallSite::~CallSite()
- {
- delete []mTags;
- }
-
- void CallSite::invalidate()
- {
- mCached = false;
- }
-}
-
-namespace
-{
- bool shouldLogToStderr()
- {
-#if LL_DARWIN
- // On macOS, stderr from apps launched from the Finder goes to the
- // console log. It's generally considered bad form to spam too much
- // there. That scenario can be detected by noticing that stderr is a
- // character device (S_IFCHR).
-
- // If stderr is a tty or a pipe, assume the user launched from the
- // command line or debugger and therefore wants to see stderr.
- if (isatty(STDERR_FILENO))
- return true;
- // not a tty, but might still be a pipe -- check
- struct stat st;
- if (fstat(STDERR_FILENO, &st) < 0)
- {
- // capture errno right away, before engaging any other operations
- auto errno_save = errno;
- // this gets called during log-system setup -- can't log yet!
- std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno "
- << errno_save << std::endl;
- // if we can't tell, err on the safe side and don't write stderr
- return false;
- }
-
- // fstat() worked: return true only if stderr is a pipe
- return ((st.st_mode & S_IFMT) == S_IFIFO);
-#else
- return true;
-#endif
- }
-
- bool stderrLogWantsTime()
- {
-#if LL_WINDOWS
- return false;
-#else
- return true;
-#endif
- }
-
-
- void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true)
- {
- Globals::getInstance()->resetSettingsConfig();
-
- LLError::setDefaultLevel(LLError::LEVEL_INFO);
- LLError::setAlwaysFlush(true);
- LLError::setEnabledLogTypesMask(0xFFFFFFFF);
- LLError::setTimeFunction(LLError::utcTime);
-
- // log_to_stderr is only false in the unit and integration tests to keep builds quieter
- if (log_to_stderr && shouldLogToStderr())
- {
- LLError::logToStderr();
- }
-
-#if LL_WINDOWS
- LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug());
- LLError::addRecorder(recordToWinDebug);
-#endif
-
- LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir);
-
- // NOTE: We want to explicitly load the file before we add it to the event timer
- // that checks for changes to the file. Else, we're not actually loading the file yet,
- // and most of the initialization happens without any attention being paid to the
- // log control file. Not to mention that when it finally gets checked later,
- // all log statements that have been evaluated already become dirty and need to be
- // evaluated for printing again. So, make sure to call checkAndReload()
- // before addToEventTimer().
- e.checkAndReload();
- e.addToEventTimer();
- }
-}
-
-namespace LLError
-{
- void initForApplication(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr)
- {
- commonInit(user_dir, app_dir, log_to_stderr);
- }
-
- void setFatalFunction(const FatalFunction& f)
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- s->mCrashFunction = f;
- }
-
- FatalFunction getFatalFunction()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mCrashFunction;
- }
-
- std::string getFatalMessage()
- {
- return Globals::getInstance()->mFatalMessage;
- }
-
- void setTimeFunction(TimeFunction f)
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- s->mTimeFunction = f;
- }
-
- void setDefaultLevel(ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mDefaultLevel = level;
- }
-
- ELevel getDefaultLevel()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mDefaultLevel;
- }
-
- void setAlwaysFlush(bool flush)
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- s->mLogAlwaysFlush = flush;
- }
-
- bool getAlwaysFlush()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mLogAlwaysFlush;
- }
-
- void setEnabledLogTypesMask(U32 mask)
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- s->mEnabledLogTypesMask = mask;
- }
-
- U32 getEnabledLogTypesMask()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mEnabledLogTypesMask;
- }
-
- void setFunctionLevel(const std::string& function_name, ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mFunctionLevelMap[function_name] = level;
- }
-
- void setClassLevel(const std::string& class_name, ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mClassLevelMap[class_name] = level;
- }
-
- void setFileLevel(const std::string& file_name, ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mFileLevelMap[file_name] = level;
- }
-
- void setTagLevel(const std::string& tag_name, ELevel level)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
- s->mTagLevelMap[tag_name] = level;
- }
-
- LLError::ELevel decodeLevel(std::string name)
- {
- static LevelMap level_names;
- if (level_names.empty())
- {
- level_names["ALL"] = LLError::LEVEL_ALL;
- level_names["DEBUG"] = LLError::LEVEL_DEBUG;
- level_names["INFO"] = LLError::LEVEL_INFO;
- level_names["WARN"] = LLError::LEVEL_WARN;
- level_names["ERROR"] = LLError::LEVEL_ERROR;
- level_names["NONE"] = LLError::LEVEL_NONE;
- }
-
- std::transform(name.begin(), name.end(), name.begin(), toupper);
-
- LevelMap::const_iterator i = level_names.find(name);
- if (i == level_names.end())
- {
- LL_WARNS() << "unrecognized logging level: '" << name << "'" << LL_ENDL;
- return LLError::LEVEL_INFO;
- }
-
- return i->second;
- }
-}
-
-namespace {
- void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level)
- {
- LLSD::array_const_iterator i, end;
- for (i = list.beginArray(), end = list.endArray(); i != end; ++i)
- {
- map[*i] = level;
- }
- }
-}
-
-namespace LLError
-{
- void configure(const LLSD& config)
- {
- Globals *g = Globals::getInstance();
- g->invalidateCallSites();
- SettingsConfigPtr s = g->getSettingsConfig();
-
- s->mFunctionLevelMap.clear();
- s->mClassLevelMap.clear();
- s->mFileLevelMap.clear();
- s->mTagLevelMap.clear();
- s->mUniqueLogMessages.clear();
-
- setDefaultLevel(decodeLevel(config["default-level"]));
- if (config.has("log-always-flush"))
- {
- setAlwaysFlush(config["log-always-flush"]);
- }
- if (config.has("enabled-log-types-mask"))
- {
- setEnabledLogTypesMask(config["enabled-log-types-mask"].asInteger());
- }
-
- if (config.has("settings") && config["settings"].isArray())
- {
- LLSD sets = config["settings"];
- LLSD::array_const_iterator a, end;
- for (a = sets.beginArray(), end = sets.endArray(); a != end; ++a)
- {
- const LLSD& entry = *a;
- if (entry.isMap() && entry.size() != 0)
- {
- ELevel level = decodeLevel(entry["level"]);
-
- setLevels(s->mFunctionLevelMap, entry["functions"], level);
- setLevels(s->mClassLevelMap, entry["classes"], level);
- setLevels(s->mFileLevelMap, entry["files"], level);
- setLevels(s->mTagLevelMap, entry["tags"], level);
- }
- }
- }
- }
-}
-
-
-namespace LLError
-{
- Recorder::Recorder()
- : mWantsTime(true)
- , mWantsTags(true)
- , mWantsLevel(true)
- , mWantsLocation(true)
- , mWantsFunctionName(true)
- , mWantsMultiline(false)
- {
- }
-
- Recorder::~Recorder()
- {
- }
-
- bool Recorder::wantsTime()
- {
- return mWantsTime;
- }
-
- // virtual
- bool Recorder::wantsTags()
- {
- return mWantsTags;
- }
-
- // virtual
- bool Recorder::wantsLevel()
- {
- return mWantsLevel;
- }
-
- // virtual
- bool Recorder::wantsLocation()
- {
- return mWantsLocation;
- }
-
- // virtual
- bool Recorder::wantsFunctionName()
- {
- return mWantsFunctionName;
- }
-
- // virtual
- bool Recorder::wantsMultiline()
- {
- return mWantsMultiline;
- }
-
- void Recorder::showTime(bool show)
- {
- mWantsTime = show;
- }
-
- void Recorder::showTags(bool show)
- {
- mWantsTags = show;
- }
-
- void Recorder::showLevel(bool show)
- {
- mWantsLevel = show;
- }
-
- void Recorder::showLocation(bool show)
- {
- mWantsLocation = show;
- }
-
- void Recorder::showFunctionName(bool show)
- {
- mWantsFunctionName = show;
- }
-
- void Recorder::showMultiline(bool show)
- {
- mWantsMultiline = show;
- }
-
- void addRecorder(RecorderPtr recorder)
- {
- if (!recorder)
- {
- return;
- }
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- LLMutexLock lock(&s->mRecorderMutex);
- s->mRecorders.push_back(recorder);
- }
-
- void removeRecorder(RecorderPtr recorder)
- {
- if (!recorder)
- {
- return;
- }
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- LLMutexLock lock(&s->mRecorderMutex);
- s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
- s->mRecorders.end());
- }
-
- // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
- // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
- // points to the Recorder base class), but a shared_ptr<RECORDER> which
- // specifically points to the concrete RECORDER subclass instance, along
- // with a Recorders::iterator indicating the position of that entry in
- // mRecorders. The shared_ptr might be empty (operator!() returns true) if
- // there was no such RECORDER subclass instance in mRecorders.
- //
- // NOTE!!! Requires external mutex lock!!!
- template <typename RECORDER>
- std::pair<std::shared_ptr<RECORDER>, Recorders::iterator>
- findRecorderPos(SettingsConfigPtr &s)
- {
- // Since we promise to return an iterator, use a classic iterator
- // loop.
- auto end{s->mRecorders.end()};
- for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it)
- {
- // *it is a RecorderPtr, a shared_ptr<Recorder>. Use a
- // dynamic_pointer_cast to try to downcast to test if it's also a
- // shared_ptr<RECORDER>.
- auto ptr = std::dynamic_pointer_cast<RECORDER>(*it);
- if (ptr)
- {
- // found the entry we want
- return { ptr, it };
- }
- }
- // dropped out of the loop without finding any such entry -- instead
- // of default-constructing Recorders::iterator (which might or might
- // not be valid), return a value that is valid but not dereferenceable.
- return { {}, end };
- }
-
- // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
- // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
- // points to the Recorder base class), but a shared_ptr<RECORDER> which
- // specifically points to the concrete RECORDER subclass instance. The
- // shared_ptr might be empty (operator!() returns true) if there was no
- // such RECORDER subclass instance in mRecorders.
- template <typename RECORDER>
- std::shared_ptr<RECORDER> findRecorder()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- LLMutexLock lock(&s->mRecorderMutex);
- return findRecorderPos<RECORDER>(s).first;
- }
-
- // Remove an entry from SettingsConfig::mRecorders whose RecorderPtr
- // points to a Recorder subclass of type RECORDER. Return true if there
- // was one and we removed it, false if there wasn't one to start with.
- template <typename RECORDER>
- bool removeRecorder()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- LLMutexLock lock(&s->mRecorderMutex);
- auto found = findRecorderPos<RECORDER>(s);
- if (found.first)
- {
- s->mRecorders.erase(found.second);
- }
- return bool(found.first);
- }
-}
-
-namespace LLError
-{
- void logToFile(const std::string& file_name)
- {
- // remove any previous Recorder filling this role
- removeRecorder<RecordToFile>();
-
- if (!file_name.empty())
- {
- std::shared_ptr<RecordToFile> recordToFile(new RecordToFile(file_name));
- if (recordToFile->okay())
- {
- addRecorder(recordToFile);
- }
- }
- }
-
- std::string logFileName()
- {
- auto found = findRecorder<RecordToFile>();
- return found? found->getFilename() : std::string();
- }
-
- void logToStderr()
- {
- if (! findRecorder<RecordToStderr>())
- {
- RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
- addRecorder(recordToStdErr);
- }
- }
-
- void logToFixedBuffer(LLLineBuffer* fixedBuffer)
- {
- // remove any previous Recorder filling this role
- removeRecorder<RecordToFixedBuffer>();
-
- if (fixedBuffer)
- {
- RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
- addRecorder(recordToFixedBuffer);
- }
- }
-}
-
-namespace
-{
- std::string escapedMessageLines(const std::string& message)
- {
- std::ostringstream out;
- size_t written_out = 0;
- size_t all_content = message.length();
- size_t escape_char_index; // always relative to start of message
- // Use find_first_of to find the next character in message that needs escaping
- for ( escape_char_index = message.find_first_of("\\\n\r");
- escape_char_index != std::string::npos && written_out < all_content;
- // record what we've written this iteration, scan for next char that needs escaping
- written_out = escape_char_index + 1, escape_char_index = message.find_first_of("\\\n\r", written_out)
- )
- {
- // found a character that needs escaping, so write up to that with the escape prefix
- // note that escape_char_index is relative to the start, not to the written_out offset
- out << message.substr(written_out, escape_char_index - written_out) << '\\';
-
- // write out the appropriate second character in the escape sequence
- char found = message[escape_char_index];
- switch ( found )
- {
- case '\\':
- out << '\\';
- break;
- case '\n':
- out << 'n';
- break;
- case '\r':
- out << 'r';
- break;
- }
- }
-
- if ( written_out < all_content ) // if the loop above didn't write everything
- {
- // write whatever was left
- out << message.substr(written_out, std::string::npos);
- }
- return out.str();
- }
-
- void writeToRecorders(const LLError::CallSite& site, const std::string& message)
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- LLError::ELevel level = site.mLevel;
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
-
- std::string escaped_message;
-
- LLMutexLock lock(&s->mRecorderMutex);
- for (LLError::RecorderPtr& r : s->mRecorders)
- {
- if (!r->enabled())
- {
- continue;
- }
-
- std::ostringstream message_stream;
-
- if (r->wantsTime() && s->mTimeFunction != NULL)
- {
- message_stream << s->mTimeFunction();
- }
- message_stream << " ";
-
- if (r->wantsLevel())
- {
- message_stream << site.mLevelString;
- }
- message_stream << " ";
-
- if (r->wantsTags())
- {
- message_stream << site.mTagString;
- }
- message_stream << " ";
-
- if (r->wantsLocation() || level == LLError::LEVEL_ERROR)
- {
- message_stream << site.mLocationString;
- }
- message_stream << " ";
-
- if (r->wantsFunctionName())
- {
- message_stream << site.mFunctionString;
- }
- message_stream << " : ";
-
- if (r->wantsMultiline())
- {
- message_stream << message;
- }
- else
- {
- if (escaped_message.empty())
- {
- escaped_message = escapedMessageLines(message);
- }
- message_stream << escaped_message;
- }
-
- r->recordMessage(level, message_stream.str());
- }
- }
-}
-
-namespace {
- // We need a couple different mutexes, but we want to use the same mechanism
- // for both. Make getMutex() a template function with different instances
- // for different MutexDiscriminator values.
- enum MutexDiscriminator
- {
- LOG_MUTEX,
- STACKS_MUTEX
- };
- // Some logging calls happen very early in processing -- so early that our
- // module-static variables aren't yet initialized. getMutex() wraps a
- // function-static LLMutex so that early calls can still have a valid
- // LLMutex instance.
- template <MutexDiscriminator MTX>
- LLMutex* getMutex()
- {
- // guaranteed to be initialized the first time control reaches here
- static LLMutex sMutex;
- return &sMutex;
- }
-
- bool checkLevelMap(const LevelMap& map, const std::string& key,
- LLError::ELevel& level)
- {
- bool stop_checking;
- LevelMap::const_iterator i = map.find(key);
- if (i == map.end())
- {
- return stop_checking = false;
- }
-
- level = i->second;
- return stop_checking = true;
- }
-
- bool checkLevelMap( const LevelMap& map,
- const char *const * keys,
- size_t count,
- LLError::ELevel& level)
- {
- bool found_level = false;
-
- LLError::ELevel tag_level = LLError::LEVEL_NONE;
-
- for (size_t i = 0; i < count; i++)
- {
- LevelMap::const_iterator it = map.find(keys[i]);
- if (it != map.end())
- {
- found_level = true;
- tag_level = llmin(tag_level, it->second);
- }
- }
-
- if (found_level)
- {
- level = tag_level;
- }
- return found_level;
- }
-}
-
-namespace LLError
-{
-
- bool Log::shouldLog(CallSite& site)
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
- if (!lock.isLocked())
- {
- return false;
- }
-
- Globals *g = Globals::getInstance();
- SettingsConfigPtr s = g->getSettingsConfig();
-
- s->mShouldLogCallCounter++;
-
- const std::string& class_name = className(site.mClassInfo);
- std::string function_name = functionName(site.mFunction);
-#if LL_LINUX
- // gross, but typeid comparison seems to always fail here with gcc4.1
- if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name()))
-#else
- if (site.mClassInfo != typeid(NoClassInfo))
-#endif // LL_LINUX
- {
- function_name = class_name + "::" + function_name;
- }
-
- ELevel compareLevel = s->mDefaultLevel;
-
- // The most specific match found will be used as the log level,
- // since the computation short circuits.
- // So, in increasing order of importance:
- // Default < Tags < File < Class < Function
- checkLevelMap(s->mFunctionLevelMap, function_name, compareLevel)
- || checkLevelMap(s->mClassLevelMap, class_name, compareLevel)
- || checkLevelMap(s->mFileLevelMap, abbreviateFile(site.mFile), compareLevel)
- || (site.mTagCount > 0
- ? checkLevelMap(s->mTagLevelMap, site.mTags, site.mTagCount, compareLevel)
- : false);
-
- site.mCached = true;
- g->addCallSite(site);
- return site.mShouldLog = site.mLevel >= compareLevel;
- }
-
-
- void Log::flush(const std::ostringstream& out, const CallSite& site)
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING
- LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
- if (!lock.isLocked())
- {
- return;
- }
-
- Globals* g = Globals::getInstance();
- SettingsConfigPtr s = g->getSettingsConfig();
-
- std::string message = out.str();
-
- if (site.mPrintOnce)
- {
- std::ostringstream message_stream;
-
- std::map<std::string, unsigned int>::iterator messageIter = s->mUniqueLogMessages.find(message);
- if (messageIter != s->mUniqueLogMessages.end())
- {
- messageIter->second++;
- unsigned int num_messages = messageIter->second;
- if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0)
- {
- message_stream << "ONCE (" << num_messages << "th time seen): ";
- }
- else
- {
- return;
- }
- }
- else
- {
- message_stream << "ONCE: ";
- s->mUniqueLogMessages[message] = 1;
- }
- message_stream << message;
- message = message_stream.str();
- }
-
- writeToRecorders(site, message);
-
- if (site.mLevel == LEVEL_ERROR)
- {
- g->mFatalMessage = message;
- if (s->mCrashFunction)
- {
- s->mCrashFunction(message);
- }
- }
- }
-}
-
-namespace LLError
-{
- SettingsStoragePtr saveAndResetSettings()
- {
- return Globals::getInstance()->saveAndResetSettingsConfig();
- }
-
- void restoreSettings(SettingsStoragePtr pSettingsStorage)
- {
- return Globals::getInstance()->restore(pSettingsStorage);
- }
-
- std::string removePrefix(std::string& s, const std::string& p)
- {
- std::string::size_type where = s.find(p);
- if (where == std::string::npos)
- {
- return s;
- }
-
- return std::string(s, where + p.size());
- }
-
- void replaceChar(std::string& s, char old, char replacement)
- {
- std::string::size_type i = 0;
- std::string::size_type len = s.length();
- for ( ; i < len; i++ )
- {
- if (s[i] == old)
- {
- s[i] = replacement;
- }
- }
- }
-
- std::string abbreviateFile(const std::string& filePath)
- {
- std::string f = filePath;
-#if LL_WINDOWS
- replaceChar(f, '\\', '/');
-#endif
- static std::string indra_prefix = "indra/";
- f = removePrefix(f, indra_prefix);
-
-#if LL_DARWIN
- static std::string newview_prefix = "newview/../";
- f = removePrefix(f, newview_prefix);
-#endif
-
- return f;
- }
-
- int shouldLogCallCount()
- {
- SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig();
- return s->mShouldLogCallCounter;
- }
-
- std::string utcTime()
- {
- time_t now = time(NULL);
- const size_t BUF_SIZE = 64;
- char time_str[BUF_SIZE]; /* Flawfinder: ignore */
-
- auto chars = strftime(time_str, BUF_SIZE,
- "%Y-%m-%dT%H:%M:%SZ",
- gmtime(&now));
-
- return chars ? time_str : "time error";
- }
-}
-
-namespace LLError
-{
- LLCallStacks::StringVector LLCallStacks::sBuffer ;
-
- //static
- void LLCallStacks::push(const char* function, const int line)
- {
- LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
- if (!lock.isLocked())
- {
- return;
- }
-
- if(sBuffer.size() > 511)
- {
- clear() ;
- }
-
- std::ostringstream out;
- insert(out, function, line);
- sBuffer.push_back(out.str());
- }
-
- //static
- void LLCallStacks::insert(std::ostream& out, const char* function, const int line)
- {
- out << function << " line " << line << " " ;
- }
-
- //static
- void LLCallStacks::end(const std::ostringstream& out)
- {
- LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
- if (!lock.isLocked())
- {
- return;
- }
-
- if(sBuffer.size() > 511)
- {
- clear() ;
- }
-
- sBuffer.push_back(out.str());
- }
-
- //static
- void LLCallStacks::print()
- {
- LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
- if (!lock.isLocked())
- {
- return;
- }
-
- if(! sBuffer.empty())
- {
- LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
- for (StringVector::const_reverse_iterator ri(sBuffer.rbegin()), re(sBuffer.rend());
- ri != re; ++ri)
- {
- LL_INFOS() << (*ri) << LL_ENDL;
- }
- LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
- }
-
- cleanup();
- }
-
- //static
- void LLCallStacks::clear()
- {
- sBuffer.clear();
- }
-
- //static
- void LLCallStacks::cleanup()
- {
- clear();
- }
-
- std::ostream& operator<<(std::ostream& out, const LLStacktrace&)
- {
- return out << boost::stacktrace::stacktrace();
- }
-
- // LLOutOfMemoryWarning
- std::string LLUserWarningMsg::sLocalizedOutOfMemoryTitle;
- std::string LLUserWarningMsg::sLocalizedOutOfMemoryWarning;
- LLUserWarningMsg::Handler LLUserWarningMsg::sHandler;
-
- void LLUserWarningMsg::show(const std::string& message)
- {
- if (sHandler)
- {
- sHandler(std::string(), message);
- }
- }
-
- void LLUserWarningMsg::showOutOfMemory()
- {
- if (sHandler && !sLocalizedOutOfMemoryTitle.empty())
- {
- sHandler(sLocalizedOutOfMemoryTitle, sLocalizedOutOfMemoryWarning);
- }
- }
-
- void LLUserWarningMsg::showMissingFiles()
- {
- // Files Are missing, likely can't localize.
- const std::string error_string =
- "Second Life viewer couldn't access some of the files it needs and will be closed."
- "\n\nPlease reinstall viewer from https://secondlife.com/support/downloads/ and "
- "contact https://support.secondlife.com if issue persists after reinstall.";
- sHandler("Missing Files", error_string);
- }
-
- void LLUserWarningMsg::setHandler(const LLUserWarningMsg::Handler &handler)
- {
- sHandler = handler;
- }
-
- void LLUserWarningMsg::setOutOfMemoryStrings(const std::string& title, const std::string& message)
- {
- sLocalizedOutOfMemoryTitle = title;
- sLocalizedOutOfMemoryWarning = message;
- }
-}
-
-void crashdriver(void (*callback)(int*))
-{
- // The LLERROR_CRASH macro used to have inline code of the form:
- //int* make_me_crash = NULL;
- //*make_me_crash = 0;
-
- // But compilers are getting smart enough to recognize that, so we must
- // assign to an address supplied by a separate source file. We could do
- // the assignment here in crashdriver() -- but then BugSplat would group
- // all LL_ERRS() crashes as the fault of this one function, instead of
- // identifying the specific LL_ERRS() source line. So instead, do the
- // assignment in a lambda in the caller's source. We just provide the
- // nullptr target.
- callback(nullptr);
-}
+/** + * @file llerror.cpp + * @date December 2006 + * @brief error message system + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llerror.h" +#include "llerrorcontrol.h" +#include "llsdutil.h" + +#include <cctype> +#ifdef __GNUC__ +# include <cxxabi.h> +#endif // __GNUC__ +#include <sstream> +#if !LL_WINDOWS +# include <syslog.h> +# include <unistd.h> +# include <sys/stat.h> +#else +# include <io.h> +#endif // !LL_WINDOWS +#include <vector> +#include "string.h" + +#include "llapp.h" +#include "llapr.h" +#include "llfile.h" +#include "lllivefile.h" +#include "llsd.h" +#include "llsdserialize.h" +#include "llsingleton.h" +#include "llstl.h" +#include "lltimer.h" + +// On Mac, got: +// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define +// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if +// _Unwind_Backtrace is available without `_GNU_SOURCE`." +#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED +#include <boost/stacktrace.hpp> + +namespace { +#if LL_WINDOWS + void debugger_print(const std::string& s) + { + // Be careful when calling OutputDebugString as it throws DBG_PRINTEXCEPTION_C + // which works just fine under the windows debugger, but can cause users who + // have enabled SEHOP exception chain validation to crash due to interactions + // between the Win 32-bit exception handling and boost coroutine fiber stacks. BUG-2707 + // + if (IsDebuggerPresent()) + { + // Need UTF16 for Unicode OutputDebugString + // + if (s.size()) + { + OutputDebugString(utf8str_to_utf16str(s).c_str()); + OutputDebugString(TEXT("\n")); + } + } + } +#else + class RecordToSyslog : public LLError::Recorder + { + public: + RecordToSyslog(const std::string& identity) + : mIdentity(identity) + { + openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0); + // we need to set the string from a local copy of the string + // since apparanetly openlog expects the const char* to remain + // valid even after it returns (presumably until closelog) + } + + ~RecordToSyslog() + { + closelog(); + } + + virtual bool enabled() override + { + return LLError::getEnabledLogTypesMask() & 0x01; + } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + int syslogPriority = LOG_CRIT; + switch (level) { + case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break; + case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break; + case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break; + case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break; + default: syslogPriority = LOG_CRIT; + } + + syslog(syslogPriority, "%s", message.c_str()); + } + private: + std::string mIdentity; + }; +#endif + + class RecordToFile : public LLError::Recorder + { + public: + RecordToFile(const std::string& filename): + mName(filename) + { + mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app); + if (!mFile) + { + LL_INFOS() << "Error setting log file to " << filename << LL_ENDL; + } + else + { + if (!LLError::getAlwaysFlush()) + { + mFile.sync_with_stdio(false); + } + } + } + + ~RecordToFile() + { + mFile.close(); + } + + virtual bool enabled() override + { +#ifdef LL_RELEASE_FOR_DOWNLOAD + return 1; +#else + return LLError::getEnabledLogTypesMask() & 0x02; +#endif + } + + bool okay() const { return mFile.good(); } + + std::string getFilename() const { return mName; } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + if (LLError::getAlwaysFlush()) + { + mFile << message << std::endl; + } + else + { + mFile << message << "\n"; + } + } + + private: + const std::string mName; + llofstream mFile; + }; + + + class RecordToStderr : public LLError::Recorder + { + public: + RecordToStderr(bool timestamp) : mUseANSI(checkANSI()) + { + this->showMultiline(true); + } + + virtual bool enabled() override + { + return LLError::getEnabledLogTypesMask() & 0x04; + } + + LL_FORCE_INLINE std::string createBoldANSI() + { + std::string ansi_code; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += "1"; + ansi_code += "m"; + + return ansi_code; + } + + LL_FORCE_INLINE std::string createResetANSI() + { + std::string ansi_code; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += "0"; + ansi_code += "m"; + + return ansi_code; + } + + LL_FORCE_INLINE std::string createANSI(const std::string& color) + { + std::string ansi_code; + ansi_code += '\033'; + ansi_code += "["; + ansi_code += "38;5;"; + ansi_code += color; + ansi_code += "m"; + + return ansi_code; + } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + // The default colors for error, warn and debug are now a bit more pastel + // and easier to read on the default (black) terminal background but you + // now have the option to set the color of each via an environment variables: + // LL_ANSI_ERROR_COLOR_CODE (default is red) + // LL_ANSI_WARN_COLOR_CODE (default is blue) + // LL_ANSI_DEBUG_COLOR_CODE (default is magenta) + // The list of color codes can be found in many places but I used this page: + // https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html#256-colors + // (Note: you may need to restart Visual Studio to pick environment changes) + char* val = nullptr; + std::string s_ansi_error_code = "160"; + if ((val = getenv("LL_ANSI_ERROR_COLOR_CODE")) != nullptr) s_ansi_error_code = std::string(val); + std::string s_ansi_warn_code = "33"; + if ((val = getenv("LL_ANSI_WARN_COLOR_CODE")) != nullptr) s_ansi_warn_code = std::string(val); + std::string s_ansi_debug_code = "177"; + if ((val = getenv("LL_ANSI_DEBUG_COLOR_CODE")) != nullptr) s_ansi_debug_code = std::string(val); + + static std::string s_ansi_error = createANSI(s_ansi_error_code); // default is red + static std::string s_ansi_warn = createANSI(s_ansi_warn_code); // default is blue + static std::string s_ansi_debug = createANSI(s_ansi_debug_code); // default is magenta + + if (mUseANSI) + { + writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error : + (level == LLError::LEVEL_WARN) ? s_ansi_warn : + s_ansi_debug, message); + } + else + { + LL_PROFILE_ZONE_NAMED("fprintf"); + fprintf(stderr, "%s\n", message.c_str()); + } + } + + private: + bool mUseANSI; + + LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + static std::string s_ansi_bold = createBoldANSI(); // bold text + static std::string s_ansi_reset = createResetANSI(); // reset + // ANSI color code escape sequence, message, and reset in one fprintf call + // Default all message levels to bold so we can distinguish our own messages from those dumped by subprocesses and libraries. + fprintf(stderr, "%s%s\n%s", ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() ); + } + + static bool checkANSI(void) + { + // Check whether it's okay to use ANSI; if stderr is + // a tty then we assume yes. Can be turned off with + // the LL_NO_ANSI_COLOR env var. + return (0 != isatty(2)) && + (NULL == getenv("LL_NO_ANSI_COLOR")); + } + }; + + class RecordToFixedBuffer : public LLError::Recorder + { + public: + RecordToFixedBuffer(LLLineBuffer* buffer) + : mBuffer(buffer) + { + this->showMultiline(true); + this->showTags(false); + this->showLocation(false); + } + + virtual bool enabled() override + { + return LLError::getEnabledLogTypesMask() & 0x08; + } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + mBuffer->addLine(message); + } + + private: + LLLineBuffer* mBuffer; + }; + +#if LL_WINDOWS + class RecordToWinDebug: public LLError::Recorder + { + public: + RecordToWinDebug() + { + this->showMultiline(true); + this->showTags(false); + this->showLocation(false); + } + + virtual bool enabled() override + { + return LLError::getEnabledLogTypesMask() & 0x10; + } + + virtual void recordMessage(LLError::ELevel level, + const std::string& message) override + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + debugger_print(message); + } + }; +#endif +} + + +namespace +{ + std::string className(const std::type_info& type) + { + return LLError::Log::demangle(type.name()); + } +} // anonymous + +namespace LLError +{ + std::string Log::demangle(const char* mangled) + { +#ifdef __GNUC__ + // GCC: type_info::name() returns a mangled class name,st demangle + // passing nullptr, 0 forces allocation of a unique buffer we can free + // fixing MAINT-8724 on OSX 10.14 + int status = -1; + char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status); + std::string result(name ? name : mangled); + free(name); + return result; + +#elif LL_WINDOWS + // Visual Studio: type_info::name() includes the text "class " at the start + std::string name = mangled; + for (const auto& prefix : std::vector<std::string>{ "class ", "struct " }) + { + if (0 == name.compare(0, prefix.length(), prefix)) + { + return name.substr(prefix.length()); + } + } + // huh, that's odd, we should see one or the other prefix -- but don't + // try to log unless logging is already initialized + // in Python, " or ".join(vector) -- but in C++, a PITB + LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '" + << name << "'" << LL_ENDL; + return name; + +#else // neither GCC nor Visual Studio + return mangled; +#endif + } +} // LLError + +namespace +{ + std::string functionName(const std::string& preprocessor_name) + { +#if LL_WINDOWS + // DevStudio: the __FUNCTION__ macro string includes + // the type and/or namespace prefixes + + std::string::size_type p = preprocessor_name.rfind(':'); + if (p == std::string::npos) + { + return preprocessor_name; + } + return preprocessor_name.substr(p + 1); + +#else + return preprocessor_name; +#endif + } + + + class LogControlFile : public LLLiveFile + { + LOG_CLASS(LogControlFile); + + public: + static LogControlFile& fromDirectory(const std::string& user_dir, const std::string& app_dir); + + virtual bool loadFile(); + + private: + LogControlFile(const std::string &filename) + : LLLiveFile(filename) + { } + }; + + LogControlFile& LogControlFile::fromDirectory(const std::string& user_dir, const std::string& app_dir) + { + // NB: We have no abstraction in llcommon for the "proper" + // delimiter but it turns out that "/" works on all three platforms + + std::string file = user_dir + "/logcontrol-dev.xml"; + + llstat stat_info; + if (LLFile::stat(file, &stat_info)) { + // NB: stat returns non-zero if it can't read the file, for example + // if it doesn't exist. LLFile has no better abstraction for + // testing for file existence. + + file = app_dir + "/logcontrol.xml"; + } + return * new LogControlFile(file); + // NB: This instance is never freed + } + + bool LogControlFile::loadFile() + { + LLSD configuration; + + { + llifstream file(filename().c_str()); + if (!file.is_open()) + { + LL_WARNS() << filename() << " failed to open file; not changing configuration" << LL_ENDL; + return false; + } + + if (LLSDSerialize::fromXML(configuration, file) == LLSDParser::PARSE_FAILURE) + { + LL_WARNS() << filename() << " parcing error; not changing configuration" << LL_ENDL; + return false; + } + + if (! configuration || !configuration.isMap()) + { + LL_WARNS() << filename() << " missing, ill-formed, or simply undefined" + " content; not changing configuration" + << LL_ENDL; + return false; + } + } + + LLError::configure(configuration); + LL_INFOS("LogControlFile") << "logging reconfigured from " << filename() << LL_ENDL; + return true; + } + + + typedef std::map<std::string, LLError::ELevel> LevelMap; + typedef std::vector<LLError::RecorderPtr> Recorders; + typedef std::vector<LLError::CallSite*> CallSiteVector; + + class SettingsConfig : public LLRefCount + { + friend class Globals; + + public: + virtual ~SettingsConfig(); + + LLError::ELevel mDefaultLevel; + + bool mLogAlwaysFlush; + + U32 mEnabledLogTypesMask; + + LevelMap mFunctionLevelMap; + LevelMap mClassLevelMap; + LevelMap mFileLevelMap; + LevelMap mTagLevelMap; + std::map<std::string, unsigned int> mUniqueLogMessages; + + LLError::FatalFunction mCrashFunction; + LLError::TimeFunction mTimeFunction; + + Recorders mRecorders; + LLMutex mRecorderMutex; + + int mShouldLogCallCounter; + + private: + SettingsConfig(); + }; + + typedef LLPointer<SettingsConfig> SettingsConfigPtr; + + SettingsConfig::SettingsConfig() + : LLRefCount(), + mDefaultLevel(LLError::LEVEL_DEBUG), + mLogAlwaysFlush(true), + mEnabledLogTypesMask(255), + mFunctionLevelMap(), + mClassLevelMap(), + mFileLevelMap(), + mTagLevelMap(), + mUniqueLogMessages(), + mCrashFunction(NULL), + mTimeFunction(NULL), + mRecorders(), + mRecorderMutex(), + mShouldLogCallCounter(0) + { + } + + SettingsConfig::~SettingsConfig() + { + mRecorders.clear(); + } + + class Globals + { + public: + static Globals* getInstance(); + protected: + Globals(); + public: + std::string mFatalMessage; + + void addCallSite(LLError::CallSite&); + void invalidateCallSites(); + + SettingsConfigPtr getSettingsConfig(); + + void resetSettingsConfig(); + LLError::SettingsStoragePtr saveAndResetSettingsConfig(); + void restore(LLError::SettingsStoragePtr pSettingsStorage); + private: + CallSiteVector callSites; + SettingsConfigPtr mSettingsConfig; + }; + + Globals::Globals() + : + callSites(), + mSettingsConfig(new SettingsConfig()) + { + } + + + Globals* Globals::getInstance() + { + // According to C++11 Function-Local Initialization + // of static variables is supposed to be thread safe + // without risk of deadlocks. + static Globals inst; + + return &inst; + } + + void Globals::addCallSite(LLError::CallSite& site) + { + callSites.push_back(&site); + } + + void Globals::invalidateCallSites() + { + for (LLError::CallSite* site : callSites) + { + site->invalidate(); + } + + callSites.clear(); + } + + SettingsConfigPtr Globals::getSettingsConfig() + { + return mSettingsConfig; + } + + void Globals::resetSettingsConfig() + { + invalidateCallSites(); + mSettingsConfig = new SettingsConfig(); + } + + LLError::SettingsStoragePtr Globals::saveAndResetSettingsConfig() + { + LLError::SettingsStoragePtr oldSettingsConfig(mSettingsConfig.get()); + resetSettingsConfig(); + return oldSettingsConfig; + } + + void Globals::restore(LLError::SettingsStoragePtr pSettingsStorage) + { + invalidateCallSites(); + SettingsConfigPtr newSettingsConfig(dynamic_cast<SettingsConfig *>(pSettingsStorage.get())); + mSettingsConfig = newSettingsConfig; + } +} + +namespace LLError +{ + CallSite::CallSite(ELevel level, + const char* file, + int line, + const std::type_info& class_info, + const char* function, + bool printOnce, + const char** tags, + size_t tag_count) + : mLevel(level), + mFile(file), + mLine(line), + mClassInfo(class_info), + mFunction(function), + mCached(false), + mShouldLog(false), + mPrintOnce(printOnce), + mTags(new const char* [tag_count]), + mTagCount(tag_count) + { + switch (mLevel) + { + case LEVEL_DEBUG: mLevelString = "DEBUG"; break; + case LEVEL_INFO: mLevelString = "INFO"; break; + case LEVEL_WARN: mLevelString = "WARNING"; break; + case LEVEL_ERROR: mLevelString = "ERROR"; break; + default: mLevelString = "XXX"; break; + }; + + mLocationString = llformat("%s(%d)", abbreviateFile(mFile).c_str(), mLine); +#if LL_WINDOWS + // DevStudio: __FUNCTION__ already includes the full class name +#else +#if LL_LINUX + // gross, but typeid comparison seems to always fail here with gcc4.1 + if (0 != strcmp(mClassInfo.name(), typeid(NoClassInfo).name())) +#else + if (mClassInfo != typeid(NoClassInfo)) +#endif // LL_LINUX + { + mFunctionString = className(mClassInfo) + "::"; + } +#endif + mFunctionString += std::string(mFunction); + + for (int i = 0; i < tag_count; i++) + { + if (strchr(tags[i], ' ')) + { + LL_ERRS() << "Space is not allowed in a log tag at " << mLocationString << LL_ENDL; + } + mTags[i] = tags[i]; + } + + mTagString.append("#"); + // always construct a tag sequence; will be just a single # if no tag + for (size_t i = 0; i < mTagCount; i++) + { + mTagString.append(mTags[i]); + mTagString.append("#"); + } + } + + CallSite::~CallSite() + { + delete []mTags; + } + + void CallSite::invalidate() + { + mCached = false; + } +} + +namespace +{ + bool shouldLogToStderr() + { +#if LL_DARWIN + // On macOS, stderr from apps launched from the Finder goes to the + // console log. It's generally considered bad form to spam too much + // there. That scenario can be detected by noticing that stderr is a + // character device (S_IFCHR). + + // If stderr is a tty or a pipe, assume the user launched from the + // command line or debugger and therefore wants to see stderr. + if (isatty(STDERR_FILENO)) + return true; + // not a tty, but might still be a pipe -- check + struct stat st; + if (fstat(STDERR_FILENO, &st) < 0) + { + // capture errno right away, before engaging any other operations + auto errno_save = errno; + // this gets called during log-system setup -- can't log yet! + std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno " + << errno_save << std::endl; + // if we can't tell, err on the safe side and don't write stderr + return false; + } + + // fstat() worked: return true only if stderr is a pipe + return ((st.st_mode & S_IFMT) == S_IFIFO); +#else + return true; +#endif + } + + bool stderrLogWantsTime() + { +#if LL_WINDOWS + return false; +#else + return true; +#endif + } + + + void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true) + { + Globals::getInstance()->resetSettingsConfig(); + + LLError::setDefaultLevel(LLError::LEVEL_INFO); + LLError::setAlwaysFlush(true); + LLError::setEnabledLogTypesMask(0xFFFFFFFF); + LLError::setTimeFunction(LLError::utcTime); + + // log_to_stderr is only false in the unit and integration tests to keep builds quieter + if (log_to_stderr && shouldLogToStderr()) + { + LLError::logToStderr(); + } + +#if LL_WINDOWS + LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug()); + LLError::addRecorder(recordToWinDebug); +#endif + + LogControlFile& e = LogControlFile::fromDirectory(user_dir, app_dir); + + // NOTE: We want to explicitly load the file before we add it to the event timer + // that checks for changes to the file. Else, we're not actually loading the file yet, + // and most of the initialization happens without any attention being paid to the + // log control file. Not to mention that when it finally gets checked later, + // all log statements that have been evaluated already become dirty and need to be + // evaluated for printing again. So, make sure to call checkAndReload() + // before addToEventTimer(). + e.checkAndReload(); + e.addToEventTimer(); + } +} + +namespace LLError +{ + void initForApplication(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr) + { + commonInit(user_dir, app_dir, log_to_stderr); + } + + void setFatalFunction(const FatalFunction& f) + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + s->mCrashFunction = f; + } + + FatalFunction getFatalFunction() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mCrashFunction; + } + + std::string getFatalMessage() + { + return Globals::getInstance()->mFatalMessage; + } + + void setTimeFunction(TimeFunction f) + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + s->mTimeFunction = f; + } + + void setDefaultLevel(ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mDefaultLevel = level; + } + + ELevel getDefaultLevel() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mDefaultLevel; + } + + void setAlwaysFlush(bool flush) + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + s->mLogAlwaysFlush = flush; + } + + bool getAlwaysFlush() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mLogAlwaysFlush; + } + + void setEnabledLogTypesMask(U32 mask) + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + s->mEnabledLogTypesMask = mask; + } + + U32 getEnabledLogTypesMask() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mEnabledLogTypesMask; + } + + void setFunctionLevel(const std::string& function_name, ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mFunctionLevelMap[function_name] = level; + } + + void setClassLevel(const std::string& class_name, ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mClassLevelMap[class_name] = level; + } + + void setFileLevel(const std::string& file_name, ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mFileLevelMap[file_name] = level; + } + + void setTagLevel(const std::string& tag_name, ELevel level) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + s->mTagLevelMap[tag_name] = level; + } + + LLError::ELevel decodeLevel(std::string name) + { + static LevelMap level_names; + if (level_names.empty()) + { + level_names["ALL"] = LLError::LEVEL_ALL; + level_names["DEBUG"] = LLError::LEVEL_DEBUG; + level_names["INFO"] = LLError::LEVEL_INFO; + level_names["WARN"] = LLError::LEVEL_WARN; + level_names["ERROR"] = LLError::LEVEL_ERROR; + level_names["NONE"] = LLError::LEVEL_NONE; + } + + std::transform(name.begin(), name.end(), name.begin(), toupper); + + LevelMap::const_iterator i = level_names.find(name); + if (i == level_names.end()) + { + LL_WARNS() << "unrecognized logging level: '" << name << "'" << LL_ENDL; + return LLError::LEVEL_INFO; + } + + return i->second; + } +} + +namespace { + void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level) + { + LLSD::array_const_iterator i, end; + for (i = list.beginArray(), end = list.endArray(); i != end; ++i) + { + map[*i] = level; + } + } +} + +namespace LLError +{ + void configure(const LLSD& config) + { + Globals *g = Globals::getInstance(); + g->invalidateCallSites(); + SettingsConfigPtr s = g->getSettingsConfig(); + + s->mFunctionLevelMap.clear(); + s->mClassLevelMap.clear(); + s->mFileLevelMap.clear(); + s->mTagLevelMap.clear(); + s->mUniqueLogMessages.clear(); + + setDefaultLevel(decodeLevel(config["default-level"])); + if (config.has("log-always-flush")) + { + setAlwaysFlush(config["log-always-flush"]); + } + if (config.has("enabled-log-types-mask")) + { + setEnabledLogTypesMask(config["enabled-log-types-mask"].asInteger()); + } + + if (config.has("settings") && config["settings"].isArray()) + { + LLSD sets = config["settings"]; + LLSD::array_const_iterator a, end; + for (a = sets.beginArray(), end = sets.endArray(); a != end; ++a) + { + const LLSD& entry = *a; + if (entry.isMap() && entry.size() != 0) + { + ELevel level = decodeLevel(entry["level"]); + + setLevels(s->mFunctionLevelMap, entry["functions"], level); + setLevels(s->mClassLevelMap, entry["classes"], level); + setLevels(s->mFileLevelMap, entry["files"], level); + setLevels(s->mTagLevelMap, entry["tags"], level); + } + } + } + } +} + + +namespace LLError +{ + Recorder::Recorder() + : mWantsTime(true) + , mWantsTags(true) + , mWantsLevel(true) + , mWantsLocation(true) + , mWantsFunctionName(true) + , mWantsMultiline(false) + { + } + + Recorder::~Recorder() + { + } + + bool Recorder::wantsTime() + { + return mWantsTime; + } + + // virtual + bool Recorder::wantsTags() + { + return mWantsTags; + } + + // virtual + bool Recorder::wantsLevel() + { + return mWantsLevel; + } + + // virtual + bool Recorder::wantsLocation() + { + return mWantsLocation; + } + + // virtual + bool Recorder::wantsFunctionName() + { + return mWantsFunctionName; + } + + // virtual + bool Recorder::wantsMultiline() + { + return mWantsMultiline; + } + + void Recorder::showTime(bool show) + { + mWantsTime = show; + } + + void Recorder::showTags(bool show) + { + mWantsTags = show; + } + + void Recorder::showLevel(bool show) + { + mWantsLevel = show; + } + + void Recorder::showLocation(bool show) + { + mWantsLocation = show; + } + + void Recorder::showFunctionName(bool show) + { + mWantsFunctionName = show; + } + + void Recorder::showMultiline(bool show) + { + mWantsMultiline = show; + } + + void addRecorder(RecorderPtr recorder) + { + if (!recorder) + { + return; + } + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + s->mRecorders.push_back(recorder); + } + + void removeRecorder(RecorderPtr recorder) + { + if (!recorder) + { + return; + } + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder), + s->mRecorders.end()); + } + + // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to + // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which + // points to the Recorder base class), but a shared_ptr<RECORDER> which + // specifically points to the concrete RECORDER subclass instance, along + // with a Recorders::iterator indicating the position of that entry in + // mRecorders. The shared_ptr might be empty (operator!() returns true) if + // there was no such RECORDER subclass instance in mRecorders. + // + // NOTE!!! Requires external mutex lock!!! + template <typename RECORDER> + std::pair<std::shared_ptr<RECORDER>, Recorders::iterator> + findRecorderPos(SettingsConfigPtr &s) + { + // Since we promise to return an iterator, use a classic iterator + // loop. + auto end{s->mRecorders.end()}; + for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it) + { + // *it is a RecorderPtr, a shared_ptr<Recorder>. Use a + // dynamic_pointer_cast to try to downcast to test if it's also a + // shared_ptr<RECORDER>. + auto ptr = std::dynamic_pointer_cast<RECORDER>(*it); + if (ptr) + { + // found the entry we want + return { ptr, it }; + } + } + // dropped out of the loop without finding any such entry -- instead + // of default-constructing Recorders::iterator (which might or might + // not be valid), return a value that is valid but not dereferenceable. + return { {}, end }; + } + + // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to + // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which + // points to the Recorder base class), but a shared_ptr<RECORDER> which + // specifically points to the concrete RECORDER subclass instance. The + // shared_ptr might be empty (operator!() returns true) if there was no + // such RECORDER subclass instance in mRecorders. + template <typename RECORDER> + std::shared_ptr<RECORDER> findRecorder() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + return findRecorderPos<RECORDER>(s).first; + } + + // Remove an entry from SettingsConfig::mRecorders whose RecorderPtr + // points to a Recorder subclass of type RECORDER. Return true if there + // was one and we removed it, false if there wasn't one to start with. + template <typename RECORDER> + bool removeRecorder() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + LLMutexLock lock(&s->mRecorderMutex); + auto found = findRecorderPos<RECORDER>(s); + if (found.first) + { + s->mRecorders.erase(found.second); + } + return bool(found.first); + } +} + +namespace LLError +{ + void logToFile(const std::string& file_name) + { + // remove any previous Recorder filling this role + removeRecorder<RecordToFile>(); + + if (!file_name.empty()) + { + std::shared_ptr<RecordToFile> recordToFile(new RecordToFile(file_name)); + if (recordToFile->okay()) + { + addRecorder(recordToFile); + } + } + } + + std::string logFileName() + { + auto found = findRecorder<RecordToFile>(); + return found? found->getFilename() : std::string(); + } + + void logToStderr() + { + if (! findRecorder<RecordToStderr>()) + { + RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime())); + addRecorder(recordToStdErr); + } + } + + void logToFixedBuffer(LLLineBuffer* fixedBuffer) + { + // remove any previous Recorder filling this role + removeRecorder<RecordToFixedBuffer>(); + + if (fixedBuffer) + { + RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer)); + addRecorder(recordToFixedBuffer); + } + } +} + +namespace +{ + std::string escapedMessageLines(const std::string& message) + { + std::ostringstream out; + size_t written_out = 0; + size_t all_content = message.length(); + size_t escape_char_index; // always relative to start of message + // Use find_first_of to find the next character in message that needs escaping + for ( escape_char_index = message.find_first_of("\\\n\r"); + escape_char_index != std::string::npos && written_out < all_content; + // record what we've written this iteration, scan for next char that needs escaping + written_out = escape_char_index + 1, escape_char_index = message.find_first_of("\\\n\r", written_out) + ) + { + // found a character that needs escaping, so write up to that with the escape prefix + // note that escape_char_index is relative to the start, not to the written_out offset + out << message.substr(written_out, escape_char_index - written_out) << '\\'; + + // write out the appropriate second character in the escape sequence + char found = message[escape_char_index]; + switch ( found ) + { + case '\\': + out << '\\'; + break; + case '\n': + out << 'n'; + break; + case '\r': + out << 'r'; + break; + } + } + + if ( written_out < all_content ) // if the loop above didn't write everything + { + // write whatever was left + out << message.substr(written_out, std::string::npos); + } + return out.str(); + } + + void writeToRecorders(const LLError::CallSite& site, const std::string& message) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LLError::ELevel level = site.mLevel; + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + + std::string escaped_message; + + LLMutexLock lock(&s->mRecorderMutex); + for (LLError::RecorderPtr& r : s->mRecorders) + { + if (!r->enabled()) + { + continue; + } + + std::ostringstream message_stream; + + if (r->wantsTime() && s->mTimeFunction != NULL) + { + message_stream << s->mTimeFunction(); + } + message_stream << " "; + + if (r->wantsLevel()) + { + message_stream << site.mLevelString; + } + message_stream << " "; + + if (r->wantsTags()) + { + message_stream << site.mTagString; + } + message_stream << " "; + + if (r->wantsLocation() || level == LLError::LEVEL_ERROR) + { + message_stream << site.mLocationString; + } + message_stream << " "; + + if (r->wantsFunctionName()) + { + message_stream << site.mFunctionString; + } + message_stream << " : "; + + if (r->wantsMultiline()) + { + message_stream << message; + } + else + { + if (escaped_message.empty()) + { + escaped_message = escapedMessageLines(message); + } + message_stream << escaped_message; + } + + r->recordMessage(level, message_stream.str()); + } + } +} + +namespace { + // We need a couple different mutexes, but we want to use the same mechanism + // for both. Make getMutex() a template function with different instances + // for different MutexDiscriminator values. + enum MutexDiscriminator + { + LOG_MUTEX, + STACKS_MUTEX + }; + // Some logging calls happen very early in processing -- so early that our + // module-static variables aren't yet initialized. getMutex() wraps a + // function-static LLMutex so that early calls can still have a valid + // LLMutex instance. + template <MutexDiscriminator MTX> + LLMutex* getMutex() + { + // guaranteed to be initialized the first time control reaches here + static LLMutex sMutex; + return &sMutex; + } + + bool checkLevelMap(const LevelMap& map, const std::string& key, + LLError::ELevel& level) + { + bool stop_checking; + LevelMap::const_iterator i = map.find(key); + if (i == map.end()) + { + return stop_checking = false; + } + + level = i->second; + return stop_checking = true; + } + + bool checkLevelMap( const LevelMap& map, + const char *const * keys, + size_t count, + LLError::ELevel& level) + { + bool found_level = false; + + LLError::ELevel tag_level = LLError::LEVEL_NONE; + + for (size_t i = 0; i < count; i++) + { + LevelMap::const_iterator it = map.find(keys[i]); + if (it != map.end()) + { + found_level = true; + tag_level = llmin(tag_level, it->second); + } + } + + if (found_level) + { + level = tag_level; + } + return found_level; + } +} + +namespace LLError +{ + + bool Log::shouldLog(CallSite& site) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5); + if (!lock.isLocked()) + { + return false; + } + + Globals *g = Globals::getInstance(); + SettingsConfigPtr s = g->getSettingsConfig(); + + s->mShouldLogCallCounter++; + + const std::string& class_name = className(site.mClassInfo); + std::string function_name = functionName(site.mFunction); +#if LL_LINUX + // gross, but typeid comparison seems to always fail here with gcc4.1 + if (0 != strcmp(site.mClassInfo.name(), typeid(NoClassInfo).name())) +#else + if (site.mClassInfo != typeid(NoClassInfo)) +#endif // LL_LINUX + { + function_name = class_name + "::" + function_name; + } + + ELevel compareLevel = s->mDefaultLevel; + + // The most specific match found will be used as the log level, + // since the computation short circuits. + // So, in increasing order of importance: + // Default < Tags < File < Class < Function + checkLevelMap(s->mFunctionLevelMap, function_name, compareLevel) + || checkLevelMap(s->mClassLevelMap, class_name, compareLevel) + || checkLevelMap(s->mFileLevelMap, abbreviateFile(site.mFile), compareLevel) + || (site.mTagCount > 0 + ? checkLevelMap(s->mTagLevelMap, site.mTags, site.mTagCount, compareLevel) + : false); + + site.mCached = true; + g->addCallSite(site); + return site.mShouldLog = site.mLevel >= compareLevel; + } + + + void Log::flush(const std::ostringstream& out, const CallSite& site) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LOGGING + LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5); + if (!lock.isLocked()) + { + return; + } + + Globals* g = Globals::getInstance(); + SettingsConfigPtr s = g->getSettingsConfig(); + + std::string message = out.str(); + + if (site.mPrintOnce) + { + std::ostringstream message_stream; + + std::map<std::string, unsigned int>::iterator messageIter = s->mUniqueLogMessages.find(message); + if (messageIter != s->mUniqueLogMessages.end()) + { + messageIter->second++; + unsigned int num_messages = messageIter->second; + if (num_messages == 10 || num_messages == 50 || (num_messages % 100) == 0) + { + message_stream << "ONCE (" << num_messages << "th time seen): "; + } + else + { + return; + } + } + else + { + message_stream << "ONCE: "; + s->mUniqueLogMessages[message] = 1; + } + message_stream << message; + message = message_stream.str(); + } + + writeToRecorders(site, message); + + if (site.mLevel == LEVEL_ERROR) + { + g->mFatalMessage = message; + if (s->mCrashFunction) + { + s->mCrashFunction(message); + } + } + } +} + +namespace LLError +{ + SettingsStoragePtr saveAndResetSettings() + { + return Globals::getInstance()->saveAndResetSettingsConfig(); + } + + void restoreSettings(SettingsStoragePtr pSettingsStorage) + { + return Globals::getInstance()->restore(pSettingsStorage); + } + + std::string removePrefix(std::string& s, const std::string& p) + { + std::string::size_type where = s.find(p); + if (where == std::string::npos) + { + return s; + } + + return std::string(s, where + p.size()); + } + + void replaceChar(std::string& s, char old, char replacement) + { + std::string::size_type i = 0; + std::string::size_type len = s.length(); + for ( ; i < len; i++ ) + { + if (s[i] == old) + { + s[i] = replacement; + } + } + } + + std::string abbreviateFile(const std::string& filePath) + { + std::string f = filePath; +#if LL_WINDOWS + replaceChar(f, '\\', '/'); +#endif + static std::string indra_prefix = "indra/"; + f = removePrefix(f, indra_prefix); + +#if LL_DARWIN + static std::string newview_prefix = "newview/../"; + f = removePrefix(f, newview_prefix); +#endif + + return f; + } + + int shouldLogCallCount() + { + SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); + return s->mShouldLogCallCounter; + } + + std::string utcTime() + { + time_t now = time(NULL); + const size_t BUF_SIZE = 64; + char time_str[BUF_SIZE]; /* Flawfinder: ignore */ + + auto chars = strftime(time_str, BUF_SIZE, + "%Y-%m-%dT%H:%M:%SZ", + gmtime(&now)); + + return chars ? time_str : "time error"; + } +} + +namespace LLError +{ + LLCallStacks::StringVector LLCallStacks::sBuffer ; + + //static + void LLCallStacks::push(const char* function, const int line) + { + LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5); + if (!lock.isLocked()) + { + return; + } + + if(sBuffer.size() > 511) + { + clear() ; + } + + std::ostringstream out; + insert(out, function, line); + sBuffer.push_back(out.str()); + } + + //static + void LLCallStacks::insert(std::ostream& out, const char* function, const int line) + { + out << function << " line " << line << " " ; + } + + //static + void LLCallStacks::end(const std::ostringstream& out) + { + LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5); + if (!lock.isLocked()) + { + return; + } + + if(sBuffer.size() > 511) + { + clear() ; + } + + sBuffer.push_back(out.str()); + } + + //static + void LLCallStacks::print() + { + LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5); + if (!lock.isLocked()) + { + return; + } + + if(! sBuffer.empty()) + { + LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL; + for (StringVector::const_reverse_iterator ri(sBuffer.rbegin()), re(sBuffer.rend()); + ri != re; ++ri) + { + LL_INFOS() << (*ri) << LL_ENDL; + } + LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL; + } + + cleanup(); + } + + //static + void LLCallStacks::clear() + { + sBuffer.clear(); + } + + //static + void LLCallStacks::cleanup() + { + clear(); + } + + std::ostream& operator<<(std::ostream& out, const LLStacktrace&) + { + return out << boost::stacktrace::stacktrace(); + } + + // LLOutOfMemoryWarning + std::string LLUserWarningMsg::sLocalizedOutOfMemoryTitle; + std::string LLUserWarningMsg::sLocalizedOutOfMemoryWarning; + LLUserWarningMsg::Handler LLUserWarningMsg::sHandler; + + void LLUserWarningMsg::show(const std::string& message) + { + if (sHandler) + { + sHandler(std::string(), message); + } + } + + void LLUserWarningMsg::showOutOfMemory() + { + if (sHandler && !sLocalizedOutOfMemoryTitle.empty()) + { + sHandler(sLocalizedOutOfMemoryTitle, sLocalizedOutOfMemoryWarning); + } + } + + void LLUserWarningMsg::showMissingFiles() + { + // Files Are missing, likely can't localize. + const std::string error_string = + "Second Life viewer couldn't access some of the files it needs and will be closed." + "\n\nPlease reinstall viewer from https://secondlife.com/support/downloads/ and " + "contact https://support.secondlife.com if issue persists after reinstall."; + sHandler("Missing Files", error_string); + } + + void LLUserWarningMsg::setHandler(const LLUserWarningMsg::Handler &handler) + { + sHandler = handler; + } + + void LLUserWarningMsg::setOutOfMemoryStrings(const std::string& title, const std::string& message) + { + sLocalizedOutOfMemoryTitle = title; + sLocalizedOutOfMemoryWarning = message; + } +} + +void crashdriver(void (*callback)(int*)) +{ + // The LLERROR_CRASH macro used to have inline code of the form: + //int* make_me_crash = NULL; + //*make_me_crash = 0; + + // But compilers are getting smart enough to recognize that, so we must + // assign to an address supplied by a separate source file. We could do + // the assignment here in crashdriver() -- but then BugSplat would group + // all LL_ERRS() crashes as the fault of this one function, instead of + // identifying the specific LL_ERRS() source line. So instead, do the + // assignment in a lambda in the caller's source. We just provide the + // nullptr target. + callback(nullptr); +} diff --git a/indra/llcommon/lleventemitter.h b/indra/llcommon/lleventemitter.h index 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 <algorithm>
-#include <typeinfo>
-#include <iostream>
-#include <string>
-#include <list>
-
-#include "stdtypes.h"
-
-///////////////////////////////////////////////////////////////////////////////
-// templatized emitter class
-template < class T >
-class eventEmitter
-{
- public:
- typedef typename T::EventType EventType;
- typedef std::list< T* > ObserverContainer;
- typedef void ( T::*observerMethod )( const EventType& );
-
- protected:
- ObserverContainer observers;
-
- public:
- eventEmitter () { };
-
- ~eventEmitter () { };
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- bool addObserver ( T* observerIn )
- {
- if ( ! observerIn )
- return false;
-
- // check if observer already exists
- if ( std::find ( observers.begin (), observers.end (), observerIn ) != observers.end () )
- return false;
-
- // save it
- observers.push_back ( observerIn );
-
- return true;
- };
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- bool remObserver ( T* observerIn )
- {
- if ( ! observerIn )
- return false;
-
- observers.remove ( observerIn );
-
- return true;
- };
-
- ///////////////////////////////////////////////////////////////////////////////
- //
- void update ( observerMethod method, const EventType& msgIn )
- {
- typename std::list< T* >::iterator iter = observers.begin ();
-
- while ( iter != observers.end () )
- {
- ( ( *iter )->*method ) ( msgIn );
-
- ++iter;
- };
- };
-};
-
-#endif // lleventemitter_h
+/** + * @file lleventemitter.h + * @brief General event emitter class + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// header guard +#ifndef LL_EVENTEMITTER_H +#define LL_EVENTEMITTER_H + +// standard headers +#include <algorithm> +#include <typeinfo> +#include <iostream> +#include <string> +#include <list> + +#include "stdtypes.h" + +/////////////////////////////////////////////////////////////////////////////// +// templatized emitter class +template < class T > +class eventEmitter +{ + public: + typedef typename T::EventType EventType; + typedef std::list< T* > ObserverContainer; + typedef void ( T::*observerMethod )( const EventType& ); + + protected: + ObserverContainer observers; + + public: + eventEmitter () { }; + + ~eventEmitter () { }; + + /////////////////////////////////////////////////////////////////////////////// + // + bool addObserver ( T* observerIn ) + { + if ( ! observerIn ) + return false; + + // check if observer already exists + if ( std::find ( observers.begin (), observers.end (), observerIn ) != observers.end () ) + return false; + + // save it + observers.push_back ( observerIn ); + + return true; + }; + + /////////////////////////////////////////////////////////////////////////////// + // + bool remObserver ( T* observerIn ) + { + if ( ! observerIn ) + return false; + + observers.remove ( observerIn ); + + return true; + }; + + /////////////////////////////////////////////////////////////////////////////// + // + void update ( observerMethod method, const EventType& msgIn ) + { + typename std::list< T* >::iterator iter = observers.begin (); + + while ( iter != observers.end () ) + { + ( ( *iter )->*method ) ( msgIn ); + + ++iter; + }; + }; +}; + +#endif // lleventemitter_h diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h index 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<LLEventTimer>
-{
-public:
-
- LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds
- LLEventTimer(const LLDate& time);
- virtual ~LLEventTimer();
-
- //function to be called at the supplied frequency
- // Normally return FALSE; TRUE will delete the timer after the function returns.
- virtual bool tick() = 0;
-
- static void updateClass();
-
- /// Schedule recurring calls to generic callable every period seconds.
- /// Returns a pointer; if you delete it, cancels the recurring calls.
- template <typename CALLABLE>
- static LLEventTimer* run_every(F32 period, const CALLABLE& callable);
-
- /// Schedule a future call to generic callable. Returns a pointer.
- /// CAUTION: The object referenced by that pointer WILL BE DELETED once
- /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT
- /// pointer->getInstance(pointer)!) can be used to test whether the
- /// pointer is still valid. If it is, deleting it will cancel the
- /// callback.
- template <typename CALLABLE>
- static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable);
-
- /// Like run_at(), but after a time delta rather than at a timestamp.
- /// Same CAUTION.
- template <typename CALLABLE>
- static LLEventTimer* run_after(F32 interval, const CALLABLE& callable);
-
-protected:
- LLTimer mEventTimer;
- F32 mPeriod;
-
-private:
- template <typename CALLABLE>
- class Generic;
-};
-
-template <typename CALLABLE>
-class LLEventTimer::Generic: public LLEventTimer
-{
-public:
- // making TIME generic allows engaging either LLEventTimer constructor
- template <typename TIME>
- Generic(const TIME& time, bool once, const CALLABLE& callable):
- LLEventTimer(time),
- mOnce(once),
- mCallable(callable)
- {}
- bool tick() override
- {
- mCallable();
- // true tells updateClass() to delete this instance
- return mOnce;
- }
-
-private:
- bool mOnce;
- CALLABLE mCallable;
-};
-
-template <typename CALLABLE>
-LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable)
-{
- // return false to schedule recurring calls
- return new Generic<CALLABLE>(period, false, callable);
-}
-
-template <typename CALLABLE>
-LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable)
-{
- // return true for one-shot callback
- return new Generic<CALLABLE>(time, true, callable);
-}
-
-template <typename CALLABLE>
-LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable)
-{
- // one-shot callback after specified interval
- return new Generic<CALLABLE>(interval, true, callable);
-}
-
-#endif //LL_EVENTTIMER_H
+/** + * @file lleventtimer.h + * @brief Cross-platform objects for doing timing + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_EVENTTIMER_H +#define LL_EVENTTIMER_H + +#include "stdtypes.h" +#include "lldate.h" +#include "llinstancetracker.h" +#include "lltimer.h" + +// class for scheduling a function to be called at a given frequency (approximate, inprecise) +class LL_COMMON_API LLEventTimer : public LLInstanceTracker<LLEventTimer> +{ +public: + + LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds + LLEventTimer(const LLDate& time); + virtual ~LLEventTimer(); + + //function to be called at the supplied frequency + // Normally return FALSE; TRUE will delete the timer after the function returns. + virtual bool tick() = 0; + + static void updateClass(); + + /// Schedule recurring calls to generic callable every period seconds. + /// Returns a pointer; if you delete it, cancels the recurring calls. + template <typename CALLABLE> + static LLEventTimer* run_every(F32 period, const CALLABLE& callable); + + /// Schedule a future call to generic callable. Returns a pointer. + /// CAUTION: The object referenced by that pointer WILL BE DELETED once + /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT + /// pointer->getInstance(pointer)!) can be used to test whether the + /// pointer is still valid. If it is, deleting it will cancel the + /// callback. + template <typename CALLABLE> + static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable); + + /// Like run_at(), but after a time delta rather than at a timestamp. + /// Same CAUTION. + template <typename CALLABLE> + static LLEventTimer* run_after(F32 interval, const CALLABLE& callable); + +protected: + LLTimer mEventTimer; + F32 mPeriod; + +private: + template <typename CALLABLE> + class Generic; +}; + +template <typename CALLABLE> +class LLEventTimer::Generic: public LLEventTimer +{ +public: + // making TIME generic allows engaging either LLEventTimer constructor + template <typename TIME> + Generic(const TIME& time, bool once, const CALLABLE& callable): + LLEventTimer(time), + mOnce(once), + mCallable(callable) + {} + bool tick() override + { + mCallable(); + // true tells updateClass() to delete this instance + return mOnce; + } + +private: + bool mOnce; + CALLABLE mCallable; +}; + +template <typename CALLABLE> +LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable) +{ + // return false to schedule recurring calls + return new Generic<CALLABLE>(period, false, callable); +} + +template <typename CALLABLE> +LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable) +{ + // return true for one-shot callback + return new Generic<CALLABLE>(time, true, callable); +} + +template <typename CALLABLE> +LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable) +{ + // one-shot callback after specified interval + return new Generic<CALLABLE>(interval, true, callable); +} + +#endif //LL_EVENTTIMER_H diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp index 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 <map>
-
-
-// forward declaration so LLKeyThrottleImpl can befriend it
-template <class T> class LLKeyThrottle;
-
-
-// Implementation utility class - use LLKeyThrottle, not this
-template <class T>
-class LLKeyThrottleImpl
-{
- friend class LLKeyThrottle<T>;
-protected:
- struct Entry {
- U32 count;
- bool blocked;
-
- Entry() : count(0), blocked(false) { }
- };
-
- typedef std::map<T, Entry> EntryMap;
-
- EntryMap* prevMap;
- EntryMap* currMap;
-
- U32 countLimit;
- // maximum number of keys allowed per interval
-
- U64 intervalLength; // each map covers this time period (usec or frame number)
- U64 startTime; // start of the time period (usec or frame number)
- // currMap started counting at this time
- // prevMap covers the previous interval
-
- LLKeyThrottleImpl() :
- prevMap(NULL),
- currMap(NULL),
- countLimit(0),
- intervalLength(1),
- startTime(0)
- {}
-
- static U64 getTime()
- {
- return LLFrameTimer::getTotalTime();
- }
- static U64 getFrame() // Return the current frame number
- {
- return (U64) LLFrameTimer::getFrameCount();
- }
-};
-
-
-template< class T >
-class LLKeyThrottle
-{
-public:
- // @param realtime = false for frame-based throttle, true for usec
- // real-time throttle
- LLKeyThrottle(U32 limit, F32 interval, bool realtime = true)
- : m(* new LLKeyThrottleImpl<T>)
- {
- setParameters( limit, interval, realtime );
- }
-
- ~LLKeyThrottle()
- {
- delete m.prevMap;
- delete m.currMap;
- delete &m;
- }
-
- enum State {
- THROTTLE_OK, // rate not exceeded, let pass
- THROTTLE_NEWLY_BLOCKED, // rate exceed for the first time
- THROTTLE_BLOCKED, // rate exceed, block key
- };
-
- F64 getActionCount(const T& id)
- {
- U64 now = 0;
- if ( mIsRealtime )
- {
- now = LLKeyThrottleImpl<T>::getTime();
- }
- else
- {
- now = LLKeyThrottleImpl<T>::getFrame();
- }
-
- if (now >= (m.startTime + m.intervalLength))
- {
- if (now < (m.startTime + 2 * m.intervalLength))
- {
- // prune old data
- delete m.prevMap;
- m.prevMap = m.currMap;
- m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
-
- m.startTime += m.intervalLength;
- }
- else
- {
- // lots of time has passed, all data is stale
- delete m.prevMap;
- delete m.currMap;
- m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap;
- m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
-
- m.startTime = now;
- }
- }
-
- U32 prevCount = 0;
-
- typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id);
- if (prev != m.prevMap->end())
- {
- prevCount = prev->second.count;
- }
-
- typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id];
-
- // curr.count is the number of keys in
- // this current 'time slice' from the beginning of it until now
- // prevCount is the number of keys in the previous
- // time slice scaled to be one full time slice back from the current
- // (now) time.
-
- // compute current, windowed rate
- F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength);
- F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent);
- return averageCount;
- }
-
- // call each time the key wants use
- State noteAction(const T& id, S32 weight = 1)
- {
- U64 now = 0;
- if ( mIsRealtime )
- {
- now = LLKeyThrottleImpl<T>::getTime();
- }
- else
- {
- now = LLKeyThrottleImpl<T>::getFrame();
- }
-
- if (now >= (m.startTime + m.intervalLength))
- {
- if (now < (m.startTime + 2 * m.intervalLength))
- {
- // prune old data
- delete m.prevMap;
- m.prevMap = m.currMap;
- m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
-
- m.startTime += m.intervalLength;
- }
- else
- {
- // lots of time has passed, all data is stale
- delete m.prevMap;
- delete m.currMap;
- m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap;
- m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
-
- m.startTime = now;
- }
- }
-
- U32 prevCount = 0;
- bool prevBlocked = false;
-
- typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id);
- if (prev != m.prevMap->end())
- {
- prevCount = prev->second.count;
- prevBlocked = prev->second.blocked;
- }
-
- typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id];
-
- bool wereBlocked = curr.blocked || prevBlocked;
-
- curr.count += weight;
-
- // curr.count is the number of keys in
- // this current 'time slice' from the beginning of it until now
- // prevCount is the number of keys in the previous
- // time slice scaled to be one full time slice back from the current
- // (now) time.
-
- // compute current, windowed rate
- F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength);
- F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent);
-
- curr.blocked |= averageCount > m.countLimit;
-
- bool nowBlocked = curr.blocked || prevBlocked;
-
- if (!nowBlocked)
- {
- return THROTTLE_OK;
- }
- else if (!wereBlocked)
- {
- return THROTTLE_NEWLY_BLOCKED;
- }
- else
- {
- return THROTTLE_BLOCKED;
- }
- }
-
- // call to force throttle conditions for id
- void throttleAction(const T& id)
- {
- noteAction(id);
- typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id];
- curr.count = llmax(m.countLimit, curr.count);
- curr.blocked = true;
- }
-
- // returns true if key is blocked
- bool isThrottled(const T& id) const
- {
- if (m.currMap->empty()
- && m.prevMap->empty())
- {
- // most of the time we'll fall in here
- return false;
- }
-
- // NOTE, we ignore the case where id is in the map but the map is stale.
- // You might think that we'd stop throttling things in such a case,
- // however it may be that a god has disabled scripts in the region or
- // estate --> we probably want to report the state of the id when the
- // scripting engine was paused.
- typename LLKeyThrottleImpl<T>::EntryMap::const_iterator entry = m.currMap->find(id);
- if (entry != m.currMap->end())
- {
- return entry->second.blocked;
- }
- entry = m.prevMap->find(id);
- if (entry != m.prevMap->end())
- {
- return entry->second.blocked;
- }
- return false;
- }
-
- // Get the throttling parameters
- void getParameters( U32 & out_limit, F32 & out_interval, bool & out_realtime )
- {
- out_limit = m.countLimit;
- out_interval = m.intervalLength;
- out_realtime = mIsRealtime;
- }
-
- // Set the throttling behavior
- void setParameters( U32 limit, F32 interval, bool realtime = true )
- {
- // limit is the maximum number of keys
- // allowed per interval (in seconds or frames)
- mIsRealtime = realtime;
- m.countLimit = limit;
- if ( mIsRealtime )
- {
- m.intervalLength = (U64)(interval * USEC_PER_SEC);
- m.startTime = LLKeyThrottleImpl<T>::getTime();
- }
- else
- {
- m.intervalLength = (U64)interval;
- m.startTime = LLKeyThrottleImpl<T>::getFrame();
- }
-
- if ( m.intervalLength == 0 )
- { // Don't allow zero intervals
- m.intervalLength = 1;
- }
-
- delete m.prevMap;
- m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap;
- delete m.currMap;
- m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap;
- }
-
-protected:
- LLKeyThrottleImpl<T>& m;
- bool mIsRealtime; // true to be time based (default), false for frame based
-};
-
-#endif
+/** + * @file llkeythrottle.h + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLKEY_THROTTLE_H +#define LL_LLKEY_THROTTLE_H + +// LLKeyThrottle keeps track of the number of action occurences with a key value +// for a type over a given time period. If the rate set in the constructor is +// exceeed, the key is considered blocked. The transition from unblocked to +// blocked is noted so the responsible agent can be informed. This transition +// takes twice the look back window to clear. + +#include "linden_common.h" + +#include "llframetimer.h" +#include <map> + + +// forward declaration so LLKeyThrottleImpl can befriend it +template <class T> class LLKeyThrottle; + + +// Implementation utility class - use LLKeyThrottle, not this +template <class T> +class LLKeyThrottleImpl +{ + friend class LLKeyThrottle<T>; +protected: + struct Entry { + U32 count; + bool blocked; + + Entry() : count(0), blocked(false) { } + }; + + typedef std::map<T, Entry> EntryMap; + + EntryMap* prevMap; + EntryMap* currMap; + + U32 countLimit; + // maximum number of keys allowed per interval + + U64 intervalLength; // each map covers this time period (usec or frame number) + U64 startTime; // start of the time period (usec or frame number) + // currMap started counting at this time + // prevMap covers the previous interval + + LLKeyThrottleImpl() : + prevMap(NULL), + currMap(NULL), + countLimit(0), + intervalLength(1), + startTime(0) + {} + + static U64 getTime() + { + return LLFrameTimer::getTotalTime(); + } + static U64 getFrame() // Return the current frame number + { + return (U64) LLFrameTimer::getFrameCount(); + } +}; + + +template< class T > +class LLKeyThrottle +{ +public: + // @param realtime = false for frame-based throttle, true for usec + // real-time throttle + LLKeyThrottle(U32 limit, F32 interval, bool realtime = true) + : m(* new LLKeyThrottleImpl<T>) + { + setParameters( limit, interval, realtime ); + } + + ~LLKeyThrottle() + { + delete m.prevMap; + delete m.currMap; + delete &m; + } + + enum State { + THROTTLE_OK, // rate not exceeded, let pass + THROTTLE_NEWLY_BLOCKED, // rate exceed for the first time + THROTTLE_BLOCKED, // rate exceed, block key + }; + + F64 getActionCount(const T& id) + { + U64 now = 0; + if ( mIsRealtime ) + { + now = LLKeyThrottleImpl<T>::getTime(); + } + else + { + now = LLKeyThrottleImpl<T>::getFrame(); + } + + if (now >= (m.startTime + m.intervalLength)) + { + if (now < (m.startTime + 2 * m.intervalLength)) + { + // prune old data + delete m.prevMap; + m.prevMap = m.currMap; + m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; + + m.startTime += m.intervalLength; + } + else + { + // lots of time has passed, all data is stale + delete m.prevMap; + delete m.currMap; + m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap; + m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; + + m.startTime = now; + } + } + + U32 prevCount = 0; + + typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id); + if (prev != m.prevMap->end()) + { + prevCount = prev->second.count; + } + + typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id]; + + // curr.count is the number of keys in + // this current 'time slice' from the beginning of it until now + // prevCount is the number of keys in the previous + // time slice scaled to be one full time slice back from the current + // (now) time. + + // compute current, windowed rate + F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); + F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); + return averageCount; + } + + // call each time the key wants use + State noteAction(const T& id, S32 weight = 1) + { + U64 now = 0; + if ( mIsRealtime ) + { + now = LLKeyThrottleImpl<T>::getTime(); + } + else + { + now = LLKeyThrottleImpl<T>::getFrame(); + } + + if (now >= (m.startTime + m.intervalLength)) + { + if (now < (m.startTime + 2 * m.intervalLength)) + { + // prune old data + delete m.prevMap; + m.prevMap = m.currMap; + m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; + + m.startTime += m.intervalLength; + } + else + { + // lots of time has passed, all data is stale + delete m.prevMap; + delete m.currMap; + m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap; + m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; + + m.startTime = now; + } + } + + U32 prevCount = 0; + bool prevBlocked = false; + + typename LLKeyThrottleImpl<T>::EntryMap::const_iterator prev = m.prevMap->find(id); + if (prev != m.prevMap->end()) + { + prevCount = prev->second.count; + prevBlocked = prev->second.blocked; + } + + typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id]; + + bool wereBlocked = curr.blocked || prevBlocked; + + curr.count += weight; + + // curr.count is the number of keys in + // this current 'time slice' from the beginning of it until now + // prevCount is the number of keys in the previous + // time slice scaled to be one full time slice back from the current + // (now) time. + + // compute current, windowed rate + F64 timeInCurrent = ((F64)(now - m.startTime) / m.intervalLength); + F64 averageCount = curr.count + prevCount * (1.0 - timeInCurrent); + + curr.blocked |= averageCount > m.countLimit; + + bool nowBlocked = curr.blocked || prevBlocked; + + if (!nowBlocked) + { + return THROTTLE_OK; + } + else if (!wereBlocked) + { + return THROTTLE_NEWLY_BLOCKED; + } + else + { + return THROTTLE_BLOCKED; + } + } + + // call to force throttle conditions for id + void throttleAction(const T& id) + { + noteAction(id); + typename LLKeyThrottleImpl<T>::Entry& curr = (*m.currMap)[id]; + curr.count = llmax(m.countLimit, curr.count); + curr.blocked = true; + } + + // returns true if key is blocked + bool isThrottled(const T& id) const + { + if (m.currMap->empty() + && m.prevMap->empty()) + { + // most of the time we'll fall in here + return false; + } + + // NOTE, we ignore the case where id is in the map but the map is stale. + // You might think that we'd stop throttling things in such a case, + // however it may be that a god has disabled scripts in the region or + // estate --> we probably want to report the state of the id when the + // scripting engine was paused. + typename LLKeyThrottleImpl<T>::EntryMap::const_iterator entry = m.currMap->find(id); + if (entry != m.currMap->end()) + { + return entry->second.blocked; + } + entry = m.prevMap->find(id); + if (entry != m.prevMap->end()) + { + return entry->second.blocked; + } + return false; + } + + // Get the throttling parameters + void getParameters( U32 & out_limit, F32 & out_interval, bool & out_realtime ) + { + out_limit = m.countLimit; + out_interval = m.intervalLength; + out_realtime = mIsRealtime; + } + + // Set the throttling behavior + void setParameters( U32 limit, F32 interval, bool realtime = true ) + { + // limit is the maximum number of keys + // allowed per interval (in seconds or frames) + mIsRealtime = realtime; + m.countLimit = limit; + if ( mIsRealtime ) + { + m.intervalLength = (U64)(interval * USEC_PER_SEC); + m.startTime = LLKeyThrottleImpl<T>::getTime(); + } + else + { + m.intervalLength = (U64)interval; + m.startTime = LLKeyThrottleImpl<T>::getFrame(); + } + + if ( m.intervalLength == 0 ) + { // Don't allow zero intervals + m.intervalLength = 1; + } + + delete m.prevMap; + m.prevMap = new typename LLKeyThrottleImpl<T>::EntryMap; + delete m.currMap; + m.currMap = new typename LLKeyThrottleImpl<T>::EntryMap; + } + +protected: + LLKeyThrottleImpl<T>& m; + bool mIsRealtime; // true to be time based (default), false for frame based +}; + +#endif diff --git a/indra/llcommon/lllivefile.cpp b/indra/llcommon/lllivefile.cpp index 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 <psapi.h>
-#elif defined(LL_DARWIN)
-# include <sys/types.h>
-# include <mach/task.h>
-# include <mach/mach_init.h>
-#include <mach/mach_host.h>
-#elif LL_LINUX
-# include <unistd.h>
-# include <sys/resource.h>
-#endif
-
-#include "llmemory.h"
-
-#include "llsys.h"
-#include "llframetimer.h"
-#include "lltrace.h"
-#include "llerror.h"
-//----------------------------------------------------------------------------
-
-//static
-U32Kilobytes LLMemory::sAvailPhysicalMemInKB(U32_MAX);
-U32Kilobytes LLMemory::sMaxPhysicalMemInKB(0);
-static LLTrace::SampleStatHandle<F64Megabytes> sAllocatedMem("allocated_mem", "active memory in use by application");
-static LLTrace::SampleStatHandle<F64Megabytes> sVirtualMem("virtual_mem", "virtual memory assigned to application");
-U32Kilobytes LLMemory::sAllocatedMemInKB(0);
-U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0);
-U32Kilobytes LLMemory::sMaxHeapSizeInKB(U32_MAX);
-
-void ll_assert_aligned_func(uintptr_t ptr,U32 alignment)
-{
-#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN)
- //do not check
- return;
-#else
- #ifdef SHOW_ASSERT
- // Redundant, place to set breakpoints.
- if (ptr%alignment!=0)
- {
- LL_WARNS() << "alignment check failed" << LL_ENDL;
- }
- llassert(ptr%alignment==0);
- #endif
-#endif
-}
-
-//static
-void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size)
-{
- sMaxHeapSizeInKB = U32Kilobytes::convert(max_heap_size);
-}
-
-//static
-void LLMemory::updateMemoryInfo()
-{
- LL_PROFILE_ZONE_SCOPED
-#if LL_WINDOWS
- PROCESS_MEMORY_COUNTERS counters;
-
- if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters)))
- {
- LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL;
- return ;
- }
-
- sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize));
- sample(sAllocatedMem, sAllocatedMemInKB);
- sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage));
- sample(sVirtualMem, sAllocatedPageSizeInKB);
-
- U32Kilobytes avail_phys, avail_virtual;
- LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ;
- sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB);
-
- if(sMaxPhysicalMemInKB > sAllocatedMemInKB)
- {
- sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ;
- }
- else
- {
- sAvailPhysicalMemInKB = U32Kilobytes(0);
- }
-
-#elif defined(LL_DARWIN)
- task_vm_info info;
- mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT;
- // MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint.
- if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&info), &infoCount) == KERN_SUCCESS)
- {
- // Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of
- // memory that the memory manager has committed for a running process", which is rss.
- sAllocatedPageSizeInKB = U32Bytes(info.resident_size);
-
- // Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size
- // Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less.
- // I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals.
- //
- // In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value
- // reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint.
- //
- // (On Windows, we use WorkingSetSize.)
- sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable);
- }
- else
- {
- LL_WARNS() << "task_info failed" << LL_ENDL;
- }
-
- // Total installed and available physical memory are properties of the host, not just our process.
- vm_statistics64_data_t vmstat;
- mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
- mach_port_t host = mach_host_self();
- vm_size_t page_size;
- host_page_size(host, &page_size);
- kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count);
- if (result == KERN_SUCCESS) {
- // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.'
- // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure.
- sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size);
- sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize();
- }
- else
- {
- LL_WARNS() << "task_info failed" << LL_ENDL;
- }
-
-#else
- //not valid for other systems for now.
- sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS());
- sMaxPhysicalMemInKB = U64Bytes(U32_MAX);
- sAvailPhysicalMemInKB = U64Bytes(U32_MAX);
-#endif
-
- return ;
-}
-
-//
-//this function is to test if there is enough space with the size in the virtual address space.
-//it does not do any real allocation
-//if success, it returns the address where the memory chunk can fit in;
-//otherwise it returns NULL.
-//
-//static
-void* LLMemory::tryToAlloc(void* address, U32 size)
-{
-#if LL_WINDOWS
- address = VirtualAlloc(address, size, MEM_RESERVE | MEM_TOP_DOWN, PAGE_NOACCESS) ;
- if(address)
- {
- if(!VirtualFree(address, 0, MEM_RELEASE))
- {
- LL_ERRS() << "error happens when free some memory reservation." << LL_ENDL ;
- }
- }
- return address ;
-#else
- return (void*)0x01 ; //skip checking
-#endif
-}
-
-//static
-void LLMemory::logMemoryInfo(bool update)
-{
- LL_PROFILE_ZONE_SCOPED
- if(update)
- {
- updateMemoryInfo() ;
- }
-
- LL_INFOS() << "Current allocated physical memory(KB): " << sAllocatedMemInKB << LL_ENDL ;
- LL_INFOS() << "Current allocated page size (KB): " << sAllocatedPageSizeInKB << LL_ENDL ;
- LL_INFOS() << "Current available physical memory(KB): " << sAvailPhysicalMemInKB << LL_ENDL ;
- LL_INFOS() << "Current max usable memory(KB): " << sMaxPhysicalMemInKB << LL_ENDL ;
-}
-
-//static
-U32Kilobytes LLMemory::getAvailableMemKB()
-{
- return sAvailPhysicalMemInKB ;
-}
-
-//static
-U32Kilobytes LLMemory::getMaxMemKB()
-{
- return sMaxPhysicalMemInKB ;
-}
-
-//static
-U32Kilobytes LLMemory::getAllocatedMemKB()
-{
- return sAllocatedMemInKB ;
-}
-
-//----------------------------------------------------------------------------
-
-#if defined(LL_WINDOWS)
-
-//static
-U64 LLMemory::getCurrentRSS()
-{
- PROCESS_MEMORY_COUNTERS counters;
-
- if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters)))
- {
- LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL;
- return 0;
- }
-
- return counters.WorkingSetSize;
-}
-
-#elif defined(LL_DARWIN)
-
-// if (sysctl(ctl, 2, &page_size, &size, NULL, 0) == -1)
-// {
-// LL_WARNS() << "Couldn't get page size" << LL_ENDL;
-// return 0;
-// } else {
-// return page_size;
-// }
-// }
-
-U64 LLMemory::getCurrentRSS()
-{
- U64 residentSize = 0;
- mach_task_basic_info_data_t basicInfo;
- mach_msg_type_number_t basicInfoCount = MACH_TASK_BASIC_INFO_COUNT;
- if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&basicInfo, &basicInfoCount) == KERN_SUCCESS)
- {
- residentSize = basicInfo.resident_size;
- // 64-bit macos apps allocate 32 GB or more at startup, and this is reflected in virtual_size.
- // basicInfo.virtual_size is not what we want.
- }
- else
- {
- LL_WARNS() << "task_info failed" << LL_ENDL;
- }
-
- return residentSize;
-}
-
-#elif defined(LL_LINUX)
-
-U64 LLMemory::getCurrentRSS()
-{
- struct rusage usage;
-
- if (getrusage(RUSAGE_SELF, &usage) != 0) {
- // Error handling code could be here
- return 0;
- }
-
- // ru_maxrss (since Linux 2.6.32)
- // This is the maximum resident set size used (in kilobytes).
- return usage.ru_maxrss * 1024;
-}
-
-#else
-
-U64 LLMemory::getCurrentRSS()
-{
- return 0;
-}
-
-#endif
-
-//--------------------------------------------------------------------
-
-#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN)
-
-#include <map>
-
-struct mem_info {
- std::map<void*, void*> memory_info;
- LLMutex mutex;
-
- static mem_info& get() {
- static mem_info instance;
- return instance;
- }
-
-private:
- mem_info(){}
-};
-
-void* ll_aligned_malloc_fallback( size_t size, int align )
-{
- SYSTEM_INFO sysinfo;
- GetSystemInfo(&sysinfo);
-
- unsigned int for_alloc = (size/sysinfo.dwPageSize + !!(size%sysinfo.dwPageSize)) * sysinfo.dwPageSize;
-
- void *p = VirtualAlloc(NULL, for_alloc+sysinfo.dwPageSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
- if(NULL == p) {
- // call debugger
- __asm int 3;
- }
- DWORD old;
- bool Res = VirtualProtect((void*)((char*)p + for_alloc), sysinfo.dwPageSize, PAGE_NOACCESS, &old);
- if(false == Res) {
- // call debugger
- __asm int 3;
- }
-
- void* ret = (void*)((char*)p + for_alloc-size);
-
- {
- LLMutexLock lock(&mem_info::get().mutex);
- mem_info::get().memory_info.insert(std::pair<void*, void*>(ret, p));
- }
-
-
- return ret;
-}
-
-void ll_aligned_free_fallback( void* ptr )
-{
- LLMutexLock lock(&mem_info::get().mutex);
- VirtualFree(mem_info::get().memory_info.find(ptr)->second, 0, MEM_RELEASE);
- mem_info::get().memory_info.erase(ptr);
-}
-
-#endif
+/** + * @file llmemory.cpp + * @brief Very special memory allocation/deallocation stuff here + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + + +#include "llthread.h" + +#if defined(LL_WINDOWS) +# include <psapi.h> +#elif defined(LL_DARWIN) +# include <sys/types.h> +# include <mach/task.h> +# include <mach/mach_init.h> +#include <mach/mach_host.h> +#elif LL_LINUX +# include <unistd.h> +# include <sys/resource.h> +#endif + +#include "llmemory.h" + +#include "llsys.h" +#include "llframetimer.h" +#include "lltrace.h" +#include "llerror.h" +//---------------------------------------------------------------------------- + +//static +U32Kilobytes LLMemory::sAvailPhysicalMemInKB(U32_MAX); +U32Kilobytes LLMemory::sMaxPhysicalMemInKB(0); +static LLTrace::SampleStatHandle<F64Megabytes> sAllocatedMem("allocated_mem", "active memory in use by application"); +static LLTrace::SampleStatHandle<F64Megabytes> sVirtualMem("virtual_mem", "virtual memory assigned to application"); +U32Kilobytes LLMemory::sAllocatedMemInKB(0); +U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0); +U32Kilobytes LLMemory::sMaxHeapSizeInKB(U32_MAX); + +void ll_assert_aligned_func(uintptr_t ptr,U32 alignment) +{ +#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN) + //do not check + return; +#else + #ifdef SHOW_ASSERT + // Redundant, place to set breakpoints. + if (ptr%alignment!=0) + { + LL_WARNS() << "alignment check failed" << LL_ENDL; + } + llassert(ptr%alignment==0); + #endif +#endif +} + +//static +void LLMemory::initMaxHeapSizeGB(F32Gigabytes max_heap_size) +{ + sMaxHeapSizeInKB = U32Kilobytes::convert(max_heap_size); +} + +//static +void LLMemory::updateMemoryInfo() +{ + LL_PROFILE_ZONE_SCOPED +#if LL_WINDOWS + PROCESS_MEMORY_COUNTERS counters; + + if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) + { + LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL; + return ; + } + + sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize)); + sample(sAllocatedMem, sAllocatedMemInKB); + sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage)); + sample(sVirtualMem, sAllocatedPageSizeInKB); + + U32Kilobytes avail_phys, avail_virtual; + LLMemoryInfo::getAvailableMemoryKB(avail_phys, avail_virtual) ; + sMaxPhysicalMemInKB = llmin(avail_phys + sAllocatedMemInKB, sMaxHeapSizeInKB); + + if(sMaxPhysicalMemInKB > sAllocatedMemInKB) + { + sAvailPhysicalMemInKB = sMaxPhysicalMemInKB - sAllocatedMemInKB ; + } + else + { + sAvailPhysicalMemInKB = U32Kilobytes(0); + } + +#elif defined(LL_DARWIN) + task_vm_info info; + mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT; + // MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint. + if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&info), &infoCount) == KERN_SUCCESS) + { + // Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of + // memory that the memory manager has committed for a running process", which is rss. + sAllocatedPageSizeInKB = U32Bytes(info.resident_size); + + // Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size + // Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less. + // I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals. + // + // In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value + // reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint. + // + // (On Windows, we use WorkingSetSize.) + sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable); + } + else + { + LL_WARNS() << "task_info failed" << LL_ENDL; + } + + // Total installed and available physical memory are properties of the host, not just our process. + vm_statistics64_data_t vmstat; + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + mach_port_t host = mach_host_self(); + vm_size_t page_size; + host_page_size(host, &page_size); + kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count); + if (result == KERN_SUCCESS) { + // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.' + // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure. + sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size); + sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize(); + } + else + { + LL_WARNS() << "task_info failed" << LL_ENDL; + } + +#else + //not valid for other systems for now. + sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS()); + sMaxPhysicalMemInKB = U64Bytes(U32_MAX); + sAvailPhysicalMemInKB = U64Bytes(U32_MAX); +#endif + + return ; +} + +// +//this function is to test if there is enough space with the size in the virtual address space. +//it does not do any real allocation +//if success, it returns the address where the memory chunk can fit in; +//otherwise it returns NULL. +// +//static +void* LLMemory::tryToAlloc(void* address, U32 size) +{ +#if LL_WINDOWS + address = VirtualAlloc(address, size, MEM_RESERVE | MEM_TOP_DOWN, PAGE_NOACCESS) ; + if(address) + { + if(!VirtualFree(address, 0, MEM_RELEASE)) + { + LL_ERRS() << "error happens when free some memory reservation." << LL_ENDL ; + } + } + return address ; +#else + return (void*)0x01 ; //skip checking +#endif +} + +//static +void LLMemory::logMemoryInfo(bool update) +{ + LL_PROFILE_ZONE_SCOPED + if(update) + { + updateMemoryInfo() ; + } + + LL_INFOS() << "Current allocated physical memory(KB): " << sAllocatedMemInKB << LL_ENDL ; + LL_INFOS() << "Current allocated page size (KB): " << sAllocatedPageSizeInKB << LL_ENDL ; + LL_INFOS() << "Current available physical memory(KB): " << sAvailPhysicalMemInKB << LL_ENDL ; + LL_INFOS() << "Current max usable memory(KB): " << sMaxPhysicalMemInKB << LL_ENDL ; +} + +//static +U32Kilobytes LLMemory::getAvailableMemKB() +{ + return sAvailPhysicalMemInKB ; +} + +//static +U32Kilobytes LLMemory::getMaxMemKB() +{ + return sMaxPhysicalMemInKB ; +} + +//static +U32Kilobytes LLMemory::getAllocatedMemKB() +{ + return sAllocatedMemInKB ; +} + +//---------------------------------------------------------------------------- + +#if defined(LL_WINDOWS) + +//static +U64 LLMemory::getCurrentRSS() +{ + PROCESS_MEMORY_COUNTERS counters; + + if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters))) + { + LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL; + return 0; + } + + return counters.WorkingSetSize; +} + +#elif defined(LL_DARWIN) + +// if (sysctl(ctl, 2, &page_size, &size, NULL, 0) == -1) +// { +// LL_WARNS() << "Couldn't get page size" << LL_ENDL; +// return 0; +// } else { +// return page_size; +// } +// } + +U64 LLMemory::getCurrentRSS() +{ + U64 residentSize = 0; + mach_task_basic_info_data_t basicInfo; + mach_msg_type_number_t basicInfoCount = MACH_TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&basicInfo, &basicInfoCount) == KERN_SUCCESS) + { + residentSize = basicInfo.resident_size; + // 64-bit macos apps allocate 32 GB or more at startup, and this is reflected in virtual_size. + // basicInfo.virtual_size is not what we want. + } + else + { + LL_WARNS() << "task_info failed" << LL_ENDL; + } + + return residentSize; +} + +#elif defined(LL_LINUX) + +U64 LLMemory::getCurrentRSS() +{ + struct rusage usage; + + if (getrusage(RUSAGE_SELF, &usage) != 0) { + // Error handling code could be here + return 0; + } + + // ru_maxrss (since Linux 2.6.32) + // This is the maximum resident set size used (in kilobytes). + return usage.ru_maxrss * 1024; +} + +#else + +U64 LLMemory::getCurrentRSS() +{ + return 0; +} + +#endif + +//-------------------------------------------------------------------- + +#if defined(LL_WINDOWS) && defined(LL_DEBUG_BUFFER_OVERRUN) + +#include <map> + +struct mem_info { + std::map<void*, void*> memory_info; + LLMutex mutex; + + static mem_info& get() { + static mem_info instance; + return instance; + } + +private: + mem_info(){} +}; + +void* ll_aligned_malloc_fallback( size_t size, int align ) +{ + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + + unsigned int for_alloc = (size/sysinfo.dwPageSize + !!(size%sysinfo.dwPageSize)) * sysinfo.dwPageSize; + + void *p = VirtualAlloc(NULL, for_alloc+sysinfo.dwPageSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); + if(NULL == p) { + // call debugger + __asm int 3; + } + DWORD old; + bool Res = VirtualProtect((void*)((char*)p + for_alloc), sysinfo.dwPageSize, PAGE_NOACCESS, &old); + if(false == Res) { + // call debugger + __asm int 3; + } + + void* ret = (void*)((char*)p + for_alloc-size); + + { + LLMutexLock lock(&mem_info::get().mutex); + mem_info::get().memory_info.insert(std::pair<void*, void*>(ret, p)); + } + + + return ret; +} + +void ll_aligned_free_fallback( void* ptr ) +{ + LLMutexLock lock(&mem_info::get().mutex); + VirtualFree(mem_info::get().memory_info.find(ptr)->second, 0, MEM_RELEASE); + mem_info::get().memory_info.erase(ptr); +} + +#endif diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index 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 <stdint.h>
-#endif
-
-class LLMutex ;
-
-#if LL_WINDOWS && LL_DEBUG
-#define LL_CHECK_MEMORY llassert(_CrtCheckMemory());
-#else
-#define LL_CHECK_MEMORY
-#endif
-
-
-#if LL_WINDOWS
-#define LL_ALIGN_OF __alignof
-#else
-#define LL_ALIGN_OF __align_of__
-#endif
-
-#if LL_WINDOWS
-#define LL_DEFAULT_HEAP_ALIGN 8
-#elif LL_DARWIN
-#define LL_DEFAULT_HEAP_ALIGN 16
-#elif LL_LINUX
-#define LL_DEFAULT_HEAP_ALIGN 8
-#endif
-
-
-LL_COMMON_API void ll_assert_aligned_func(uintptr_t ptr,U32 alignment);
-
-#ifdef SHOW_ASSERT
-// This is incredibly expensive - in profiling Windows RWD builds, 30%
-// of CPU time was in aligment checks.
-//#define ASSERT_ALIGNMENT
-#endif
-
-#ifdef ASSERT_ALIGNMENT
-#define ll_assert_aligned(ptr,alignment) ll_assert_aligned_func(uintptr_t(ptr),((U32)alignment))
-#else
-#define ll_assert_aligned(ptr,alignment)
-#endif
-
-#include <xmmintrin.h>
-
-template <typename T> T* LL_NEXT_ALIGNED_ADDRESS(T* address)
-{
- return reinterpret_cast<T*>(
- (uintptr_t(address) + 0xF) & ~0xF);
-}
-
-template <typename T> T* LL_NEXT_ALIGNED_ADDRESS_64(T* address)
-{
- return reinterpret_cast<T*>(
- (uintptr_t(address) + 0x3F) & ~0x3F);
-}
-
-#if LL_LINUX || LL_DARWIN
-
-#define LL_ALIGN_PREFIX(x)
-#define LL_ALIGN_POSTFIX(x) __attribute__((aligned(x)))
-
-#elif LL_WINDOWS
-
-#define LL_ALIGN_PREFIX(x) __declspec(align(x))
-#define LL_ALIGN_POSTFIX(x)
-
-#else
-#error "LL_ALIGN_PREFIX and LL_ALIGN_POSTFIX undefined"
-#endif
-
-#define LL_ALIGN_16(var) LL_ALIGN_PREFIX(16) var LL_ALIGN_POSTFIX(16)
-
-#define LL_ALIGN_NEW \
-public: \
- void* operator new(size_t size) \
- { \
- return ll_aligned_malloc_16(size); \
- } \
- \
- void operator delete(void* ptr) \
- { \
- ll_aligned_free_16(ptr); \
- } \
- \
- void* operator new[](size_t size) \
- { \
- return ll_aligned_malloc_16(size); \
- } \
- \
- void operator delete[](void* ptr) \
- { \
- ll_aligned_free_16(ptr); \
- }
-
-
-//------------------------------------------------------------------------------------------------
-//------------------------------------------------------------------------------------------------
- // for enable buffer overrun detection predefine LL_DEBUG_BUFFER_OVERRUN in current library
- // change preprocessor code to: #if 1 && defined(LL_WINDOWS)
-
-#if 0 && defined(LL_WINDOWS)
- void* ll_aligned_malloc_fallback( size_t size, int align );
- void ll_aligned_free_fallback( void* ptr );
-//------------------------------------------------------------------------------------------------
-#else
- inline void* ll_aligned_malloc_fallback( size_t size, int align )
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
- #if defined(LL_WINDOWS)
- void* ret = _aligned_malloc(size, align);
- #else
- char* aligned = NULL;
- void* mem = malloc( size + (align - 1) + sizeof(void*) );
- if (mem)
- {
- aligned = ((char*)mem) + sizeof(void*);
- aligned += align - ((uintptr_t)aligned & (align - 1));
-
- ((void**)aligned)[-1] = mem;
- }
- void* ret = aligned;
- #endif
- LL_PROFILE_ALLOC(ret, size);
- return ret;
- }
-
- inline void ll_aligned_free_fallback( void* ptr )
- {
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
- LL_PROFILE_FREE(ptr);
- #if defined(LL_WINDOWS)
- _aligned_free(ptr);
- #else
- if (ptr)
- {
- free( ((void**)ptr)[-1] );
- }
- #endif
- }
-#endif
-//------------------------------------------------------------------------------------------------
-//------------------------------------------------------------------------------------------------
-
-inline void* ll_aligned_malloc_16(size_t size) // returned hunk MUST be freed with ll_aligned_free_16().
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
-#if defined(LL_WINDOWS)
- void* ret = _aligned_malloc(size, 16);
-#elif defined(LL_DARWIN)
- void* ret = malloc(size); // default osx malloc is 16 byte aligned.
-#else
- void *ret;
- if (0 != posix_memalign(&ret, 16, size))
- return nullptr;
-#endif
- LL_PROFILE_ALLOC(ret, size);
- return ret;
-}
-
-inline void ll_aligned_free_16(void *p)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
- LL_PROFILE_FREE(p);
-#if defined(LL_WINDOWS)
- _aligned_free(p);
-#elif defined(LL_DARWIN)
- return free(p);
-#else
- free(p); // posix_memalign() is compatible with heap deallocator
-#endif
-}
-
-inline void* ll_aligned_realloc_16(void* ptr, size_t size, size_t old_size) // returned hunk MUST be freed with ll_aligned_free_16().
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
- LL_PROFILE_FREE(ptr);
-#if defined(LL_WINDOWS)
- void* ret = _aligned_realloc(ptr, size, 16);
-#elif defined(LL_DARWIN)
- void* ret = realloc(ptr,size); // default osx malloc is 16 byte aligned.
-#else
- //FIXME: memcpy is SLOW
- void* ret = ll_aligned_malloc_16(size);
- if (ptr)
- {
- if (ret)
- {
- // Only copy the size of the smallest memory block to avoid memory corruption.
- memcpy(ret, ptr, llmin(old_size, size));
- }
- ll_aligned_free_16(ptr);
- }
-#endif
- LL_PROFILE_ALLOC(ptr, size);
- return ret;
-}
-
-inline void* ll_aligned_malloc_32(size_t size) // returned hunk MUST be freed with ll_aligned_free_32().
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
-#if defined(LL_WINDOWS)
- void* ret = _aligned_malloc(size, 32);
-#elif defined(LL_DARWIN)
- void* ret = ll_aligned_malloc_fallback( size, 32 );
-#else
- void *ret;
- if (0 != posix_memalign(&ret, 32, size))
- return nullptr;
-#endif
- LL_PROFILE_ALLOC(ret, size);
- return ret;
-}
-
-inline void ll_aligned_free_32(void *p)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
- LL_PROFILE_FREE(p);
-#if defined(LL_WINDOWS)
- _aligned_free(p);
-#elif defined(LL_DARWIN)
- ll_aligned_free_fallback( p );
-#else
- free(p); // posix_memalign() is compatible with heap deallocator
-#endif
-}
-
-// general purpose dispatch functions that are forced inline so they can compile down to a single call
-template<size_t ALIGNMENT>
-LL_FORCE_INLINE void* ll_aligned_malloc(size_t size)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
- void* ret;
- if (LL_DEFAULT_HEAP_ALIGN % ALIGNMENT == 0)
- {
- ret = malloc(size);
- LL_PROFILE_ALLOC(ret, size);
- }
- else if (ALIGNMENT == 16)
- {
- ret = ll_aligned_malloc_16(size);
- }
- else if (ALIGNMENT == 32)
- {
- ret = ll_aligned_malloc_32(size);
- }
- else
- {
- ret = ll_aligned_malloc_fallback(size, ALIGNMENT);
- }
- return ret;
-}
-
-template<size_t ALIGNMENT>
-LL_FORCE_INLINE void ll_aligned_free(void* ptr)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
- if (ALIGNMENT == LL_DEFAULT_HEAP_ALIGN)
- {
- LL_PROFILE_FREE(ptr);
- free(ptr);
- }
- else if (ALIGNMENT == 16)
- {
- ll_aligned_free_16(ptr);
- }
- else if (ALIGNMENT == 32)
- {
- return ll_aligned_free_32(ptr);
- }
- else
- {
- return ll_aligned_free_fallback(ptr);
- }
-}
-
-// Copy words 16-byte blocks from src to dst. Source and destination MUST NOT OVERLAP.
-// Source and dest must be 16-byte aligned and size must be multiple of 16.
-//
-inline void ll_memcpy_nonaliased_aligned_16(char* __restrict dst, const char* __restrict src, size_t bytes)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
- assert(src != NULL);
- assert(dst != NULL);
- assert(bytes > 0);
- assert((bytes % sizeof(F32))== 0);
- ll_assert_aligned(src,16);
- ll_assert_aligned(dst,16);
-
- assert((src < dst) ? ((src + bytes) <= dst) : ((dst + bytes) <= src));
- assert(bytes%16==0);
-
- char* end = dst + bytes;
-
- if (bytes > 64)
- {
-
- // Find start of 64b aligned area within block
- //
- void* begin_64 = LL_NEXT_ALIGNED_ADDRESS_64(dst);
-
- //at least 64 bytes before the end of the destination, switch to 16 byte copies
- void* end_64 = end-64;
-
- // Prefetch the head of the 64b area now
- //
- _mm_prefetch((char*)begin_64, _MM_HINT_NTA);
- _mm_prefetch((char*)begin_64 + 64, _MM_HINT_NTA);
- _mm_prefetch((char*)begin_64 + 128, _MM_HINT_NTA);
- _mm_prefetch((char*)begin_64 + 192, _MM_HINT_NTA);
-
- // Copy 16b chunks until we're 64b aligned
- //
- while (dst < begin_64)
- {
-
- _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src));
- dst += 16;
- src += 16;
- }
-
- // Copy 64b chunks up to your tail
- //
- // might be good to shmoo the 512b prefetch offset
- // (characterize performance for various values)
- //
- while (dst < end_64)
- {
- _mm_prefetch((char*)src + 512, _MM_HINT_NTA);
- _mm_prefetch((char*)dst + 512, _MM_HINT_NTA);
- _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src));
- _mm_store_ps((F32*)(dst + 16), _mm_load_ps((F32*)(src + 16)));
- _mm_store_ps((F32*)(dst + 32), _mm_load_ps((F32*)(src + 32)));
- _mm_store_ps((F32*)(dst + 48), _mm_load_ps((F32*)(src + 48)));
- dst += 64;
- src += 64;
- }
- }
-
- // Copy remainder 16b tail chunks (or ALL 16b chunks for sub-64b copies)
- //
- while (dst < end)
- {
- _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src));
- dst += 16;
- src += 16;
- }
-}
-
-#ifndef __DEBUG_PRIVATE_MEM__
-#define __DEBUG_PRIVATE_MEM__ 0
-#endif
-
-class LL_COMMON_API LLMemory
-{
-public:
- // Return the resident set size of the current process, in bytes.
- // Return value is zero if not known.
- static U64 getCurrentRSS();
- static void* tryToAlloc(void* address, U32 size);
- static void initMaxHeapSizeGB(F32Gigabytes max_heap_size);
- static void updateMemoryInfo() ;
- static void logMemoryInfo(bool update = false);
-
- static U32Kilobytes getAvailableMemKB() ;
- static U32Kilobytes getMaxMemKB() ;
- static U32Kilobytes getAllocatedMemKB() ;
-private:
- static U32Kilobytes sAvailPhysicalMemInKB ;
- static U32Kilobytes sMaxPhysicalMemInKB ;
- static U32Kilobytes sAllocatedMemInKB;
- static U32Kilobytes sAllocatedPageSizeInKB ;
-
- static U32Kilobytes sMaxHeapSizeInKB;
-};
-
-// LLRefCount moved to llrefcount.h
-
-// LLPointer moved to llpointer.h
-
-// LLSafeHandle moved to llsafehandle.h
-
-// LLSingleton moved to llsingleton.h
-
-
-
-
-#endif
+/** + * @file llmemory.h + * @brief Memory allocation/deallocation header-stuff goes here. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LLMEMORY_H +#define LLMEMORY_H + +#include "linden_common.h" +#include "llunits.h" +#include "stdtypes.h" +#if !LL_WINDOWS +#include <stdint.h> +#endif + +class LLMutex ; + +#if LL_WINDOWS && LL_DEBUG +#define LL_CHECK_MEMORY llassert(_CrtCheckMemory()); +#else +#define LL_CHECK_MEMORY +#endif + + +#if LL_WINDOWS +#define LL_ALIGN_OF __alignof +#else +#define LL_ALIGN_OF __align_of__ +#endif + +#if LL_WINDOWS +#define LL_DEFAULT_HEAP_ALIGN 8 +#elif LL_DARWIN +#define LL_DEFAULT_HEAP_ALIGN 16 +#elif LL_LINUX +#define LL_DEFAULT_HEAP_ALIGN 8 +#endif + + +LL_COMMON_API void ll_assert_aligned_func(uintptr_t ptr,U32 alignment); + +#ifdef SHOW_ASSERT +// This is incredibly expensive - in profiling Windows RWD builds, 30% +// of CPU time was in aligment checks. +//#define ASSERT_ALIGNMENT +#endif + +#ifdef ASSERT_ALIGNMENT +#define ll_assert_aligned(ptr,alignment) ll_assert_aligned_func(uintptr_t(ptr),((U32)alignment)) +#else +#define ll_assert_aligned(ptr,alignment) +#endif + +#include <xmmintrin.h> + +template <typename T> T* LL_NEXT_ALIGNED_ADDRESS(T* address) +{ + return reinterpret_cast<T*>( + (uintptr_t(address) + 0xF) & ~0xF); +} + +template <typename T> T* LL_NEXT_ALIGNED_ADDRESS_64(T* address) +{ + return reinterpret_cast<T*>( + (uintptr_t(address) + 0x3F) & ~0x3F); +} + +#if LL_LINUX || LL_DARWIN + +#define LL_ALIGN_PREFIX(x) +#define LL_ALIGN_POSTFIX(x) __attribute__((aligned(x))) + +#elif LL_WINDOWS + +#define LL_ALIGN_PREFIX(x) __declspec(align(x)) +#define LL_ALIGN_POSTFIX(x) + +#else +#error "LL_ALIGN_PREFIX and LL_ALIGN_POSTFIX undefined" +#endif + +#define LL_ALIGN_16(var) LL_ALIGN_PREFIX(16) var LL_ALIGN_POSTFIX(16) + +#define LL_ALIGN_NEW \ +public: \ + void* operator new(size_t size) \ + { \ + return ll_aligned_malloc_16(size); \ + } \ + \ + void operator delete(void* ptr) \ + { \ + ll_aligned_free_16(ptr); \ + } \ + \ + void* operator new[](size_t size) \ + { \ + return ll_aligned_malloc_16(size); \ + } \ + \ + void operator delete[](void* ptr) \ + { \ + ll_aligned_free_16(ptr); \ + } + + +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ + // for enable buffer overrun detection predefine LL_DEBUG_BUFFER_OVERRUN in current library + // change preprocessor code to: #if 1 && defined(LL_WINDOWS) + +#if 0 && defined(LL_WINDOWS) + void* ll_aligned_malloc_fallback( size_t size, int align ); + void ll_aligned_free_fallback( void* ptr ); +//------------------------------------------------------------------------------------------------ +#else + inline void* ll_aligned_malloc_fallback( size_t size, int align ) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + #if defined(LL_WINDOWS) + void* ret = _aligned_malloc(size, align); + #else + char* aligned = NULL; + void* mem = malloc( size + (align - 1) + sizeof(void*) ); + if (mem) + { + aligned = ((char*)mem) + sizeof(void*); + aligned += align - ((uintptr_t)aligned & (align - 1)); + + ((void**)aligned)[-1] = mem; + } + void* ret = aligned; + #endif + LL_PROFILE_ALLOC(ret, size); + return ret; + } + + inline void ll_aligned_free_fallback( void* ptr ) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + LL_PROFILE_FREE(ptr); + #if defined(LL_WINDOWS) + _aligned_free(ptr); + #else + if (ptr) + { + free( ((void**)ptr)[-1] ); + } + #endif + } +#endif +//------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------ + +inline void* ll_aligned_malloc_16(size_t size) // returned hunk MUST be freed with ll_aligned_free_16(). +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; +#if defined(LL_WINDOWS) + void* ret = _aligned_malloc(size, 16); +#elif defined(LL_DARWIN) + void* ret = malloc(size); // default osx malloc is 16 byte aligned. +#else + void *ret; + if (0 != posix_memalign(&ret, 16, size)) + return nullptr; +#endif + LL_PROFILE_ALLOC(ret, size); + return ret; +} + +inline void ll_aligned_free_16(void *p) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + LL_PROFILE_FREE(p); +#if defined(LL_WINDOWS) + _aligned_free(p); +#elif defined(LL_DARWIN) + return free(p); +#else + free(p); // posix_memalign() is compatible with heap deallocator +#endif +} + +inline void* ll_aligned_realloc_16(void* ptr, size_t size, size_t old_size) // returned hunk MUST be freed with ll_aligned_free_16(). +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + LL_PROFILE_FREE(ptr); +#if defined(LL_WINDOWS) + void* ret = _aligned_realloc(ptr, size, 16); +#elif defined(LL_DARWIN) + void* ret = realloc(ptr,size); // default osx malloc is 16 byte aligned. +#else + //FIXME: memcpy is SLOW + void* ret = ll_aligned_malloc_16(size); + if (ptr) + { + if (ret) + { + // Only copy the size of the smallest memory block to avoid memory corruption. + memcpy(ret, ptr, llmin(old_size, size)); + } + ll_aligned_free_16(ptr); + } +#endif + LL_PROFILE_ALLOC(ptr, size); + return ret; +} + +inline void* ll_aligned_malloc_32(size_t size) // returned hunk MUST be freed with ll_aligned_free_32(). +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; +#if defined(LL_WINDOWS) + void* ret = _aligned_malloc(size, 32); +#elif defined(LL_DARWIN) + void* ret = ll_aligned_malloc_fallback( size, 32 ); +#else + void *ret; + if (0 != posix_memalign(&ret, 32, size)) + return nullptr; +#endif + LL_PROFILE_ALLOC(ret, size); + return ret; +} + +inline void ll_aligned_free_32(void *p) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + LL_PROFILE_FREE(p); +#if defined(LL_WINDOWS) + _aligned_free(p); +#elif defined(LL_DARWIN) + ll_aligned_free_fallback( p ); +#else + free(p); // posix_memalign() is compatible with heap deallocator +#endif +} + +// general purpose dispatch functions that are forced inline so they can compile down to a single call +template<size_t ALIGNMENT> +LL_FORCE_INLINE void* ll_aligned_malloc(size_t size) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + void* ret; + if (LL_DEFAULT_HEAP_ALIGN % ALIGNMENT == 0) + { + ret = malloc(size); + LL_PROFILE_ALLOC(ret, size); + } + else if (ALIGNMENT == 16) + { + ret = ll_aligned_malloc_16(size); + } + else if (ALIGNMENT == 32) + { + ret = ll_aligned_malloc_32(size); + } + else + { + ret = ll_aligned_malloc_fallback(size, ALIGNMENT); + } + return ret; +} + +template<size_t ALIGNMENT> +LL_FORCE_INLINE void ll_aligned_free(void* ptr) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + if (ALIGNMENT == LL_DEFAULT_HEAP_ALIGN) + { + LL_PROFILE_FREE(ptr); + free(ptr); + } + else if (ALIGNMENT == 16) + { + ll_aligned_free_16(ptr); + } + else if (ALIGNMENT == 32) + { + return ll_aligned_free_32(ptr); + } + else + { + return ll_aligned_free_fallback(ptr); + } +} + +// Copy words 16-byte blocks from src to dst. Source and destination MUST NOT OVERLAP. +// Source and dest must be 16-byte aligned and size must be multiple of 16. +// +inline void ll_memcpy_nonaliased_aligned_16(char* __restrict dst, const char* __restrict src, size_t bytes) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + assert(src != NULL); + assert(dst != NULL); + assert(bytes > 0); + assert((bytes % sizeof(F32))== 0); + ll_assert_aligned(src,16); + ll_assert_aligned(dst,16); + + assert((src < dst) ? ((src + bytes) <= dst) : ((dst + bytes) <= src)); + assert(bytes%16==0); + + char* end = dst + bytes; + + if (bytes > 64) + { + + // Find start of 64b aligned area within block + // + void* begin_64 = LL_NEXT_ALIGNED_ADDRESS_64(dst); + + //at least 64 bytes before the end of the destination, switch to 16 byte copies + void* end_64 = end-64; + + // Prefetch the head of the 64b area now + // + _mm_prefetch((char*)begin_64, _MM_HINT_NTA); + _mm_prefetch((char*)begin_64 + 64, _MM_HINT_NTA); + _mm_prefetch((char*)begin_64 + 128, _MM_HINT_NTA); + _mm_prefetch((char*)begin_64 + 192, _MM_HINT_NTA); + + // Copy 16b chunks until we're 64b aligned + // + while (dst < begin_64) + { + + _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); + dst += 16; + src += 16; + } + + // Copy 64b chunks up to your tail + // + // might be good to shmoo the 512b prefetch offset + // (characterize performance for various values) + // + while (dst < end_64) + { + _mm_prefetch((char*)src + 512, _MM_HINT_NTA); + _mm_prefetch((char*)dst + 512, _MM_HINT_NTA); + _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); + _mm_store_ps((F32*)(dst + 16), _mm_load_ps((F32*)(src + 16))); + _mm_store_ps((F32*)(dst + 32), _mm_load_ps((F32*)(src + 32))); + _mm_store_ps((F32*)(dst + 48), _mm_load_ps((F32*)(src + 48))); + dst += 64; + src += 64; + } + } + + // Copy remainder 16b tail chunks (or ALL 16b chunks for sub-64b copies) + // + while (dst < end) + { + _mm_store_ps((F32*)dst, _mm_load_ps((F32*)src)); + dst += 16; + src += 16; + } +} + +#ifndef __DEBUG_PRIVATE_MEM__ +#define __DEBUG_PRIVATE_MEM__ 0 +#endif + +class LL_COMMON_API LLMemory +{ +public: + // Return the resident set size of the current process, in bytes. + // Return value is zero if not known. + static U64 getCurrentRSS(); + static void* tryToAlloc(void* address, U32 size); + static void initMaxHeapSizeGB(F32Gigabytes max_heap_size); + static void updateMemoryInfo() ; + static void logMemoryInfo(bool update = false); + + static U32Kilobytes getAvailableMemKB() ; + static U32Kilobytes getMaxMemKB() ; + static U32Kilobytes getAllocatedMemKB() ; +private: + static U32Kilobytes sAvailPhysicalMemInKB ; + static U32Kilobytes sMaxPhysicalMemInKB ; + static U32Kilobytes sAllocatedMemInKB; + static U32Kilobytes sAllocatedPageSizeInKB ; + + static U32Kilobytes sMaxHeapSizeInKB; +}; + +// LLRefCount moved to llrefcount.h + +// LLPointer moved to llpointer.h + +// LLSafeHandle moved to llsafehandle.h + +// LLSingleton moved to llsingleton.h + + + + +#endif diff --git a/indra/llcommon/llmetricperformancetester.cpp b/indra/llcommon/llmetricperformancetester.cpp index 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 <list>
-
-std::list<LLMortician*> LLMortician::sGraveyard;
-
-bool LLMortician::sDestroyImmediate = false;
-
-LLMortician::~LLMortician()
-{
- sGraveyard.remove(this);
-}
-
-size_t LLMortician::logClass(std::stringstream &str)
-{
- auto size = sGraveyard.size();
- str << "Mortician graveyard count: " << size;
- str << " Zealous: " << (sDestroyImmediate ? "True" : "False");
- if (size == 0)
- {
- return size;
- }
- str << " Output:\n";
- std::list<LLMortician*>::iterator iter = sGraveyard.begin();
- std::list<LLMortician*>::iterator end = sGraveyard.end();
- while (iter!=end)
- {
- LLMortician* dead = *iter;
- iter++;
- // Be as detailed and safe as possible to figure out issues
- str << "Pointer: " << dead;
- if (dead)
- {
- try
- {
- str << " Is dead: " << (dead->isDead() ? "True" : "False");
- str << " Name: " << typeid(*dead).name();
- }
- catch (...)
- {
-
- }
- }
- str << "\n";
- }
- str << "--------------------------------------------";
- return size;
-}
-
-void LLMortician::updateClass()
-{
- while (!sGraveyard.empty())
- {
- LLMortician* dead = sGraveyard.front();
- delete dead;
- }
-}
-
-void LLMortician::die()
-{
- // It is valid to call die() more than once on something that hasn't died yet
- if (sDestroyImmediate)
- {
- // *NOTE: This is a hack to ensure destruction order on shutdown (relative to non-mortician controlled classes).
- mIsDead = true;
- delete this;
- return;
- }
- else if (!mIsDead)
- {
- mIsDead = true;
- sGraveyard.push_back(this);
- }
-}
-
-// static
-void LLMortician::setZealous(bool b)
-{
- sDestroyImmediate = b;
-}
+/** + * @file llmortician.cpp + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llmortician.h" + +#include <list> + +std::list<LLMortician*> LLMortician::sGraveyard; + +bool LLMortician::sDestroyImmediate = false; + +LLMortician::~LLMortician() +{ + sGraveyard.remove(this); +} + +size_t LLMortician::logClass(std::stringstream &str) +{ + auto size = sGraveyard.size(); + str << "Mortician graveyard count: " << size; + str << " Zealous: " << (sDestroyImmediate ? "True" : "False"); + if (size == 0) + { + return size; + } + str << " Output:\n"; + std::list<LLMortician*>::iterator iter = sGraveyard.begin(); + std::list<LLMortician*>::iterator end = sGraveyard.end(); + while (iter!=end) + { + LLMortician* dead = *iter; + iter++; + // Be as detailed and safe as possible to figure out issues + str << "Pointer: " << dead; + if (dead) + { + try + { + str << " Is dead: " << (dead->isDead() ? "True" : "False"); + str << " Name: " << typeid(*dead).name(); + } + catch (...) + { + + } + } + str << "\n"; + } + str << "--------------------------------------------"; + return size; +} + +void LLMortician::updateClass() +{ + while (!sGraveyard.empty()) + { + LLMortician* dead = sGraveyard.front(); + delete dead; + } +} + +void LLMortician::die() +{ + // It is valid to call die() more than once on something that hasn't died yet + if (sDestroyImmediate) + { + // *NOTE: This is a hack to ensure destruction order on shutdown (relative to non-mortician controlled classes). + mIsDead = true; + delete this; + return; + } + else if (!mIsDead) + { + mIsDead = true; + sGraveyard.push_back(this); + } +} + +// static +void LLMortician::setZealous(bool b) +{ + sDestroyImmediate = b; +} diff --git a/indra/llcommon/llmortician.h b/indra/llcommon/llmortician.h index 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 <list>
-
-class LL_COMMON_API LLMortician
-{
-public:
- LLMortician() { mIsDead = false; }
- static auto graveyardCount() { return sGraveyard.size(); };
- static size_t logClass(std::stringstream &str);
- static void updateClass();
- virtual ~LLMortician();
- void die();
- bool isDead() { return mIsDead; }
-
- // sets destroy immediate true
- static void setZealous(bool b);
-
-private:
- static bool sDestroyImmediate;
-
- bool mIsDead;
-
- static std::list<LLMortician*> sGraveyard;
-};
-
-#endif
-
+/** + * @file llmortician.h + * @brief Base class for delayed deletions. + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLMORTICIAN_H +#define LLMORTICIAN_H + +#include "stdtypes.h" +#include <list> + +class LL_COMMON_API LLMortician +{ +public: + LLMortician() { mIsDead = false; } + static auto graveyardCount() { return sGraveyard.size(); }; + static size_t logClass(std::stringstream &str); + static void updateClass(); + virtual ~LLMortician(); + void die(); + bool isDead() { return mIsDead; } + + // sets destroy immediate true + static void setZealous(bool b); + +private: + static bool sDestroyImmediate; + + bool mIsDead; + + static std::list<LLMortician*> sGraveyard; +}; + +#endif + diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp index 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<std::mutex> lock(mLockMutex);
-
- return !mLockingThreads.empty();
-}
-
-bool LLSharedMutex::isThreadLocked() const
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- LLThread::id_t current_thread = LLThread::currentID();
- std::lock_guard<std::mutex> lock(mLockMutex);
-
- const_iterator it = mLockingThreads.find(current_thread);
- return it != mLockingThreads.end();
-}
-
-void LLSharedMutex::lockShared()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- LLThread::id_t current_thread = LLThread::currentID();
-
- mLockMutex.lock();
- iterator it = mLockingThreads.find(current_thread);
- if (it != mLockingThreads.end())
- {
- it->second++;
- }
- else
- {
- // Acquire the mutex immediately if the mutex is not locked exclusively
- // or enter a locking state if the mutex is already locked exclusively
- mLockMutex.unlock();
- mSharedMutex.lock_shared();
- mLockMutex.lock();
- // Continue after acquiring the mutex
- mLockingThreads.emplace(std::make_pair(current_thread, 1));
- mIsShared = true;
- }
- mLockMutex.unlock();
-}
-
-void LLSharedMutex::lockExclusive()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- LLThread::id_t current_thread = LLThread::currentID();
-
- mLockMutex.lock();
- iterator it = mLockingThreads.find(current_thread);
- if (it != mLockingThreads.end())
- {
- if (mIsShared)
- {
- // The mutex is already locked in the current thread
- // but this lock is SHARED (not EXCLISIVE)
- // We can't lock it again, the lock stays shared
- // This can lead to a collision (theoretically)
- llassert_always(!"The current thread is already locked SHARED and can't be locked EXCLUSIVE");
- }
- it->second++;
- }
- else
- {
- // Acquire the mutex immediately if mLockingThreads is empty
- // or enter a locking state if mLockingThreads is not empty
- mLockMutex.unlock();
- mSharedMutex.lock();
- mLockMutex.lock();
- // Continue after acquiring the mutex (and possible quitting the locking state)
- mLockingThreads.emplace(std::make_pair(current_thread, 1));
- mIsShared = false;
- }
- mLockMutex.unlock();
-}
-
-bool LLSharedMutex::trylockShared()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- LLThread::id_t current_thread = LLThread::currentID();
- std::lock_guard<std::mutex> lock(mLockMutex);
-
- iterator it = mLockingThreads.find(current_thread);
- if (it != mLockingThreads.end())
- {
- it->second++;
- }
- else
- {
- if (!mSharedMutex.try_lock_shared())
- return false;
-
- mLockingThreads.emplace(std::make_pair(current_thread, 1));
- mIsShared = true;
- }
-
- return true;
-}
-
-bool LLSharedMutex::trylockExclusive()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- LLThread::id_t current_thread = LLThread::currentID();
- std::lock_guard<std::mutex> lock(mLockMutex);
-
- if (mLockingThreads.size() == 1 && mLockingThreads.begin()->first == current_thread)
- {
- mLockingThreads.begin()->second++;
- }
- else
- {
- if (!mSharedMutex.try_lock())
- return false;
-
- mLockingThreads.emplace(std::make_pair(current_thread, 1));
- mIsShared = false;
- }
-
- return true;
-}
-
-void LLSharedMutex::unlockShared()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- LLThread::id_t current_thread = LLThread::currentID();
- std::lock_guard<std::mutex> lock(mLockMutex);
-
- iterator it = mLockingThreads.find(current_thread);
- if (it != mLockingThreads.end())
- {
- if (it->second > 1)
- {
- it->second--;
- }
- else
- {
- mLockingThreads.erase(it);
- mSharedMutex.unlock_shared();
- }
- }
-}
-
-void LLSharedMutex::unlockExclusive()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- LLThread::id_t current_thread = LLThread::currentID();
- std::lock_guard<std::mutex> lock(mLockMutex);
-
- iterator it = mLockingThreads.find(current_thread);
- if (it != mLockingThreads.end())
- {
- if (it->second > 1)
- {
- it->second--;
- }
- else
- {
- mLockingThreads.erase(it);
- mSharedMutex.unlock();
- }
- }
-}
-
-
-//---------------------------------------------------------------------
-//
-// LLCondition
-//
-LLCondition::LLCondition() :
- LLMutex()
-{
-}
-
-LLCondition::~LLCondition()
-{
-}
-
-void LLCondition::wait()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- std::unique_lock< std::mutex > lock(mMutex);
- mCond.wait(lock);
-}
-
-void LLCondition::signal()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- mCond.notify_one();
-}
-
-void LLCondition::broadcast()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- mCond.notify_all();
-}
-
-
-//---------------------------------------------------------------------
-//
-// LLMutexTrylock
-//
-LLMutexTrylock::LLMutexTrylock(LLMutex* mutex)
- : mMutex(mutex),
- mLocked(false)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- if (mMutex)
- mLocked = mMutex->trylock();
-}
-
-LLMutexTrylock::LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms)
- : mMutex(mutex),
- mLocked(false)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- if (!mMutex)
- return;
-
- for (U32 i = 0; i < aTries; ++i)
- {
- mLocked = mMutex->trylock();
- if (mLocked)
- break;
- ms_sleep(delay_ms);
- }
-}
-
-LLMutexTrylock::~LLMutexTrylock()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- if (mMutex && mLocked)
- mMutex->unlock();
-}
-
-
-//---------------------------------------------------------------------
-//
-// LLScopedLock
-//
-LLScopedLock::LLScopedLock(std::mutex* mutex) : mMutex(mutex)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- if(mutex)
- {
- mutex->lock();
- mLocked = true;
- }
- else
- {
- mLocked = false;
- }
-}
-
-LLScopedLock::~LLScopedLock()
-{
- unlock();
-}
-
-void LLScopedLock::unlock()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- if(mLocked)
- {
- mMutex->unlock();
- mLocked = false;
- }
-}
-
-//============================================================================
+/** + * @file llmutex.cpp + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llmutex.h" +#include "llthread.h" +#include "lltimer.h" + + +//--------------------------------------------------------------------- +// +// LLMutex +// +LLMutex::LLMutex() : + mCount(0) +{ +} + +LLMutex::~LLMutex() +{ +} + +void LLMutex::lock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if(isSelfLocked()) + { //redundant lock + mCount++; + return; + } + + mMutex.lock(); + +#if MUTEX_DEBUG + // Have to have the lock before we can access the debug info + auto id = LLThread::currentID(); + if (mIsLocked[id]) + LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; + mIsLocked[id] = true; +#endif + + mLockingThread = LLThread::currentID(); +} + +void LLMutex::unlock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (mCount > 0) + { //not the root unlock + mCount--; + return; + } + +#if MUTEX_DEBUG + // Access the debug info while we have the lock + auto id = LLThread::currentID(); + if (!mIsLocked[id]) + LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL; + mIsLocked[id] = false; +#endif + + mLockingThread = LLThread::id_t(); + mMutex.unlock(); +} + +bool LLMutex::isLocked() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (!mMutex.try_lock()) + { + return true; + } + else + { + mMutex.unlock(); + return false; + } +} + +bool LLMutex::isSelfLocked() +{ + return mLockingThread == LLThread::currentID(); +} + +LLThread::id_t LLMutex::lockingThread() const +{ + return mLockingThread; +} + +bool LLMutex::trylock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (isSelfLocked()) + { //redundant lock + mCount++; + return true; + } + + if (!mMutex.try_lock()) + { + return false; + } + +#if MUTEX_DEBUG + // Have to have the lock before we can access the debug info + auto id = LLThread::currentID(); + if (mIsLocked[id]) + LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL; + mIsLocked[id] = true; +#endif + + mLockingThread = LLThread::currentID(); + return true; +} + + +//--------------------------------------------------------------------- +// +// LLSharedMutex +// +LLSharedMutex::LLSharedMutex() +: mLockingThreads(2) // Reserve 2 slots in the map hash table +, mIsShared(false) +{ +} + +bool LLSharedMutex::isLocked() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + std::lock_guard<std::mutex> lock(mLockMutex); + + return !mLockingThreads.empty(); +} + +bool LLSharedMutex::isThreadLocked() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard<std::mutex> lock(mLockMutex); + + const_iterator it = mLockingThreads.find(current_thread); + return it != mLockingThreads.end(); +} + +void LLSharedMutex::lockShared() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + + mLockMutex.lock(); + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + it->second++; + } + else + { + // Acquire the mutex immediately if the mutex is not locked exclusively + // or enter a locking state if the mutex is already locked exclusively + mLockMutex.unlock(); + mSharedMutex.lock_shared(); + mLockMutex.lock(); + // Continue after acquiring the mutex + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = true; + } + mLockMutex.unlock(); +} + +void LLSharedMutex::lockExclusive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + + mLockMutex.lock(); + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + if (mIsShared) + { + // The mutex is already locked in the current thread + // but this lock is SHARED (not EXCLISIVE) + // We can't lock it again, the lock stays shared + // This can lead to a collision (theoretically) + llassert_always(!"The current thread is already locked SHARED and can't be locked EXCLUSIVE"); + } + it->second++; + } + else + { + // Acquire the mutex immediately if mLockingThreads is empty + // or enter a locking state if mLockingThreads is not empty + mLockMutex.unlock(); + mSharedMutex.lock(); + mLockMutex.lock(); + // Continue after acquiring the mutex (and possible quitting the locking state) + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = false; + } + mLockMutex.unlock(); +} + +bool LLSharedMutex::trylockShared() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard<std::mutex> lock(mLockMutex); + + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + it->second++; + } + else + { + if (!mSharedMutex.try_lock_shared()) + return false; + + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = true; + } + + return true; +} + +bool LLSharedMutex::trylockExclusive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard<std::mutex> lock(mLockMutex); + + if (mLockingThreads.size() == 1 && mLockingThreads.begin()->first == current_thread) + { + mLockingThreads.begin()->second++; + } + else + { + if (!mSharedMutex.try_lock()) + return false; + + mLockingThreads.emplace(std::make_pair(current_thread, 1)); + mIsShared = false; + } + + return true; +} + +void LLSharedMutex::unlockShared() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard<std::mutex> lock(mLockMutex); + + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + if (it->second > 1) + { + it->second--; + } + else + { + mLockingThreads.erase(it); + mSharedMutex.unlock_shared(); + } + } +} + +void LLSharedMutex::unlockExclusive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + LLThread::id_t current_thread = LLThread::currentID(); + std::lock_guard<std::mutex> lock(mLockMutex); + + iterator it = mLockingThreads.find(current_thread); + if (it != mLockingThreads.end()) + { + if (it->second > 1) + { + it->second--; + } + else + { + mLockingThreads.erase(it); + mSharedMutex.unlock(); + } + } +} + + +//--------------------------------------------------------------------- +// +// LLCondition +// +LLCondition::LLCondition() : + LLMutex() +{ +} + +LLCondition::~LLCondition() +{ +} + +void LLCondition::wait() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + std::unique_lock< std::mutex > lock(mMutex); + mCond.wait(lock); +} + +void LLCondition::signal() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mCond.notify_one(); +} + +void LLCondition::broadcast() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mCond.notify_all(); +} + + +//--------------------------------------------------------------------- +// +// LLMutexTrylock +// +LLMutexTrylock::LLMutexTrylock(LLMutex* mutex) + : mMutex(mutex), + mLocked(false) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (mMutex) + mLocked = mMutex->trylock(); +} + +LLMutexTrylock::LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms) + : mMutex(mutex), + mLocked(false) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (!mMutex) + return; + + for (U32 i = 0; i < aTries; ++i) + { + mLocked = mMutex->trylock(); + if (mLocked) + break; + ms_sleep(delay_ms); + } +} + +LLMutexTrylock::~LLMutexTrylock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if (mMutex && mLocked) + mMutex->unlock(); +} + + +//--------------------------------------------------------------------- +// +// LLScopedLock +// +LLScopedLock::LLScopedLock(std::mutex* mutex) : mMutex(mutex) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if(mutex) + { + mutex->lock(); + mLocked = true; + } + else + { + mLocked = false; + } +} + +LLScopedLock::~LLScopedLock() +{ + unlock(); +} + +void LLScopedLock::unlock() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if(mLocked) + { + mMutex->unlock(); + mLocked = false; + } +} + +//============================================================================ diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index 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 <boost/noncopyable.hpp>
-
-#include "mutex.h"
-#include <shared_mutex>
-#include <unordered_map>
-#include <condition_variable>
-
-//============================================================================
-
-//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
-#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles
-
-#if MUTEX_DEBUG
-#include <map>
-#endif
-
-class LL_COMMON_API LLMutex
-{
-public:
- LLMutex();
- virtual ~LLMutex();
-
- void lock(); // blocks
- bool trylock(); // non-blocking, returns true if lock held.
- void unlock(); // undefined behavior when called on mutex not being held
- bool isLocked(); // non-blocking, but does do a lock/unlock so not free
- bool isSelfLocked(); //return true if locked in a same thread
- LLThread::id_t lockingThread() const; //get ID of locking thread
-
-protected:
- std::mutex mMutex;
- mutable U32 mCount;
- mutable LLThread::id_t mLockingThread;
-
-#if MUTEX_DEBUG
- std::unordered_map<LLThread::id_t, bool> mIsLocked;
-#endif
-};
-
-//============================================================================
-
-class LL_COMMON_API LLSharedMutex
-{
-public:
- LLSharedMutex();
-
- bool isLocked() const;
- bool isThreadLocked() const;
- bool isShared() const { return mIsShared; }
-
- void lockShared();
- void lockExclusive();
- template<bool SHARED> void lock();
-
- bool trylockShared();
- bool trylockExclusive();
- template<bool SHARED> bool trylock();
-
- void unlockShared();
- void unlockExclusive();
- template<bool SHARED> void unlock();
-
-private:
- std::shared_mutex mSharedMutex;
- mutable std::mutex mLockMutex;
- std::unordered_map<LLThread::id_t, U32> mLockingThreads;
- bool mIsShared;
-
- using iterator = std::unordered_map<LLThread::id_t, U32>::iterator;
- using const_iterator = std::unordered_map<LLThread::id_t, U32>::const_iterator;
-};
-
-template<>
-inline void LLSharedMutex::lock<true>()
-{
- lockShared();
-}
-
-template<>
-inline void LLSharedMutex::lock<false>()
-{
- lockExclusive();
-}
-
-template<>
-inline bool LLSharedMutex::trylock<true>()
-{
- return trylockShared();
-}
-
-template<>
-inline bool LLSharedMutex::trylock<false>()
-{
- return trylockExclusive();
-}
-
-template<>
-inline void LLSharedMutex::unlock<true>()
-{
- unlockShared();
-}
-
-template<>
-inline void LLSharedMutex::unlock<false>()
-{
- unlockExclusive();
-}
-
-// Actually a condition/mutex pair (since each condition needs to be associated with a mutex).
-class LL_COMMON_API LLCondition : public LLMutex
-{
-public:
- LLCondition();
- ~LLCondition();
-
- void wait(); // blocks
- void signal();
- void broadcast();
-
-protected:
- std::condition_variable mCond;
-};
-
-//============================================================================
-
-class LLMutexLock
-{
-public:
- LLMutexLock(LLMutex* mutex)
- {
- mMutex = mutex;
-
- if (mMutex)
- mMutex->lock();
- }
-
- ~LLMutexLock()
- {
- if (mMutex)
- mMutex->unlock();
- }
-
-private:
- LLMutex* mMutex;
-};
-
-//============================================================================
-
-template<bool SHARED>
-class LLSharedMutexLockTemplate
-{
-public:
- LLSharedMutexLockTemplate(LLSharedMutex* mutex)
- : mSharedMutex(mutex)
- {
- if (mSharedMutex)
- mSharedMutex->lock<SHARED>();
- }
-
- ~LLSharedMutexLockTemplate()
- {
- if (mSharedMutex)
- mSharedMutex->unlock<SHARED>();
- }
-
-private:
- LLSharedMutex* mSharedMutex;
-};
-
-using LLSharedMutexLock = LLSharedMutexLockTemplate<true>;
-using LLExclusiveMutexLock = LLSharedMutexLockTemplate<false>;
-
-//============================================================================
-
-// Scoped locking class similar in function to LLMutexLock but uses
-// the trylock() method to conditionally acquire lock without
-// blocking. Caller resolves the resulting condition by calling
-// the isLocked() method and either punts or continues as indicated.
-//
-// Mostly of interest to callers needing to avoid stalls who can
-// guarantee another attempt at a later time.
-
-class LLMutexTrylock
-{
-public:
- LLMutexTrylock(LLMutex* mutex);
- LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms = 10);
- ~LLMutexTrylock();
-
- bool isLocked() const
- {
- return mLocked;
- }
-
-private:
- LLMutex* mMutex;
- bool mLocked;
-};
-
-//============================================================================
-
-/**
-* @class LLScopedLock
-* @brief Small class to help lock and unlock mutexes.
-*
-* The constructor handles the lock, and the destructor handles
-* the unlock. Instances of this class are <b>not</b> thread safe.
-*/
-class LL_COMMON_API LLScopedLock : private boost::noncopyable
-{
-public:
- /**
- * @brief Constructor which accepts a mutex, and locks it.
- *
- * @param mutex An allocated mutex. If you pass in NULL,
- * this wrapper will not lock.
- */
- LLScopedLock(std::mutex* mutex);
-
- /**
- * @brief Destructor which unlocks the mutex if still locked.
- */
- ~LLScopedLock();
-
- /**
- * @brief Check lock.
- */
- bool isLocked() const { return mLocked; }
-
- /**
- * @brief This method unlocks the mutex.
- */
- void unlock();
-
-protected:
- bool mLocked;
- std::mutex* mMutex;
-};
-
-#endif // LL_LLMUTEX_H
+/** + * @file llmutex.h + * @brief Base classes for mutex and condition handling. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMUTEX_H +#define LL_LLMUTEX_H + +#include "stdtypes.h" +#include "llthread.h" +#include <boost/noncopyable.hpp> + +#include "mutex.h" +#include <shared_mutex> +#include <unordered_map> +#include <condition_variable> + +//============================================================================ + +//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO) +#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles + +#if MUTEX_DEBUG +#include <map> +#endif + +class LL_COMMON_API LLMutex +{ +public: + LLMutex(); + virtual ~LLMutex(); + + void lock(); // blocks + bool trylock(); // non-blocking, returns true if lock held. + void unlock(); // undefined behavior when called on mutex not being held + bool isLocked(); // non-blocking, but does do a lock/unlock so not free + bool isSelfLocked(); //return true if locked in a same thread + LLThread::id_t lockingThread() const; //get ID of locking thread + +protected: + std::mutex mMutex; + mutable U32 mCount; + mutable LLThread::id_t mLockingThread; + +#if MUTEX_DEBUG + std::unordered_map<LLThread::id_t, bool> mIsLocked; +#endif +}; + +//============================================================================ + +class LL_COMMON_API LLSharedMutex +{ +public: + LLSharedMutex(); + + bool isLocked() const; + bool isThreadLocked() const; + bool isShared() const { return mIsShared; } + + void lockShared(); + void lockExclusive(); + template<bool SHARED> void lock(); + + bool trylockShared(); + bool trylockExclusive(); + template<bool SHARED> bool trylock(); + + void unlockShared(); + void unlockExclusive(); + template<bool SHARED> void unlock(); + +private: + std::shared_mutex mSharedMutex; + mutable std::mutex mLockMutex; + std::unordered_map<LLThread::id_t, U32> mLockingThreads; + bool mIsShared; + + using iterator = std::unordered_map<LLThread::id_t, U32>::iterator; + using const_iterator = std::unordered_map<LLThread::id_t, U32>::const_iterator; +}; + +template<> +inline void LLSharedMutex::lock<true>() +{ + lockShared(); +} + +template<> +inline void LLSharedMutex::lock<false>() +{ + lockExclusive(); +} + +template<> +inline bool LLSharedMutex::trylock<true>() +{ + return trylockShared(); +} + +template<> +inline bool LLSharedMutex::trylock<false>() +{ + return trylockExclusive(); +} + +template<> +inline void LLSharedMutex::unlock<true>() +{ + unlockShared(); +} + +template<> +inline void LLSharedMutex::unlock<false>() +{ + unlockExclusive(); +} + +// Actually a condition/mutex pair (since each condition needs to be associated with a mutex). +class LL_COMMON_API LLCondition : public LLMutex +{ +public: + LLCondition(); + ~LLCondition(); + + void wait(); // blocks + void signal(); + void broadcast(); + +protected: + std::condition_variable mCond; +}; + +//============================================================================ + +class LLMutexLock +{ +public: + LLMutexLock(LLMutex* mutex) + { + mMutex = mutex; + + if (mMutex) + mMutex->lock(); + } + + ~LLMutexLock() + { + if (mMutex) + mMutex->unlock(); + } + +private: + LLMutex* mMutex; +}; + +//============================================================================ + +template<bool SHARED> +class LLSharedMutexLockTemplate +{ +public: + LLSharedMutexLockTemplate(LLSharedMutex* mutex) + : mSharedMutex(mutex) + { + if (mSharedMutex) + mSharedMutex->lock<SHARED>(); + } + + ~LLSharedMutexLockTemplate() + { + if (mSharedMutex) + mSharedMutex->unlock<SHARED>(); + } + +private: + LLSharedMutex* mSharedMutex; +}; + +using LLSharedMutexLock = LLSharedMutexLockTemplate<true>; +using LLExclusiveMutexLock = LLSharedMutexLockTemplate<false>; + +//============================================================================ + +// Scoped locking class similar in function to LLMutexLock but uses +// the trylock() method to conditionally acquire lock without +// blocking. Caller resolves the resulting condition by calling +// the isLocked() method and either punts or continues as indicated. +// +// Mostly of interest to callers needing to avoid stalls who can +// guarantee another attempt at a later time. + +class LLMutexTrylock +{ +public: + LLMutexTrylock(LLMutex* mutex); + LLMutexTrylock(LLMutex* mutex, U32 aTries, U32 delay_ms = 10); + ~LLMutexTrylock(); + + bool isLocked() const + { + return mLocked; + } + +private: + LLMutex* mMutex; + bool mLocked; +}; + +//============================================================================ + +/** +* @class LLScopedLock +* @brief Small class to help lock and unlock mutexes. +* +* The constructor handles the lock, and the destructor handles +* the unlock. Instances of this class are <b>not</b> thread safe. +*/ +class LL_COMMON_API LLScopedLock : private boost::noncopyable +{ +public: + /** + * @brief Constructor which accepts a mutex, and locks it. + * + * @param mutex An allocated mutex. If you pass in NULL, + * this wrapper will not lock. + */ + LLScopedLock(std::mutex* mutex); + + /** + * @brief Destructor which unlocks the mutex if still locked. + */ + ~LLScopedLock(); + + /** + * @brief Check lock. + */ + bool isLocked() const { return mLocked; } + + /** + * @brief This method unlocks the mutex. + */ + void unlock(); + +protected: + bool mLocked; + std::mutex* mMutex; +}; + +#endif // LL_LLMUTEX_H diff --git a/indra/llcommon/llnametable.h b/indra/llcommon/llnametable.h index 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 <map>
-
-#include "string_table.h"
-
-template <class DATA>
-class LLNameTable
-{
-public:
- LLNameTable()
- : mNameMap()
- {
- }
-
- ~LLNameTable()
- {
- }
-
- void addEntry(const std::string& name, DATA data)
- {
- addEntry(name.c_str(), data);
- }
-
- void addEntry(const char *name, DATA data)
- {
- char *tablename = gStringTable.addString(name);
- mNameMap[tablename] = data;
- }
-
- bool checkName(const std::string& name) const
- {
- return checkName(name.c_str());
- }
-
- // "logically const" even though it modifies the global nametable
- bool checkName(const char *name) const
- {
- char *tablename = gStringTable.addString(name);
- return mNameMap.find(tablename) != mNameMap.end();
- }
-
- DATA resolveName(const std::string& name) const
- {
- return resolveName(name.c_str());
- }
-
- // "logically const" even though it modifies the global nametable
- DATA resolveName(const char *name) const
- {
- char *tablename = gStringTable.addString(name);
- const_iter_t iter = mNameMap.find(tablename);
- if (iter != mNameMap.end())
- return iter->second;
- else
- return 0;
- }
-
- // O(N)! (currently only used in one place... (newsim/llstate.cpp))
- const char *resolveData(const DATA &data) const
- {
- for (const name_map_t::value_type& pair : mNameMap)
- {
- if (pair.second == data)
- return pair.first;
- }
- return NULL;
- }
-
- typedef std::map<const char *, DATA> name_map_t;
- typedef typename std::map<const char *,DATA>::iterator iter_t;
- typedef typename std::map<const char *,DATA>::const_iterator const_iter_t;
- name_map_t mNameMap;
-};
-
-#endif
+/** + * @file llnametable.h + * @brief LLNameTable class is a table to associate pointers with string names + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLNAMETABLE_H +#define LL_LLNAMETABLE_H + +#include <map> + +#include "string_table.h" + +template <class DATA> +class LLNameTable +{ +public: + LLNameTable() + : mNameMap() + { + } + + ~LLNameTable() + { + } + + void addEntry(const std::string& name, DATA data) + { + addEntry(name.c_str(), data); + } + + void addEntry(const char *name, DATA data) + { + char *tablename = gStringTable.addString(name); + mNameMap[tablename] = data; + } + + bool checkName(const std::string& name) const + { + return checkName(name.c_str()); + } + + // "logically const" even though it modifies the global nametable + bool checkName(const char *name) const + { + char *tablename = gStringTable.addString(name); + return mNameMap.find(tablename) != mNameMap.end(); + } + + DATA resolveName(const std::string& name) const + { + return resolveName(name.c_str()); + } + + // "logically const" even though it modifies the global nametable + DATA resolveName(const char *name) const + { + char *tablename = gStringTable.addString(name); + const_iter_t iter = mNameMap.find(tablename); + if (iter != mNameMap.end()) + return iter->second; + else + return 0; + } + + // O(N)! (currently only used in one place... (newsim/llstate.cpp)) + const char *resolveData(const DATA &data) const + { + for (const name_map_t::value_type& pair : mNameMap) + { + if (pair.second == data) + return pair.first; + } + return NULL; + } + + typedef std::map<const char *, DATA> name_map_t; + typedef typename std::map<const char *,DATA>::iterator iter_t; + typedef typename std::map<const char *,DATA>::const_iterator const_iter_t; + name_map_t mNameMap; +}; + +#endif diff --git a/indra/llcommon/llpriqueuemap.h b/indra/llcommon/llpriqueuemap.h index 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 <map>
-
-//
-// Priority queue, implemented under the hood as a
-// map. Needs to be done this way because none of the
-// standard STL containers provide a representation
-// where it's easy to reprioritize.
-//
-
-template <class DATA>
-class LLPQMKey
-{
-public:
- LLPQMKey(const F32 priority, DATA data) : mPriority(priority), mData(data)
- {
- }
-
- bool operator<(const LLPQMKey &b) const
- {
- if (mPriority > b.mPriority)
- {
- return true;
- }
- if (mPriority < b.mPriority)
- {
- return false;
- }
- if (mData > b.mData)
- {
- return true;
- }
- return false;
- }
-
- F32 mPriority;
- DATA mData;
-};
-
-template <class DATA_TYPE>
-class LLPriQueueMap
-{
-public:
- typedef typename std::map<LLPQMKey<DATA_TYPE>, DATA_TYPE>::iterator pqm_iter;
- typedef std::pair<LLPQMKey<DATA_TYPE>, DATA_TYPE> pqm_pair;
- typedef void (*set_pri_fn)(DATA_TYPE &data, const F32 priority);
- typedef F32 (*get_pri_fn)(DATA_TYPE &data);
-
-
- LLPriQueueMap(set_pri_fn set_pri, get_pri_fn get_pri) : mSetPriority(set_pri), mGetPriority(get_pri)
- {
- }
-
- void push(const F32 priority, DATA_TYPE data)
- {
-#ifdef _DEBUG
- pqm_iter iter = mMap.find(LLPQMKey<DATA_TYPE>(priority, data));
- if (iter != mMap.end())
- {
- LL_ERRS() << "Pushing already existing data onto queue!" << LL_ENDL;
- }
-#endif
- mMap.insert(pqm_pair(LLPQMKey<DATA_TYPE>(priority, data), data));
- }
-
- bool pop(DATA_TYPE *datap)
- {
- pqm_iter iter;
- iter = mMap.begin();
- if (iter == mMap.end())
- {
- return false;
- }
- *datap = (*(iter)).second;
- mMap.erase(iter);
-
- return true;
- }
-
- void reprioritize(const F32 new_priority, DATA_TYPE data)
- {
- pqm_iter iter;
- F32 cur_priority = mGetPriority(data);
- LLPQMKey<DATA_TYPE> cur_key(cur_priority, data);
- iter = mMap.find(cur_key);
- if (iter == mMap.end())
- {
- LL_WARNS() << "Data not on priority queue!" << LL_ENDL;
- // OK, try iterating through all of the data and seeing if we just screwed up the priority
- // somehow.
- for (pqm_pair pair : mMap)
- {
- if (pair.second == data)
- {
- LL_ERRS() << "Data on priority queue but priority not matched!" << LL_ENDL;
- }
- }
- return;
- }
-
- mMap.erase(iter);
- mSetPriority(data, new_priority);
- push(new_priority, data);
- }
-
- S32 getLength() const
- {
- return (S32)mMap.size();
- }
-
- // Hack: public for use by the transfer manager, ugh.
- std::map<LLPQMKey<DATA_TYPE>, DATA_TYPE> mMap;
-protected:
- void (*mSetPriority)(DATA_TYPE &data, const F32 priority);
- F32 (*mGetPriority)(DATA_TYPE &data);
-};
-
-#endif // LL_LLPRIQUEUEMAP_H
+/** + * @file llpriqueuemap.h + * @brief Priority queue implementation + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LL_LLPRIQUEUEMAP_H +#define LL_LLPRIQUEUEMAP_H + +#include <map> + +// +// Priority queue, implemented under the hood as a +// map. Needs to be done this way because none of the +// standard STL containers provide a representation +// where it's easy to reprioritize. +// + +template <class DATA> +class LLPQMKey +{ +public: + LLPQMKey(const F32 priority, DATA data) : mPriority(priority), mData(data) + { + } + + bool operator<(const LLPQMKey &b) const + { + if (mPriority > b.mPriority) + { + return true; + } + if (mPriority < b.mPriority) + { + return false; + } + if (mData > b.mData) + { + return true; + } + return false; + } + + F32 mPriority; + DATA mData; +}; + +template <class DATA_TYPE> +class LLPriQueueMap +{ +public: + typedef typename std::map<LLPQMKey<DATA_TYPE>, DATA_TYPE>::iterator pqm_iter; + typedef std::pair<LLPQMKey<DATA_TYPE>, DATA_TYPE> pqm_pair; + typedef void (*set_pri_fn)(DATA_TYPE &data, const F32 priority); + typedef F32 (*get_pri_fn)(DATA_TYPE &data); + + + LLPriQueueMap(set_pri_fn set_pri, get_pri_fn get_pri) : mSetPriority(set_pri), mGetPriority(get_pri) + { + } + + void push(const F32 priority, DATA_TYPE data) + { +#ifdef _DEBUG + pqm_iter iter = mMap.find(LLPQMKey<DATA_TYPE>(priority, data)); + if (iter != mMap.end()) + { + LL_ERRS() << "Pushing already existing data onto queue!" << LL_ENDL; + } +#endif + mMap.insert(pqm_pair(LLPQMKey<DATA_TYPE>(priority, data), data)); + } + + bool pop(DATA_TYPE *datap) + { + pqm_iter iter; + iter = mMap.begin(); + if (iter == mMap.end()) + { + return false; + } + *datap = (*(iter)).second; + mMap.erase(iter); + + return true; + } + + void reprioritize(const F32 new_priority, DATA_TYPE data) + { + pqm_iter iter; + F32 cur_priority = mGetPriority(data); + LLPQMKey<DATA_TYPE> cur_key(cur_priority, data); + iter = mMap.find(cur_key); + if (iter == mMap.end()) + { + LL_WARNS() << "Data not on priority queue!" << LL_ENDL; + // OK, try iterating through all of the data and seeing if we just screwed up the priority + // somehow. + for (pqm_pair pair : mMap) + { + if (pair.second == data) + { + LL_ERRS() << "Data on priority queue but priority not matched!" << LL_ENDL; + } + } + return; + } + + mMap.erase(iter); + mSetPriority(data, new_priority); + push(new_priority, data); + } + + S32 getLength() const + { + return (S32)mMap.size(); + } + + // Hack: public for use by the transfer manager, ugh. + std::map<LLPQMKey<DATA_TYPE>, DATA_TYPE> mMap; +protected: + void (*mSetPriority)(DATA_TYPE &data, const F32 priority); + F32 (*mGetPriority)(DATA_TYPE &data); +}; + +#endif // LL_LLPRIQUEUEMAP_H diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 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 <boost/bind.hpp>
-#include <boost/asio/streambuf.hpp>
-#include <boost/asio/buffers_iterator.hpp>
-#include <iostream>
-#include <stdexcept>
-#include <limits>
-#include <algorithm>
-#include <vector>
-#include <typeinfo>
-#include <utility>
-
-/*****************************************************************************
-* Helpers
-*****************************************************************************/
-static const char* whichfile_[] = { "stdin", "stdout", "stderr" };
-static std::string empty;
-static LLProcess::Status interpret_status(int status);
-static std::string getDesc(const LLProcess::Params& params);
-
-static std::string whichfile(LLProcess::FILESLOT index)
-{
- if (index < LL_ARRAY_SIZE(whichfile_))
- return whichfile_[index];
- return STRINGIZE("file slot " << index);
-}
-
-/**
- * Ref-counted "mainloop" listener. As long as there are still outstanding
- * LLProcess objects, keep listening on "mainloop" so we can keep polling APR
- * for process status.
- */
-class LLProcessListener
-{
- LOG_CLASS(LLProcessListener);
-public:
- LLProcessListener():
- mCount(0)
- {}
-
- void addPoll(const LLProcess&)
- {
- // Unconditionally increment mCount. If it was zero before
- // incrementing, listen on "mainloop".
- if (mCount++ == 0)
- {
- LL_DEBUGS("LLProcess") << "listening on \"mainloop\"" << LL_ENDL;
- mConnection = LLEventPumps::instance().obtain("mainloop")
- .listen("LLProcessListener", boost::bind(&LLProcessListener::tick, this, _1));
- }
- }
-
- void dropPoll(const LLProcess&)
- {
- // Unconditionally decrement mCount. If it's zero after decrementing,
- // stop listening on "mainloop".
- if (--mCount == 0)
- {
- LL_DEBUGS("LLProcess") << "disconnecting from \"mainloop\"" << LL_ENDL;
- mConnection.disconnect();
- }
- }
-
-private:
- /// called once per frame by the "mainloop" LLEventPump
- bool tick(const LLSD&)
- {
- // Tell APR to sense whether each registered LLProcess is still
- // running and call handle_status() appropriately. We should be able
- // to get the same info from an apr_proc_wait(APR_NOWAIT) call; but at
- // least in APR 1.4.2, testing suggests that even with APR_NOWAIT,
- // apr_proc_wait() blocks the caller. We can't have that in the
- // viewer. Hence the callback rigmarole. (Once we update APR, it's
- // probably worth testing again.) Also -- although there's an
- // apr_proc_other_child_refresh() call, i.e. get that information for
- // one specific child, it accepts an 'apr_other_child_rec_t*' that's
- // mentioned NOWHERE else in the documentation or header files! I
- // would use the specific call in LLProcess::getStatus() if I knew
- // how. As it is, each call to apr_proc_other_child_refresh_all() will
- // call callbacks for ALL still-running child processes. That's why we
- // centralize such calls, using "mainloop" to ensure it happens once
- // per frame, and refcounting running LLProcess objects to remain
- // registered only while needed.
- LL_DEBUGS("LLProcess") << "calling apr_proc_other_child_refresh_all()" << LL_ENDL;
- apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING);
- return false;
- }
-
- /// If this object is destroyed before mCount goes to zero, stop
- /// listening on "mainloop" anyway.
- LLTempBoundListener mConnection;
- unsigned mCount;
-};
-static LLProcessListener sProcessListener;
-
-/*****************************************************************************
-* WritePipe and ReadPipe
-*****************************************************************************/
-LLProcess::BasePipe::~BasePipe() {}
-const LLProcess::BasePipe::size_type
- // use funky syntax to call max() to avoid blighted max() macros
- LLProcess::BasePipe::npos((std::numeric_limits<LLProcess::BasePipe::size_type>::max)());
-
-class WritePipeImpl: public LLProcess::WritePipe
-{
- LOG_CLASS(WritePipeImpl);
-public:
- WritePipeImpl(const std::string& desc, apr_file_t* pipe):
- mDesc(desc),
- mPipe(pipe),
- // Essential to initialize our std::ostream with our special streambuf!
- mStream(&mStreambuf)
- {
- mConnection = LLEventPumps::instance().obtain("mainloop")
- .listen(LLEventPump::inventName("WritePipe"),
- boost::bind(&WritePipeImpl::tick, this, _1));
-
-#if ! LL_WINDOWS
- // We can't count on every child process reading everything we try to
- // write to it. And if the child terminates with WritePipe data still
- // pending, unless we explicitly suppress it, Posix will hit us with
- // SIGPIPE. That would terminate the viewer, boom. "Ignoring" it means
- // APR gets the correct errno, passes it back to us, we log it, etc.
- signal(SIGPIPE, SIG_IGN);
-#endif
- }
-
- virtual std::ostream& get_ostream() { return mStream; }
- virtual size_type size() const { return mStreambuf.size(); }
-
- bool tick(const LLSD&)
- {
- typedef boost::asio::streambuf::const_buffers_type const_buffer_sequence;
- // If there's anything to send, try to send it.
- std::size_t total(mStreambuf.size()), consumed(0);
- if (total)
- {
- const_buffer_sequence bufs = mStreambuf.data();
- // In general, our streambuf might contain a number of different
- // physical buffers; iterate over those.
- bool keepwriting = true;
- for (const_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end());
- bufi != bufend && keepwriting; ++bufi)
- {
- // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents
- // Although apr_file_write() accepts const void*, we
- // manipulate const char* so we can increment the pointer.
- const char* remainptr = boost::asio::buffer_cast<const char*>(*bufi);
- std::size_t remainlen = boost::asio::buffer_size(*bufi);
- while (remainlen)
- {
- // Tackle the current buffer in discrete chunks. On
- // Windows, we've observed strange failures when trying to
- // write big lengths (~1 MB) in a single operation. Even a
- // 32K chunk seems too large. At some point along the way
- // apr_file_write() returns 11 (Resource temporarily
- // unavailable, i.e. EAGAIN) and says it wrote 0 bytes --
- // even though it did write the chunk! Our next write
- // attempt retries with the same chunk, resulting in the
- // chunk being duplicated at the child end. Using smaller
- // chunks is empirically more reliable.
- std::size_t towrite((std::min)(remainlen, std::size_t(4*1024)));
- apr_size_t written(towrite);
- apr_status_t err = apr_file_write(mPipe, remainptr, &written);
- // EAGAIN is exactly what we want from a nonblocking pipe.
- // Rather than waiting for data, it should return immediately.
- if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err)))
- {
- LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc
- << " got " << err << ":" << LL_ENDL;
- ll_apr_warn_status(err);
- }
-
- // 'written' is modified to reflect the number of bytes actually
- // written. Make sure we consume those later. (Don't consume them
- // now, that would invalidate the buffer iterator sequence!)
- consumed += written;
- // don't forget to advance to next chunk of current buffer
- remainptr += written;
- remainlen -= written;
-
- char msgbuf[512];
- LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite
- << " bytes to " << mDesc
- << " (original " << total << "),"
- << " code " << err << ": "
- << apr_strerror(err, msgbuf, sizeof(msgbuf))
- << LL_ENDL;
-
- // The parent end of this pipe is nonblocking. If we weren't able
- // to write everything we wanted, don't keep banging on it -- that
- // won't change until the child reads some. Wait for next tick().
- if (written < towrite)
- {
- keepwriting = false; // break outer loop over buffers too
- break;
- }
- } // next chunk of current buffer
- } // next buffer
- // In all, we managed to write 'consumed' bytes. Remove them from the
- // streambuf so we don't keep trying to send them. This could be
- // anywhere from 0 up to mStreambuf.size(); anything we haven't yet
- // sent, we'll try again later.
- mStreambuf.consume(consumed);
- }
-
- return false;
- }
-
-private:
- std::string mDesc;
- apr_file_t* mPipe;
- LLTempBoundListener mConnection;
- boost::asio::streambuf mStreambuf;
- std::ostream mStream;
-};
-
-class ReadPipeImpl: public LLProcess::ReadPipe
-{
- LOG_CLASS(ReadPipeImpl);
-public:
- ReadPipeImpl(const std::string& desc, apr_file_t* pipe, LLProcess::FILESLOT index):
- mDesc(desc),
- mPipe(pipe),
- mIndex(index),
- // Essential to initialize our std::istream with our special streambuf!
- mStream(&mStreambuf),
- mPump("ReadPipe", true), // tweak name as needed to avoid collisions
- mLimit(0),
- mEOF(false)
- {
- mConnection = LLEventPumps::instance().obtain("mainloop")
- .listen(LLEventPump::inventName("ReadPipe"),
- boost::bind(&ReadPipeImpl::tick, this, _1));
- }
-
- ~ReadPipeImpl()
- {
- if (mConnection.connected())
- {
- mConnection.disconnect();
- }
- }
-
- // Much of the implementation is simply connecting the abstract virtual
- // methods with implementation data concealed from the base class.
- virtual std::istream& get_istream() { return mStream; }
- virtual std::string getline() { return LLProcess::getline(mStream); }
- virtual LLEventPump& getPump() { return mPump; }
- virtual void setLimit(size_type limit) { mLimit = limit; }
- virtual size_type getLimit() const { return mLimit; }
- virtual size_type size() const { return mStreambuf.size(); }
-
- virtual std::string read(size_type len)
- {
- // Read specified number of bytes into a buffer.
- size_type readlen((std::min)(size(), len));
- // Formally, &buffer[0] is invalid for a vector of size() 0. Exit
- // early in that situation.
- if (! readlen)
- return "";
- // Make a buffer big enough.
- std::vector<char> buffer(readlen);
- mStream.read(&buffer[0], readlen);
- // Since we've already clamped 'readlen', we can think of no reason
- // why mStream.read() should read fewer than 'readlen' bytes.
- // Nonetheless, use the actual retrieved length.
- return std::string(&buffer[0], mStream.gcount());
- }
-
- virtual std::string peek(size_type offset=0, size_type len=npos) const
- {
- // Constrain caller's offset and len to overlap actual buffer content.
- std::size_t real_offset = (std::min)(mStreambuf.size(), std::size_t(offset));
- size_type want_end = (len == npos)? npos : (real_offset + len);
- std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(want_end));
- boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
- return std::string(boost::asio::buffers_begin(cbufs) + real_offset,
- boost::asio::buffers_begin(cbufs) + real_end);
- }
-
- virtual size_type find(const std::string& seek, size_type offset=0) const
- {
- // If we're passing a string of length 1, use find(char), which can
- // use an O(n) std::find() rather than the O(n^2) std::search().
- if (seek.length() == 1)
- {
- return find(seek[0], offset);
- }
-
- // If offset is beyond the whole buffer, can't even construct a valid
- // iterator range; can't possibly find the string we seek.
- if (offset > mStreambuf.size())
- {
- return npos;
- }
-
- boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
- boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type>
- begin(boost::asio::buffers_begin(cbufs)),
- end (boost::asio::buffers_end(cbufs)),
- found(std::search(begin + offset, end, seek.begin(), seek.end()));
- return (found == end)? npos : (found - begin);
- }
-
- virtual size_type find(char seek, size_type offset=0) const
- {
- // If offset is beyond the whole buffer, can't even construct a valid
- // iterator range; can't possibly find the char we seek.
- if (offset > mStreambuf.size())
- {
- return npos;
- }
-
- boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data();
- boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type>
- begin(boost::asio::buffers_begin(cbufs)),
- end (boost::asio::buffers_end(cbufs)),
- found(std::find(begin + offset, end, seek));
- return (found == end)? npos : (found - begin);
- }
-
- bool tick(const LLSD&)
- {
- // Once we've hit EOF, skip all the rest of this.
- if (mEOF)
- return false;
-
- typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence;
- // Try, every time, to read into our streambuf. In fact, we have no
- // idea how much data the child might be trying to send: keep trying
- // until we're convinced we've temporarily exhausted the pipe.
- enum PipeState { RETRY, EXHAUSTED, CLOSED };
- PipeState state = RETRY;
- std::size_t committed(0);
- do
- {
- // attempt to read an arbitrary size
- mutable_buffer_sequence bufs = mStreambuf.prepare(4096);
- // In general, the mutable_buffer_sequence returned by prepare() might
- // contain a number of different physical buffers; iterate over those.
- std::size_t tocommit(0);
- for (mutable_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end());
- bufi != bufend; ++bufi)
- {
- // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents
- std::size_t toread(boost::asio::buffer_size(*bufi));
- apr_size_t gotten(toread);
- apr_status_t err = apr_file_read(mPipe,
- boost::asio::buffer_cast<void*>(*bufi),
- &gotten);
- // EAGAIN is exactly what we want from a nonblocking pipe.
- // Rather than waiting for data, it should return immediately.
- if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err)))
- {
- // Handle EOF specially: it's part of normal-case processing.
- if (err == APR_EOF)
- {
- LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL;
- }
- else
- {
- LL_WARNS("LLProcess") << "apr_file_read(" << toread << ") on " << mDesc
- << " got " << err << ":" << LL_ENDL;
- ll_apr_warn_status(err);
- }
- // Either way, though, we won't need any more tick() calls.
- mConnection.disconnect();
- // Ignore any subsequent calls we might get anyway.
- mEOF = true;
- state = CLOSED; // also break outer retry loop
- break;
- }
-
- // 'gotten' was modified to reflect the number of bytes actually
- // received. Make sure we commit those later. (Don't commit them
- // now, that would invalidate the buffer iterator sequence!)
- tocommit += gotten;
- LL_DEBUGS("LLProcess") << "filled " << gotten << " of " << toread
- << " bytes from " << mDesc << LL_ENDL;
-
- // The parent end of this pipe is nonblocking. If we weren't even
- // able to fill this buffer, don't loop to try to fill the next --
- // that won't change until the child writes more. Wait for next
- // tick().
- if (gotten < toread)
- {
- // break outer retry loop too
- state = EXHAUSTED;
- break;
- }
- }
-
- // Don't forget to "commit" the data!
- mStreambuf.commit(tocommit);
- committed += tocommit;
-
- // state is changed from RETRY when we can't fill any one buffer
- // of the mutable_buffer_sequence established by the current
- // prepare() call -- whether due to error or not enough bytes.
- // That is, if state is still RETRY, we've filled every physical
- // buffer in the mutable_buffer_sequence. In that case, for all we
- // know, the child might have still more data pending -- go for it!
- } while (state == RETRY);
-
- // Once we recognize that the pipe is closed, make one more call to
- // listener. The listener might be waiting for a particular substring
- // to arrive, or a particular length of data or something. The event
- // with "eof" == true announces that nothing further will arrive, so
- // use it or lose it.
- if (committed || state == CLOSED)
- {
- // If we actually received new data, publish it on our LLEventPump
- // as advertised. Constrain it by mLimit. But show listener the
- // actual accumulated buffer size, regardless of mLimit.
- size_type datasize((std::min)(mLimit, size_type(mStreambuf.size())));
- mPump.post(LLSDMap
- ("data", peek(0, datasize))
- ("len", LLSD::Integer(mStreambuf.size()))
- ("slot", LLSD::Integer(mIndex))
- ("name", whichfile(mIndex))
- ("desc", mDesc)
- ("eof", state == CLOSED));
- }
-
- return false;
- }
-
-private:
- std::string mDesc;
- apr_file_t* mPipe;
- LLProcess::FILESLOT mIndex;
- LLTempBoundListener mConnection;
- boost::asio::streambuf mStreambuf;
- std::istream mStream;
- LLEventStream mPump;
- size_type mLimit;
- bool mEOF;
-};
-
-/*****************************************************************************
-* LLProcess itself
-*****************************************************************************/
-/// Need an exception to avoid constructing an invalid LLProcess object, but
-/// internal use only
-struct LLProcessError: public LLException
-{
- LLProcessError(const std::string& msg): LLException(msg) {}
-};
-
-LLProcessPtr LLProcess::create(const LLSDOrParams& params)
-{
- try
- {
- return LLProcessPtr(new LLProcess(params));
- }
- catch (const LLProcessError& e)
- {
- LL_WARNS("LLProcess") << e.what() << LL_ENDL;
-
- // If caller is requesting an event on process termination, send one
- // indicating bad launch. This may prevent someone waiting forever for
- // a termination post that can't arrive because the child never
- // started.
- if (params.postend.isProvided())
- {
- LLEventPumps::instance().obtain(params.postend)
- .post(LLSDMap
- // no "id"
- ("desc", getDesc(params))
- ("state", LLProcess::UNSTARTED)
- // no "data"
- ("string", e.what())
- );
- }
-
- return LLProcessPtr();
- }
-}
-
-/// Call an apr function returning apr_status_t. On failure, log warning and
-/// throw LLProcessError mentioning the function call that produced that
-/// result.
-#define chkapr(func) \
- if (ll_apr_warn_status(func)) \
- throw LLProcessError(#func " failed")
-
-LLProcess::LLProcess(const LLSDOrParams& params):
- mAutokill(params.autokill),
- // Because 'autokill' originally meant both 'autokill' and 'attached', to
- // preserve existing semantics, we promise that mAttached defaults to the
- // same setting as mAutokill.
- mAttached(params.attached.isProvided()? params.attached : params.autokill),
- mPool(NULL),
- mPipes(NSLOTS)
-{
- // Hmm, when you construct a ptr_vector with a size, it merely reserves
- // space, it doesn't actually make it that big. Explicitly make it bigger.
- // Because of ptr_vector's odd semantics, have to push_back(0) the right
- // number of times! resize() wants to default-construct new BasePipe
- // instances, which fails because it's pure virtual. But because of the
- // constructor call, these push_back() calls should require no new
- // allocation.
- for (size_t i = 0; i < mPipes.capacity(); ++i)
- mPipes.push_back(0);
-
- if (! params.validateBlock(true))
- {
- LLTHROW(LLProcessError(STRINGIZE("not launched: failed parameter validation\n"
- << LLSDNotationStreamer(params))));
- }
-
- mPostend = params.postend;
-
- apr_pool_create(&mPool, gAPRPoolp);
- if (!mPool)
- {
- LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool")));
- }
-
- apr_procattr_t *procattr = NULL;
- chkapr(apr_procattr_create(&procattr, mPool));
-
- // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to
- // constrain the set of handles passed to the child process. Before we
- // changed to APR, the Windows implementation of LLProcessLauncher called
- // CreateProcess(bInheritHandles=false), meaning to pass NO open handles
- // to the child process. Now that we support pipes, though, we must allow
- // apr_proc_create() to pass bInheritHandles=true. But without taking
- // special pains, that causes trouble in a number of ways, due to the fact
- // that the viewer is constantly opening and closing files -- most of
- // which CreateProcess() passes to every child process!
-#if ! defined(APR_HAS_PROCATTR_CONSTRAIN_HANDLE_SET)
- // Our special preprocessor symbol isn't even defined -- wrong APR
- LL_WARNS("LLProcess") << "This version of APR lacks Linden "
- << "apr_procattr_constrain_handle_set() extension" << LL_ENDL;
-#else
- chkapr(apr_procattr_constrain_handle_set(procattr, 1));
-#endif
-
- // For which of stdin, stdout, stderr should we create a pipe to the
- // child? In the viewer, there are only a couple viable
- // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx
- // handle (APR_NO_PIPE, e.g. for stdout, stderr), or create a pipe that's
- // blocking on the child end but nonblocking at the viewer end
- // (APR_CHILD_BLOCK).
- // Other major options could include explicitly creating a single APR pipe
- // and passing it as both stdout and stderr (apr_procattr_child_out_set(),
- // apr_procattr_child_err_set()), or accepting a filename, opening it and
- // passing that apr_file_t (simple <, >, 2> redirect emulation).
- std::vector<apr_int32_t> select;
- for (const FileParam& fparam : params.files)
- {
- // Every iteration, we're going to append an item to 'select'. At the
- // top of the loop, its size() is, in effect, an index. Use that to
- // pick a string description for messages.
- std::string which(whichfile(FILESLOT(select.size())));
- if (fparam.type().empty()) // inherit our file descriptor
- {
- select.push_back(APR_NO_PIPE);
- }
- else if (fparam.type() == "pipe") // anonymous pipe
- {
- if (! fparam.name().empty())
- {
- LL_WARNS("LLProcess") << "For " << params.executable()
- << ": internal names for reusing pipes ('"
- << fparam.name() << "' for " << which
- << ") are not yet supported -- creating distinct pipe"
- << LL_ENDL;
- }
- // The viewer can't block for anything: the parent end MUST be
- // nonblocking. As the APR documentation itself points out, it
- // makes very little sense to set nonblocking I/O for the child
- // end of a pipe: only a specially-written child could deal with
- // that.
- select.push_back(APR_CHILD_BLOCK);
- }
- else
- {
- LLTHROW(LLProcessError(STRINGIZE("For " << params.executable()
- << ": unsupported FileParam for " << which
- << ": type='" << fparam.type()
- << "', name='" << fparam.name() << "'")));
- }
- }
- // By default, pass APR_NO_PIPE for unspecified slots.
- while (select.size() < NSLOTS)
- {
- select.push_back(APR_NO_PIPE);
- }
- chkapr(apr_procattr_io_set(procattr, select[STDIN], select[STDOUT], select[STDERR]));
-
- // Thumbs down on implicitly invoking the shell to invoke the child. From
- // our point of view, the other major alternative to APR_PROGRAM_PATH
- // would be APR_PROGRAM_ENV: still copy environment, but require full
- // executable pathname. I don't see a downside to searching the PATH,
- // though: if our caller wants (e.g.) a specific Python interpreter, s/he
- // can still pass the full pathname.
- chkapr(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH));
- // YES, do extra work if necessary to report child exec() failures back to
- // parent process.
- chkapr(apr_procattr_error_check_set(procattr, 1));
- // Do not start a non-autokill child in detached state. On Posix
- // platforms, this setting attempts to daemonize the new child, closing
- // std handles and the like, and that's a bit more detachment than we
- // want. autokill=false just means not to implicitly kill the child when
- // the parent terminates!
-// chkapr(apr_procattr_detach_set(procattr, mAutokill? 0 : 1));
-
- if (mAutokill)
- {
-#if ! defined(APR_HAS_PROCATTR_AUTOKILL_SET)
- // Our special preprocessor symbol isn't even defined -- wrong APR
- LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL;
-#elif ! APR_HAS_PROCATTR_AUTOKILL_SET
- // Symbol is defined, but to 0: expect apr_procattr_autokill_set() to
- // return APR_ENOTIMPL.
-#else // APR_HAS_PROCATTR_AUTOKILL_SET nonzero
- ll_apr_warn_status(apr_procattr_autokill_set(procattr, 1));
-#endif
- }
-
- // In preparation for calling apr_proc_create(), we collect a number of
- // const char* pointers obtained from std::string::c_str(). Turns out
- // LLInitParam::Block's helpers Optional, Mandatory, Multiple et al.
- // guarantee that converting to the wrapped type (std::string in our
- // case), e.g. by calling operator(), returns a reference to *the same
- // instance* of the wrapped type that's stored in our Block subclass.
- // That's important! We know 'params' persists throughout this method
- // call; but without that guarantee, we'd have to assume that converting
- // one of its members to std::string might return a different (temp)
- // instance. Capturing the c_str() from a temporary std::string is Bad Bad
- // Bad. But armed with this knowledge, when you see params.cwd().c_str(),
- // grit your teeth and smile and carry on.
-
- if (params.cwd.isProvided())
- {
- chkapr(apr_procattr_dir_set(procattr, params.cwd().c_str()));
- }
-
- // create an argv vector for the child process
- std::vector<const char*> argv;
-
- // Add the executable path. See above remarks about c_str().
- argv.push_back(params.executable().c_str());
-
- // Add arguments. See above remarks about c_str().
- for (const std::string& arg : params.args)
- {
- argv.push_back(arg.c_str());
- }
-
- // terminate with a null pointer
- argv.push_back(NULL);
-
- // Launch! The NULL would be the environment block, if we were passing
- // one. Hand-expand chkapr() macro so we can fill in the actual command
- // string instead of the variable names.
- if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr,
- mPool)))
- {
- LLTHROW(LLProcessError(STRINGIZE(params << " failed")));
- }
-
- // arrange to call status_callback()
- apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in,
- mPool);
- // and make sure we poll it once per "mainloop" tick
- sProcessListener.addPoll(*this);
- mStatus.mState = RUNNING;
-
- mDesc = STRINGIZE(getDesc(params) << " (" << mProcess.pid << ')');
- LL_INFOS("LLProcess") << mDesc << ": launched " << params << LL_ENDL;
-
- // Unless caller explicitly turned off autokill (child should persist),
- // take steps to terminate the child. This is all suspenders-and-belt: in
- // theory our destructor should kill an autokill child, but in practice
- // that doesn't always work (e.g. VWR-21538).
- if (mAutokill)
- {
-/*==========================================================================*|
- // NO: There may be an APR bug, not sure -- but at least on Mac, when
- // gAPRPoolp is destroyed, OUR process receives SIGTERM! Apparently
- // either our own PID is getting into the list of processes to kill()
- // (unlikely), or somehow one of those PIDs is getting zeroed first,
- // so that kill() sends SIGTERM to the whole process group -- this
- // process included. I'd have to build and link with a debug version
- // of APR to know for sure. It's too bad: this mechanism would be just
- // right for dealing with static autokill LLProcessPtr variables,
- // which aren't destroyed until after APR is no longer available.
-
- // Tie the lifespan of this child process to the lifespan of our APR
- // pool: on destruction of the pool, forcibly kill the process. Tell
- // APR to try SIGTERM and suspend 3 seconds. If that didn't work, use
- // SIGKILL.
- apr_pool_note_subprocess(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT);
-|*==========================================================================*/
-
- // On Windows, associate the new child process with our Job Object.
- autokill();
- }
-
- // Instantiate the proper pipe I/O machinery
- // want to be able to point to apr_proc_t::in, out, err by index
- typedef apr_file_t* apr_proc_t::*apr_proc_file_ptr;
- static apr_proc_file_ptr members[] =
- { &apr_proc_t::in, &apr_proc_t::out, &apr_proc_t::err };
- for (size_t i = 0; i < NSLOTS; ++i)
- {
- if (select[i] != APR_CHILD_BLOCK)
- continue;
- std::string desc(STRINGIZE(mDesc << ' ' << whichfile(FILESLOT(i))));
- apr_file_t* pipe(mProcess.*(members[i]));
- if (i == STDIN)
- {
- mPipes.replace(i, new WritePipeImpl(desc, pipe));
- }
- else
- {
- mPipes.replace(i, new ReadPipeImpl(desc, pipe, FILESLOT(i)));
- }
- // Removed temporaily for Xcode 7 build tests: error was:
- // "error: expression with side effects will be evaluated despite
- // being used as an operand to 'typeid' [-Werror,-Wpotentially-evaluated-expression]""
- //LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name()
- // << "('" << desc << "')" << LL_ENDL;
- }
-}
-
-// Helper to obtain a description string, given a Params block
-static std::string getDesc(const LLProcess::Params& params)
-{
- // If caller specified a description string, by all means use it.
- if (params.desc.isProvided())
- return params.desc;
-
- // Caller didn't say. Use the executable name -- but use just the filename
- // part. On Mac, for instance, full pathnames get cumbersome.
- return LLProcess::basename(params.executable);
-}
-
-//static
-std::string LLProcess::basename(const std::string& path)
-{
- // If there are Linden utility functions to manipulate pathnames, I
- // haven't found them -- and for this usage, Boost.Filesystem seems kind
- // of heavyweight.
- std::string::size_type delim = path.find_last_of("\\/");
- // If path contains no pathname delimiters, return the whole thing.
- if (delim == std::string::npos)
- return path;
-
- // Return just the part beyond the last delimiter.
- return path.substr(delim + 1);
-}
-
-LLProcess::~LLProcess()
-{
- // In the Linden viewer, there's at least one static LLProcessPtr. Its
- // destructor will be called *after* ll_cleanup_apr(). In such a case,
- // unregistering is pointless (and fatal!) -- and kill(), which also
- // relies on APR, is impossible.
- if (! gAPRPoolp)
- return;
-
- // Only in state RUNNING are we registered for callback. In UNSTARTED we
- // haven't yet registered. And since receiving the callback is the only
- // way we detect child termination, we only change from state RUNNING at
- // the same time we unregister.
- if (mStatus.mState == RUNNING)
- {
- // We're still registered for a callback: unregister. Do it before
- // we even issue the kill(): even if kill() somehow prompted an
- // instantaneous callback (unlikely), this object is going away! Any
- // information updated in this object by such a callback is no longer
- // available to any consumer anyway.
- apr_proc_other_child_unregister(this);
- // One less LLProcess to poll for
- sProcessListener.dropPoll(*this);
- }
-
- if (mAttached)
- {
- kill("destructor");
- }
-
- if (mPool)
- {
- apr_pool_destroy(mPool);
- mPool = NULL;
- }
-}
-
-bool LLProcess::kill(const std::string& who)
-{
- if (isRunning())
- {
- LL_INFOS("LLProcess") << who << " killing " << mDesc << LL_ENDL;
-
-#if LL_WINDOWS
- int sig = -1;
-#else // Posix
- int sig = SIGTERM;
-#endif
-
- ll_apr_warn_status(apr_proc_kill(&mProcess, sig));
- }
-
- return ! isRunning();
-}
-
-//static
-bool LLProcess::kill(const LLProcessPtr& p, const std::string& who)
-{
- if (! p)
- return true; // process dead! (was never running)
- return p->kill(who);
-}
-
-bool LLProcess::isRunning() const
-{
- return getStatus().mState == RUNNING;
-}
-
-//static
-bool LLProcess::isRunning(const LLProcessPtr& p)
-{
- if (! p)
- return false;
- return p->isRunning();
-}
-
-LLProcess::Status LLProcess::getStatus() const
-{
- return mStatus;
-}
-
-//static
-LLProcess::Status LLProcess::getStatus(const LLProcessPtr& p)
-{
- if (! p)
- {
- // default-constructed Status has mState == UNSTARTED
- return Status();
- }
- return p->getStatus();
-}
-
-std::string LLProcess::getStatusString() const
-{
- return getStatusString(getStatus());
-}
-
-std::string LLProcess::getStatusString(const Status& status) const
-{
- return getStatusString(mDesc, status);
-}
-
-//static
-std::string LLProcess::getStatusString(const std::string& desc, const LLProcessPtr& p)
-{
- if (! p)
- {
- // default-constructed Status has mState == UNSTARTED
- return getStatusString(desc, Status());
- }
- return desc + " " + p->getStatusString();
-}
-
-//static
-std::string LLProcess::getStatusString(const std::string& desc, const Status& status)
-{
- if (status.mState == UNSTARTED)
- return desc + " was never launched";
-
- if (status.mState == RUNNING)
- return desc + " running";
-
- if (status.mState == EXITED)
- return STRINGIZE(desc << " exited with code " << status.mData);
-
- if (status.mState == KILLED)
-#if LL_WINDOWS
- return STRINGIZE(desc << " killed with exception " << std::hex << status.mData);
-#else
- return STRINGIZE(desc << " killed by signal " << status.mData
- << " (" << apr_signal_description_get(status.mData) << ")");
-#endif
-
- return STRINGIZE(desc << " in unknown state " << status.mState << " (" << status.mData << ")");
-}
-
-// Classic-C-style APR callback
-void LLProcess::status_callback(int reason, void* data, int status)
-{
- // Our only role is to bounce this static method call back into object
- // space.
- static_cast<LLProcess*>(data)->handle_status(reason, status);
-}
-
-#define tabent(symbol) { symbol, #symbol }
-static struct ReasonCode
-{
- int code;
- const char* name;
-} reasons[] =
-{
- tabent(APR_OC_REASON_DEATH),
- tabent(APR_OC_REASON_UNWRITABLE),
- tabent(APR_OC_REASON_RESTART),
- tabent(APR_OC_REASON_UNREGISTER),
- tabent(APR_OC_REASON_LOST),
- tabent(APR_OC_REASON_RUNNING)
-};
-#undef tabent
-
-// Object-oriented callback
-void LLProcess::handle_status(int reason, int status)
-{
- {
- // This odd appearance of LL_DEBUGS is just to bracket a lookup that will
- // only be performed if in fact we're going to produce the log message.
- LL_DEBUGS("LLProcess") << empty;
- std::string reason_str;
- for (const ReasonCode& rcp : reasons)
- {
- if (reason == rcp.code)
- {
- reason_str = rcp.name;
- break;
- }
- }
- if (reason_str.empty())
- {
- reason_str = STRINGIZE("unknown reason " << reason);
- }
- LL_CONT << mDesc << ": handle_status(" << reason_str << ", " << status << ")" << LL_ENDL;
- }
-
- if (! (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST))
- {
- // We're only interested in the call when the child terminates.
- return;
- }
-
- // Somewhat oddly, APR requires that you explicitly unregister even when
- // it already knows the child has terminated. We must pass the same 'data'
- // pointer as for the register() call, which was our 'this'.
- apr_proc_other_child_unregister(this);
- // don't keep polling for a terminated process
- sProcessListener.dropPoll(*this);
- // We overload mStatus.mState to indicate whether the child is registered
- // for APR callback: only RUNNING means registered. Track that we've
- // unregistered. We know the child has terminated; might be EXITED or
- // KILLED; refine below.
- mStatus.mState = EXITED;
-
- // Make last-gasp calls for each of the ReadPipes we have on hand. Since
- // they're listening on "mainloop", we can be sure they'll eventually
- // collect all pending data from the child. But we want to be able to
- // guarantee to our consumer that by the time we post on the "postend"
- // LLEventPump, our ReadPipes are already buffering all the data there
- // will ever be from the child. That lets the "postend" listener decide
- // what to do with that final data.
- for (size_t i = 0; i < mPipes.size(); ++i)
- {
- std::string error;
- ReadPipeImpl* ppipe = getPipePtr<ReadPipeImpl>(error, FILESLOT(i));
- if (ppipe)
- {
- static LLSD trivial;
- ppipe->tick(trivial);
- }
- }
-
-// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT);
- // It's just wrong to call apr_proc_wait() here. The only way APR knows to
- // call us with APR_OC_REASON_DEATH is that it's already reaped this child
- // process, so calling wait() will only produce "huh?" from the OS. We
- // must rely on the status param passed in, which unfortunately comes
- // straight from the OS wait() call, which means we have to decode it by
- // hand.
- mStatus = interpret_status(status);
- LL_INFOS("LLProcess") << getStatusString() << LL_ENDL;
-
- // If caller requested notification on child termination, send it.
- if (! mPostend.empty())
- {
- LLEventPumps::instance().obtain(mPostend)
- .post(LLSDMap
- ("id", getProcessID())
- ("desc", mDesc)
- ("state", mStatus.mState)
- ("data", mStatus.mData)
- ("string", getStatusString())
- );
- }
-}
-
-LLProcess::id LLProcess::getProcessID() const
-{
- return mProcess.pid;
-}
-
-LLProcess::handle LLProcess::getProcessHandle() const
-{
-#if LL_WINDOWS
- return mProcess.hproc;
-#else
- return mProcess.pid;
-#endif
-}
-
-std::string LLProcess::getPipeName(FILESLOT) const
-{
- // LLProcess::FileParam::type "npipe" is not yet implemented
- return "";
-}
-
-template<class PIPETYPE>
-PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot)
-{
- if (slot >= NSLOTS)
- {
- error = STRINGIZE(mDesc << " has no slot " << slot);
- return NULL;
- }
- if (mPipes.is_null(slot))
- {
- error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a monitored pipe");
- return NULL;
- }
- // Make sure we dynamic_cast in pointer domain so we can test, rather than
- // accepting runtime's exception.
- PIPETYPE* ppipe = dynamic_cast<PIPETYPE*>(&mPipes[slot]);
- if (! ppipe)
- {
- error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name());
- return NULL;
- }
-
- error.clear();
- return ppipe;
-}
-
-template <class PIPETYPE>
-PIPETYPE& LLProcess::getPipe(FILESLOT slot)
-{
- std::string error;
- PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
- if (! wp)
- {
- LLTHROW(NoPipe(error));
- }
- return *wp;
-}
-
-template <class PIPETYPE>
-boost::optional<PIPETYPE&> LLProcess::getOptPipe(FILESLOT slot)
-{
- std::string error;
- PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
- if (! wp)
- {
- LL_DEBUGS("LLProcess") << error << LL_ENDL;
- return boost::optional<PIPETYPE&>();
- }
- return *wp;
-}
-
-LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot)
-{
- return getPipe<WritePipe>(slot);
-}
-
-boost::optional<LLProcess::WritePipe&> LLProcess::getOptWritePipe(FILESLOT slot)
-{
- return getOptPipe<WritePipe>(slot);
-}
-
-LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot)
-{
- return getPipe<ReadPipe>(slot);
-}
-
-boost::optional<LLProcess::ReadPipe&> LLProcess::getOptReadPipe(FILESLOT slot)
-{
- return getOptPipe<ReadPipe>(slot);
-}
-
-//static
-std::string LLProcess::getline(std::istream& in)
-{
- std::string line;
- std::getline(in, line);
- // Blur the distinction between "\r\n" and plain "\n". std::getline() will
- // have eaten the "\n", but we could still end up with a trailing "\r".
- std::string::size_type lastpos = line.find_last_not_of("\r");
- if (lastpos != std::string::npos)
- {
- // Found at least one character that's not a trailing '\r'. SKIP OVER
- // IT and erase the rest of the line.
- line.erase(lastpos+1);
- }
- return line;
-}
-
-std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params)
-{
- if (params.cwd.isProvided())
- {
- out << "cd " << LLStringUtil::quote(params.cwd) << ": ";
- }
- out << LLStringUtil::quote(params.executable);
- for (const std::string& arg : params.args)
- {
- out << ' ' << LLStringUtil::quote(arg);
- }
- return out;
-}
-
-/*****************************************************************************
-* Windows specific
-*****************************************************************************/
-#if LL_WINDOWS
-
-static std::string WindowsErrorString(const std::string& operation);
-
-void LLProcess::autokill()
-{
- // hopefully now handled by apr_procattr_autokill_set()
-}
-
-LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc)
-{
- // This direct Windows implementation is because we have no access to the
- // apr_proc_t struct: we expect it's been destroyed.
- if (! h)
- return 0;
-
- DWORD waitresult = WaitForSingleObject(h, 0);
- if(waitresult == WAIT_OBJECT_0)
- {
- // the process has completed.
- if (! desc.empty())
- {
- DWORD status = 0;
- if (! GetExitCodeProcess(h, &status))
- {
- LL_WARNS("LLProcess") << desc << " terminated, but "
- << WindowsErrorString("GetExitCodeProcess()") << LL_ENDL;
- }
- {
- LL_INFOS("LLProcess") << getStatusString(desc, interpret_status(status))
- << LL_ENDL;
- }
- }
- CloseHandle(h);
- return 0;
- }
-
- return h;
-}
-
-static LLProcess::Status interpret_status(int status)
-{
- LLProcess::Status result;
-
- // This bit of code is cribbed from apr/threadproc/win32/proc.c, a
- // function (unfortunately static) called why_from_exit_code():
- /* See WinNT.h STATUS_ACCESS_VIOLATION and family for how
- * this class of failures was determined
- */
- if ((status & 0xFFFF0000) == 0xC0000000)
- {
- result.mState = LLProcess::KILLED;
- }
- else
- {
- result.mState = LLProcess::EXITED;
- }
- result.mData = status;
-
- return result;
-}
-
-/// GetLastError()/FormatMessage() boilerplate
-static std::string WindowsErrorString(const std::string& operation)
-{
- auto result = GetLastError();
- return STRINGIZE(operation << " failed (" << result << "): "
- << windows_message<std::string>(result));
-}
-
-/*****************************************************************************
-* Posix specific
-*****************************************************************************/
-#else // Mac and linux
-
-#include <signal.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <sys/wait.h>
-
-void LLProcess::autokill()
-{
- // What we ought to do here is to:
- // 1. create a unique process group and run all autokill children in that
- // group (see https://jira.secondlife.com/browse/SWAT-563);
- // 2. figure out a way to intercept control when the viewer exits --
- // gracefully or not;
- // 3. when the viewer exits, kill off the aforementioned process group.
-
- // It's point 2 that's troublesome. Although I've seen some signal-
- // handling logic in the Posix viewer code, I haven't yet found any bit of
- // code that's run no matter how the viewer exits (a try/finally for the
- // whole process, as it were).
-}
-
-// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise.
-static bool reap_pid(pid_t pid, LLProcess::Status* pstatus=NULL)
-{
- LLProcess::Status dummy;
- if (! pstatus)
- {
- // If caller doesn't want to see Status, give us a target anyway so we
- // don't have to have a bunch of conditionals.
- pstatus = &dummy;
- }
-
- int status = 0;
- pid_t wait_result = ::waitpid(pid, &status, WNOHANG);
- if (wait_result == pid)
- {
- *pstatus = interpret_status(status);
- return true;
- }
- if (wait_result == 0)
- {
- pstatus->mState = LLProcess::RUNNING;
- pstatus->mData = 0;
- return false;
- }
-
- // Clear caller's Status block; caller must interpret UNSTARTED to mean
- // "if this PID was ever valid, it no longer is."
- *pstatus = LLProcess::Status();
-
- // We've dealt with the success cases: we were able to reap the child
- // (wait_result == pid) or it's still running (wait_result == 0). It may
- // be that the child terminated but didn't hang around long enough for us
- // to reap. In that case we still have no Status to report, but we can at
- // least state that it's not running.
- if (wait_result == -1 && errno == ECHILD)
- {
- // No such process -- this may mean we're ignoring SIGCHILD.
- return true;
- }
-
- // Uh, should never happen?!
- LL_WARNS("LLProcess") << "LLProcess::reap_pid(): waitpid(" << pid << ") returned "
- << wait_result << "; not meaningful?" << LL_ENDL;
- // If caller is looping until this pid terminates, and if we can't find
- // out, better to break the loop than to claim it's still running.
- return true;
-}
-
-LLProcess::id LLProcess::isRunning(id pid, const std::string& desc)
-{
- // This direct Posix implementation is because we have no access to the
- // apr_proc_t struct: we expect it's been destroyed.
- if (! pid)
- return 0;
-
- // Check whether the process has exited, and reap it if it has.
- LLProcess::Status status;
- if(reap_pid(pid, &status))
- {
- // the process has exited.
- if (! desc.empty())
- {
- std::string statstr(desc + " apparently terminated: no status available");
- // We don't just pass UNSTARTED to getStatusString() because, in
- // the context of reap_pid(), that state has special meaning.
- if (status.mState != UNSTARTED)
- {
- statstr = getStatusString(desc, status);
- }
- LL_INFOS("LLProcess") << statstr << LL_ENDL;
- }
- return 0;
- }
-
- return pid;
-}
-
-static LLProcess::Status interpret_status(int status)
-{
- LLProcess::Status result;
-
- if (WIFEXITED(status))
- {
- result.mState = LLProcess::EXITED;
- result.mData = WEXITSTATUS(status);
- }
- else if (WIFSIGNALED(status))
- {
- result.mState = LLProcess::KILLED;
- result.mData = WTERMSIG(status);
- }
- else // uh, shouldn't happen?
- {
- result.mState = LLProcess::EXITED;
- result.mData = status; // someone else will have to decode
- }
-
- return result;
-}
-
-#endif // Posix
+/** + * @file llprocess.cpp + * @brief Utility class for launching, terminating, and tracking the state of processes. + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llprocess.h" +#include "llsdutil.h" +#include "llsdserialize.h" +#include "llsingleton.h" +#include "llstring.h" +#include "stringize.h" +#include "llapr.h" +#include "apr_signal.h" +#include "llevents.h" +#include "llexception.h" + +#include <boost/bind.hpp> +#include <boost/asio/streambuf.hpp> +#include <boost/asio/buffers_iterator.hpp> +#include <iostream> +#include <stdexcept> +#include <limits> +#include <algorithm> +#include <vector> +#include <typeinfo> +#include <utility> + +/***************************************************************************** +* Helpers +*****************************************************************************/ +static const char* whichfile_[] = { "stdin", "stdout", "stderr" }; +static std::string empty; +static LLProcess::Status interpret_status(int status); +static std::string getDesc(const LLProcess::Params& params); + +static std::string whichfile(LLProcess::FILESLOT index) +{ + if (index < LL_ARRAY_SIZE(whichfile_)) + return whichfile_[index]; + return STRINGIZE("file slot " << index); +} + +/** + * Ref-counted "mainloop" listener. As long as there are still outstanding + * LLProcess objects, keep listening on "mainloop" so we can keep polling APR + * for process status. + */ +class LLProcessListener +{ + LOG_CLASS(LLProcessListener); +public: + LLProcessListener(): + mCount(0) + {} + + void addPoll(const LLProcess&) + { + // Unconditionally increment mCount. If it was zero before + // incrementing, listen on "mainloop". + if (mCount++ == 0) + { + LL_DEBUGS("LLProcess") << "listening on \"mainloop\"" << LL_ENDL; + mConnection = LLEventPumps::instance().obtain("mainloop") + .listen("LLProcessListener", boost::bind(&LLProcessListener::tick, this, _1)); + } + } + + void dropPoll(const LLProcess&) + { + // Unconditionally decrement mCount. If it's zero after decrementing, + // stop listening on "mainloop". + if (--mCount == 0) + { + LL_DEBUGS("LLProcess") << "disconnecting from \"mainloop\"" << LL_ENDL; + mConnection.disconnect(); + } + } + +private: + /// called once per frame by the "mainloop" LLEventPump + bool tick(const LLSD&) + { + // Tell APR to sense whether each registered LLProcess is still + // running and call handle_status() appropriately. We should be able + // to get the same info from an apr_proc_wait(APR_NOWAIT) call; but at + // least in APR 1.4.2, testing suggests that even with APR_NOWAIT, + // apr_proc_wait() blocks the caller. We can't have that in the + // viewer. Hence the callback rigmarole. (Once we update APR, it's + // probably worth testing again.) Also -- although there's an + // apr_proc_other_child_refresh() call, i.e. get that information for + // one specific child, it accepts an 'apr_other_child_rec_t*' that's + // mentioned NOWHERE else in the documentation or header files! I + // would use the specific call in LLProcess::getStatus() if I knew + // how. As it is, each call to apr_proc_other_child_refresh_all() will + // call callbacks for ALL still-running child processes. That's why we + // centralize such calls, using "mainloop" to ensure it happens once + // per frame, and refcounting running LLProcess objects to remain + // registered only while needed. + LL_DEBUGS("LLProcess") << "calling apr_proc_other_child_refresh_all()" << LL_ENDL; + apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); + return false; + } + + /// If this object is destroyed before mCount goes to zero, stop + /// listening on "mainloop" anyway. + LLTempBoundListener mConnection; + unsigned mCount; +}; +static LLProcessListener sProcessListener; + +/***************************************************************************** +* WritePipe and ReadPipe +*****************************************************************************/ +LLProcess::BasePipe::~BasePipe() {} +const LLProcess::BasePipe::size_type + // use funky syntax to call max() to avoid blighted max() macros + LLProcess::BasePipe::npos((std::numeric_limits<LLProcess::BasePipe::size_type>::max)()); + +class WritePipeImpl: public LLProcess::WritePipe +{ + LOG_CLASS(WritePipeImpl); +public: + WritePipeImpl(const std::string& desc, apr_file_t* pipe): + mDesc(desc), + mPipe(pipe), + // Essential to initialize our std::ostream with our special streambuf! + mStream(&mStreambuf) + { + mConnection = LLEventPumps::instance().obtain("mainloop") + .listen(LLEventPump::inventName("WritePipe"), + boost::bind(&WritePipeImpl::tick, this, _1)); + +#if ! LL_WINDOWS + // We can't count on every child process reading everything we try to + // write to it. And if the child terminates with WritePipe data still + // pending, unless we explicitly suppress it, Posix will hit us with + // SIGPIPE. That would terminate the viewer, boom. "Ignoring" it means + // APR gets the correct errno, passes it back to us, we log it, etc. + signal(SIGPIPE, SIG_IGN); +#endif + } + + virtual std::ostream& get_ostream() { return mStream; } + virtual size_type size() const { return mStreambuf.size(); } + + bool tick(const LLSD&) + { + typedef boost::asio::streambuf::const_buffers_type const_buffer_sequence; + // If there's anything to send, try to send it. + std::size_t total(mStreambuf.size()), consumed(0); + if (total) + { + const_buffer_sequence bufs = mStreambuf.data(); + // In general, our streambuf might contain a number of different + // physical buffers; iterate over those. + bool keepwriting = true; + for (const_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); + bufi != bufend && keepwriting; ++bufi) + { + // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents + // Although apr_file_write() accepts const void*, we + // manipulate const char* so we can increment the pointer. + const char* remainptr = boost::asio::buffer_cast<const char*>(*bufi); + std::size_t remainlen = boost::asio::buffer_size(*bufi); + while (remainlen) + { + // Tackle the current buffer in discrete chunks. On + // Windows, we've observed strange failures when trying to + // write big lengths (~1 MB) in a single operation. Even a + // 32K chunk seems too large. At some point along the way + // apr_file_write() returns 11 (Resource temporarily + // unavailable, i.e. EAGAIN) and says it wrote 0 bytes -- + // even though it did write the chunk! Our next write + // attempt retries with the same chunk, resulting in the + // chunk being duplicated at the child end. Using smaller + // chunks is empirically more reliable. + std::size_t towrite((std::min)(remainlen, std::size_t(4*1024))); + apr_size_t written(towrite); + apr_status_t err = apr_file_write(mPipe, remainptr, &written); + // EAGAIN is exactly what we want from a nonblocking pipe. + // Rather than waiting for data, it should return immediately. + if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) + { + LL_WARNS("LLProcess") << "apr_file_write(" << towrite << ") on " << mDesc + << " got " << err << ":" << LL_ENDL; + ll_apr_warn_status(err); + } + + // 'written' is modified to reflect the number of bytes actually + // written. Make sure we consume those later. (Don't consume them + // now, that would invalidate the buffer iterator sequence!) + consumed += written; + // don't forget to advance to next chunk of current buffer + remainptr += written; + remainlen -= written; + + char msgbuf[512]; + LL_DEBUGS("LLProcess") << "wrote " << written << " of " << towrite + << " bytes to " << mDesc + << " (original " << total << ")," + << " code " << err << ": " + << apr_strerror(err, msgbuf, sizeof(msgbuf)) + << LL_ENDL; + + // The parent end of this pipe is nonblocking. If we weren't able + // to write everything we wanted, don't keep banging on it -- that + // won't change until the child reads some. Wait for next tick(). + if (written < towrite) + { + keepwriting = false; // break outer loop over buffers too + break; + } + } // next chunk of current buffer + } // next buffer + // In all, we managed to write 'consumed' bytes. Remove them from the + // streambuf so we don't keep trying to send them. This could be + // anywhere from 0 up to mStreambuf.size(); anything we haven't yet + // sent, we'll try again later. + mStreambuf.consume(consumed); + } + + return false; + } + +private: + std::string mDesc; + apr_file_t* mPipe; + LLTempBoundListener mConnection; + boost::asio::streambuf mStreambuf; + std::ostream mStream; +}; + +class ReadPipeImpl: public LLProcess::ReadPipe +{ + LOG_CLASS(ReadPipeImpl); +public: + ReadPipeImpl(const std::string& desc, apr_file_t* pipe, LLProcess::FILESLOT index): + mDesc(desc), + mPipe(pipe), + mIndex(index), + // Essential to initialize our std::istream with our special streambuf! + mStream(&mStreambuf), + mPump("ReadPipe", true), // tweak name as needed to avoid collisions + mLimit(0), + mEOF(false) + { + mConnection = LLEventPumps::instance().obtain("mainloop") + .listen(LLEventPump::inventName("ReadPipe"), + boost::bind(&ReadPipeImpl::tick, this, _1)); + } + + ~ReadPipeImpl() + { + if (mConnection.connected()) + { + mConnection.disconnect(); + } + } + + // Much of the implementation is simply connecting the abstract virtual + // methods with implementation data concealed from the base class. + virtual std::istream& get_istream() { return mStream; } + virtual std::string getline() { return LLProcess::getline(mStream); } + virtual LLEventPump& getPump() { return mPump; } + virtual void setLimit(size_type limit) { mLimit = limit; } + virtual size_type getLimit() const { return mLimit; } + virtual size_type size() const { return mStreambuf.size(); } + + virtual std::string read(size_type len) + { + // Read specified number of bytes into a buffer. + size_type readlen((std::min)(size(), len)); + // Formally, &buffer[0] is invalid for a vector of size() 0. Exit + // early in that situation. + if (! readlen) + return ""; + // Make a buffer big enough. + std::vector<char> buffer(readlen); + mStream.read(&buffer[0], readlen); + // Since we've already clamped 'readlen', we can think of no reason + // why mStream.read() should read fewer than 'readlen' bytes. + // Nonetheless, use the actual retrieved length. + return std::string(&buffer[0], mStream.gcount()); + } + + virtual std::string peek(size_type offset=0, size_type len=npos) const + { + // Constrain caller's offset and len to overlap actual buffer content. + std::size_t real_offset = (std::min)(mStreambuf.size(), std::size_t(offset)); + size_type want_end = (len == npos)? npos : (real_offset + len); + std::size_t real_end = (std::min)(mStreambuf.size(), std::size_t(want_end)); + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + return std::string(boost::asio::buffers_begin(cbufs) + real_offset, + boost::asio::buffers_begin(cbufs) + real_end); + } + + virtual size_type find(const std::string& seek, size_type offset=0) const + { + // If we're passing a string of length 1, use find(char), which can + // use an O(n) std::find() rather than the O(n^2) std::search(). + if (seek.length() == 1) + { + return find(seek[0], offset); + } + + // If offset is beyond the whole buffer, can't even construct a valid + // iterator range; can't possibly find the string we seek. + if (offset > mStreambuf.size()) + { + return npos; + } + + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type> + begin(boost::asio::buffers_begin(cbufs)), + end (boost::asio::buffers_end(cbufs)), + found(std::search(begin + offset, end, seek.begin(), seek.end())); + return (found == end)? npos : (found - begin); + } + + virtual size_type find(char seek, size_type offset=0) const + { + // If offset is beyond the whole buffer, can't even construct a valid + // iterator range; can't possibly find the char we seek. + if (offset > mStreambuf.size()) + { + return npos; + } + + boost::asio::streambuf::const_buffers_type cbufs = mStreambuf.data(); + boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type> + begin(boost::asio::buffers_begin(cbufs)), + end (boost::asio::buffers_end(cbufs)), + found(std::find(begin + offset, end, seek)); + return (found == end)? npos : (found - begin); + } + + bool tick(const LLSD&) + { + // Once we've hit EOF, skip all the rest of this. + if (mEOF) + return false; + + typedef boost::asio::streambuf::mutable_buffers_type mutable_buffer_sequence; + // Try, every time, to read into our streambuf. In fact, we have no + // idea how much data the child might be trying to send: keep trying + // until we're convinced we've temporarily exhausted the pipe. + enum PipeState { RETRY, EXHAUSTED, CLOSED }; + PipeState state = RETRY; + std::size_t committed(0); + do + { + // attempt to read an arbitrary size + mutable_buffer_sequence bufs = mStreambuf.prepare(4096); + // In general, the mutable_buffer_sequence returned by prepare() might + // contain a number of different physical buffers; iterate over those. + std::size_t tocommit(0); + for (mutable_buffer_sequence::const_iterator bufi(bufs.begin()), bufend(bufs.end()); + bufi != bufend; ++bufi) + { + // http://www.boost.org/doc/libs/1_49_0_beta1/doc/html/boost_asio/reference/buffer.html#boost_asio.reference.buffer.accessing_buffer_contents + std::size_t toread(boost::asio::buffer_size(*bufi)); + apr_size_t gotten(toread); + apr_status_t err = apr_file_read(mPipe, + boost::asio::buffer_cast<void*>(*bufi), + &gotten); + // EAGAIN is exactly what we want from a nonblocking pipe. + // Rather than waiting for data, it should return immediately. + if (! (err == APR_SUCCESS || APR_STATUS_IS_EAGAIN(err))) + { + // Handle EOF specially: it's part of normal-case processing. + if (err == APR_EOF) + { + LL_DEBUGS("LLProcess") << "EOF on " << mDesc << LL_ENDL; + } + else + { + LL_WARNS("LLProcess") << "apr_file_read(" << toread << ") on " << mDesc + << " got " << err << ":" << LL_ENDL; + ll_apr_warn_status(err); + } + // Either way, though, we won't need any more tick() calls. + mConnection.disconnect(); + // Ignore any subsequent calls we might get anyway. + mEOF = true; + state = CLOSED; // also break outer retry loop + break; + } + + // 'gotten' was modified to reflect the number of bytes actually + // received. Make sure we commit those later. (Don't commit them + // now, that would invalidate the buffer iterator sequence!) + tocommit += gotten; + LL_DEBUGS("LLProcess") << "filled " << gotten << " of " << toread + << " bytes from " << mDesc << LL_ENDL; + + // The parent end of this pipe is nonblocking. If we weren't even + // able to fill this buffer, don't loop to try to fill the next -- + // that won't change until the child writes more. Wait for next + // tick(). + if (gotten < toread) + { + // break outer retry loop too + state = EXHAUSTED; + break; + } + } + + // Don't forget to "commit" the data! + mStreambuf.commit(tocommit); + committed += tocommit; + + // state is changed from RETRY when we can't fill any one buffer + // of the mutable_buffer_sequence established by the current + // prepare() call -- whether due to error or not enough bytes. + // That is, if state is still RETRY, we've filled every physical + // buffer in the mutable_buffer_sequence. In that case, for all we + // know, the child might have still more data pending -- go for it! + } while (state == RETRY); + + // Once we recognize that the pipe is closed, make one more call to + // listener. The listener might be waiting for a particular substring + // to arrive, or a particular length of data or something. The event + // with "eof" == true announces that nothing further will arrive, so + // use it or lose it. + if (committed || state == CLOSED) + { + // If we actually received new data, publish it on our LLEventPump + // as advertised. Constrain it by mLimit. But show listener the + // actual accumulated buffer size, regardless of mLimit. + size_type datasize((std::min)(mLimit, size_type(mStreambuf.size()))); + mPump.post(LLSDMap + ("data", peek(0, datasize)) + ("len", LLSD::Integer(mStreambuf.size())) + ("slot", LLSD::Integer(mIndex)) + ("name", whichfile(mIndex)) + ("desc", mDesc) + ("eof", state == CLOSED)); + } + + return false; + } + +private: + std::string mDesc; + apr_file_t* mPipe; + LLProcess::FILESLOT mIndex; + LLTempBoundListener mConnection; + boost::asio::streambuf mStreambuf; + std::istream mStream; + LLEventStream mPump; + size_type mLimit; + bool mEOF; +}; + +/***************************************************************************** +* LLProcess itself +*****************************************************************************/ +/// Need an exception to avoid constructing an invalid LLProcess object, but +/// internal use only +struct LLProcessError: public LLException +{ + LLProcessError(const std::string& msg): LLException(msg) {} +}; + +LLProcessPtr LLProcess::create(const LLSDOrParams& params) +{ + try + { + return LLProcessPtr(new LLProcess(params)); + } + catch (const LLProcessError& e) + { + LL_WARNS("LLProcess") << e.what() << LL_ENDL; + + // If caller is requesting an event on process termination, send one + // indicating bad launch. This may prevent someone waiting forever for + // a termination post that can't arrive because the child never + // started. + if (params.postend.isProvided()) + { + LLEventPumps::instance().obtain(params.postend) + .post(LLSDMap + // no "id" + ("desc", getDesc(params)) + ("state", LLProcess::UNSTARTED) + // no "data" + ("string", e.what()) + ); + } + + return LLProcessPtr(); + } +} + +/// Call an apr function returning apr_status_t. On failure, log warning and +/// throw LLProcessError mentioning the function call that produced that +/// result. +#define chkapr(func) \ + if (ll_apr_warn_status(func)) \ + throw LLProcessError(#func " failed") + +LLProcess::LLProcess(const LLSDOrParams& params): + mAutokill(params.autokill), + // Because 'autokill' originally meant both 'autokill' and 'attached', to + // preserve existing semantics, we promise that mAttached defaults to the + // same setting as mAutokill. + mAttached(params.attached.isProvided()? params.attached : params.autokill), + mPool(NULL), + mPipes(NSLOTS) +{ + // Hmm, when you construct a ptr_vector with a size, it merely reserves + // space, it doesn't actually make it that big. Explicitly make it bigger. + // Because of ptr_vector's odd semantics, have to push_back(0) the right + // number of times! resize() wants to default-construct new BasePipe + // instances, which fails because it's pure virtual. But because of the + // constructor call, these push_back() calls should require no new + // allocation. + for (size_t i = 0; i < mPipes.capacity(); ++i) + mPipes.push_back(0); + + if (! params.validateBlock(true)) + { + LLTHROW(LLProcessError(STRINGIZE("not launched: failed parameter validation\n" + << LLSDNotationStreamer(params)))); + } + + mPostend = params.postend; + + apr_pool_create(&mPool, gAPRPoolp); + if (!mPool) + { + LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool"))); + } + + apr_procattr_t *procattr = NULL; + chkapr(apr_procattr_create(&procattr, mPool)); + + // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to + // constrain the set of handles passed to the child process. Before we + // changed to APR, the Windows implementation of LLProcessLauncher called + // CreateProcess(bInheritHandles=false), meaning to pass NO open handles + // to the child process. Now that we support pipes, though, we must allow + // apr_proc_create() to pass bInheritHandles=true. But without taking + // special pains, that causes trouble in a number of ways, due to the fact + // that the viewer is constantly opening and closing files -- most of + // which CreateProcess() passes to every child process! +#if ! defined(APR_HAS_PROCATTR_CONSTRAIN_HANDLE_SET) + // Our special preprocessor symbol isn't even defined -- wrong APR + LL_WARNS("LLProcess") << "This version of APR lacks Linden " + << "apr_procattr_constrain_handle_set() extension" << LL_ENDL; +#else + chkapr(apr_procattr_constrain_handle_set(procattr, 1)); +#endif + + // For which of stdin, stdout, stderr should we create a pipe to the + // child? In the viewer, there are only a couple viable + // apr_procattr_io_set() alternatives: inherit the viewer's own stdxxx + // handle (APR_NO_PIPE, e.g. for stdout, stderr), or create a pipe that's + // blocking on the child end but nonblocking at the viewer end + // (APR_CHILD_BLOCK). + // Other major options could include explicitly creating a single APR pipe + // and passing it as both stdout and stderr (apr_procattr_child_out_set(), + // apr_procattr_child_err_set()), or accepting a filename, opening it and + // passing that apr_file_t (simple <, >, 2> redirect emulation). + std::vector<apr_int32_t> select; + for (const FileParam& fparam : params.files) + { + // Every iteration, we're going to append an item to 'select'. At the + // top of the loop, its size() is, in effect, an index. Use that to + // pick a string description for messages. + std::string which(whichfile(FILESLOT(select.size()))); + if (fparam.type().empty()) // inherit our file descriptor + { + select.push_back(APR_NO_PIPE); + } + else if (fparam.type() == "pipe") // anonymous pipe + { + if (! fparam.name().empty()) + { + LL_WARNS("LLProcess") << "For " << params.executable() + << ": internal names for reusing pipes ('" + << fparam.name() << "' for " << which + << ") are not yet supported -- creating distinct pipe" + << LL_ENDL; + } + // The viewer can't block for anything: the parent end MUST be + // nonblocking. As the APR documentation itself points out, it + // makes very little sense to set nonblocking I/O for the child + // end of a pipe: only a specially-written child could deal with + // that. + select.push_back(APR_CHILD_BLOCK); + } + else + { + LLTHROW(LLProcessError(STRINGIZE("For " << params.executable() + << ": unsupported FileParam for " << which + << ": type='" << fparam.type() + << "', name='" << fparam.name() << "'"))); + } + } + // By default, pass APR_NO_PIPE for unspecified slots. + while (select.size() < NSLOTS) + { + select.push_back(APR_NO_PIPE); + } + chkapr(apr_procattr_io_set(procattr, select[STDIN], select[STDOUT], select[STDERR])); + + // Thumbs down on implicitly invoking the shell to invoke the child. From + // our point of view, the other major alternative to APR_PROGRAM_PATH + // would be APR_PROGRAM_ENV: still copy environment, but require full + // executable pathname. I don't see a downside to searching the PATH, + // though: if our caller wants (e.g.) a specific Python interpreter, s/he + // can still pass the full pathname. + chkapr(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH)); + // YES, do extra work if necessary to report child exec() failures back to + // parent process. + chkapr(apr_procattr_error_check_set(procattr, 1)); + // Do not start a non-autokill child in detached state. On Posix + // platforms, this setting attempts to daemonize the new child, closing + // std handles and the like, and that's a bit more detachment than we + // want. autokill=false just means not to implicitly kill the child when + // the parent terminates! +// chkapr(apr_procattr_detach_set(procattr, mAutokill? 0 : 1)); + + if (mAutokill) + { +#if ! defined(APR_HAS_PROCATTR_AUTOKILL_SET) + // Our special preprocessor symbol isn't even defined -- wrong APR + LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL; +#elif ! APR_HAS_PROCATTR_AUTOKILL_SET + // Symbol is defined, but to 0: expect apr_procattr_autokill_set() to + // return APR_ENOTIMPL. +#else // APR_HAS_PROCATTR_AUTOKILL_SET nonzero + ll_apr_warn_status(apr_procattr_autokill_set(procattr, 1)); +#endif + } + + // In preparation for calling apr_proc_create(), we collect a number of + // const char* pointers obtained from std::string::c_str(). Turns out + // LLInitParam::Block's helpers Optional, Mandatory, Multiple et al. + // guarantee that converting to the wrapped type (std::string in our + // case), e.g. by calling operator(), returns a reference to *the same + // instance* of the wrapped type that's stored in our Block subclass. + // That's important! We know 'params' persists throughout this method + // call; but without that guarantee, we'd have to assume that converting + // one of its members to std::string might return a different (temp) + // instance. Capturing the c_str() from a temporary std::string is Bad Bad + // Bad. But armed with this knowledge, when you see params.cwd().c_str(), + // grit your teeth and smile and carry on. + + if (params.cwd.isProvided()) + { + chkapr(apr_procattr_dir_set(procattr, params.cwd().c_str())); + } + + // create an argv vector for the child process + std::vector<const char*> argv; + + // Add the executable path. See above remarks about c_str(). + argv.push_back(params.executable().c_str()); + + // Add arguments. See above remarks about c_str(). + for (const std::string& arg : params.args) + { + argv.push_back(arg.c_str()); + } + + // terminate with a null pointer + argv.push_back(NULL); + + // Launch! The NULL would be the environment block, if we were passing + // one. Hand-expand chkapr() macro so we can fill in the actual command + // string instead of the variable names. + if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, + mPool))) + { + LLTHROW(LLProcessError(STRINGIZE(params << " failed"))); + } + + // arrange to call status_callback() + apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, + mPool); + // and make sure we poll it once per "mainloop" tick + sProcessListener.addPoll(*this); + mStatus.mState = RUNNING; + + mDesc = STRINGIZE(getDesc(params) << " (" << mProcess.pid << ')'); + LL_INFOS("LLProcess") << mDesc << ": launched " << params << LL_ENDL; + + // Unless caller explicitly turned off autokill (child should persist), + // take steps to terminate the child. This is all suspenders-and-belt: in + // theory our destructor should kill an autokill child, but in practice + // that doesn't always work (e.g. VWR-21538). + if (mAutokill) + { +/*==========================================================================*| + // NO: There may be an APR bug, not sure -- but at least on Mac, when + // gAPRPoolp is destroyed, OUR process receives SIGTERM! Apparently + // either our own PID is getting into the list of processes to kill() + // (unlikely), or somehow one of those PIDs is getting zeroed first, + // so that kill() sends SIGTERM to the whole process group -- this + // process included. I'd have to build and link with a debug version + // of APR to know for sure. It's too bad: this mechanism would be just + // right for dealing with static autokill LLProcessPtr variables, + // which aren't destroyed until after APR is no longer available. + + // Tie the lifespan of this child process to the lifespan of our APR + // pool: on destruction of the pool, forcibly kill the process. Tell + // APR to try SIGTERM and suspend 3 seconds. If that didn't work, use + // SIGKILL. + apr_pool_note_subprocess(gAPRPoolp, &mProcess, APR_KILL_AFTER_TIMEOUT); +|*==========================================================================*/ + + // On Windows, associate the new child process with our Job Object. + autokill(); + } + + // Instantiate the proper pipe I/O machinery + // want to be able to point to apr_proc_t::in, out, err by index + typedef apr_file_t* apr_proc_t::*apr_proc_file_ptr; + static apr_proc_file_ptr members[] = + { &apr_proc_t::in, &apr_proc_t::out, &apr_proc_t::err }; + for (size_t i = 0; i < NSLOTS; ++i) + { + if (select[i] != APR_CHILD_BLOCK) + continue; + std::string desc(STRINGIZE(mDesc << ' ' << whichfile(FILESLOT(i)))); + apr_file_t* pipe(mProcess.*(members[i])); + if (i == STDIN) + { + mPipes.replace(i, new WritePipeImpl(desc, pipe)); + } + else + { + mPipes.replace(i, new ReadPipeImpl(desc, pipe, FILESLOT(i))); + } + // Removed temporaily for Xcode 7 build tests: error was: + // "error: expression with side effects will be evaluated despite + // being used as an operand to 'typeid' [-Werror,-Wpotentially-evaluated-expression]"" + //LL_DEBUGS("LLProcess") << "Instantiating " << typeid(mPipes[i]).name() + // << "('" << desc << "')" << LL_ENDL; + } +} + +// Helper to obtain a description string, given a Params block +static std::string getDesc(const LLProcess::Params& params) +{ + // If caller specified a description string, by all means use it. + if (params.desc.isProvided()) + return params.desc; + + // Caller didn't say. Use the executable name -- but use just the filename + // part. On Mac, for instance, full pathnames get cumbersome. + return LLProcess::basename(params.executable); +} + +//static +std::string LLProcess::basename(const std::string& path) +{ + // If there are Linden utility functions to manipulate pathnames, I + // haven't found them -- and for this usage, Boost.Filesystem seems kind + // of heavyweight. + std::string::size_type delim = path.find_last_of("\\/"); + // If path contains no pathname delimiters, return the whole thing. + if (delim == std::string::npos) + return path; + + // Return just the part beyond the last delimiter. + return path.substr(delim + 1); +} + +LLProcess::~LLProcess() +{ + // In the Linden viewer, there's at least one static LLProcessPtr. Its + // destructor will be called *after* ll_cleanup_apr(). In such a case, + // unregistering is pointless (and fatal!) -- and kill(), which also + // relies on APR, is impossible. + if (! gAPRPoolp) + return; + + // Only in state RUNNING are we registered for callback. In UNSTARTED we + // haven't yet registered. And since receiving the callback is the only + // way we detect child termination, we only change from state RUNNING at + // the same time we unregister. + if (mStatus.mState == RUNNING) + { + // We're still registered for a callback: unregister. Do it before + // we even issue the kill(): even if kill() somehow prompted an + // instantaneous callback (unlikely), this object is going away! Any + // information updated in this object by such a callback is no longer + // available to any consumer anyway. + apr_proc_other_child_unregister(this); + // One less LLProcess to poll for + sProcessListener.dropPoll(*this); + } + + if (mAttached) + { + kill("destructor"); + } + + if (mPool) + { + apr_pool_destroy(mPool); + mPool = NULL; + } +} + +bool LLProcess::kill(const std::string& who) +{ + if (isRunning()) + { + LL_INFOS("LLProcess") << who << " killing " << mDesc << LL_ENDL; + +#if LL_WINDOWS + int sig = -1; +#else // Posix + int sig = SIGTERM; +#endif + + ll_apr_warn_status(apr_proc_kill(&mProcess, sig)); + } + + return ! isRunning(); +} + +//static +bool LLProcess::kill(const LLProcessPtr& p, const std::string& who) +{ + if (! p) + return true; // process dead! (was never running) + return p->kill(who); +} + +bool LLProcess::isRunning() const +{ + return getStatus().mState == RUNNING; +} + +//static +bool LLProcess::isRunning(const LLProcessPtr& p) +{ + if (! p) + return false; + return p->isRunning(); +} + +LLProcess::Status LLProcess::getStatus() const +{ + return mStatus; +} + +//static +LLProcess::Status LLProcess::getStatus(const LLProcessPtr& p) +{ + if (! p) + { + // default-constructed Status has mState == UNSTARTED + return Status(); + } + return p->getStatus(); +} + +std::string LLProcess::getStatusString() const +{ + return getStatusString(getStatus()); +} + +std::string LLProcess::getStatusString(const Status& status) const +{ + return getStatusString(mDesc, status); +} + +//static +std::string LLProcess::getStatusString(const std::string& desc, const LLProcessPtr& p) +{ + if (! p) + { + // default-constructed Status has mState == UNSTARTED + return getStatusString(desc, Status()); + } + return desc + " " + p->getStatusString(); +} + +//static +std::string LLProcess::getStatusString(const std::string& desc, const Status& status) +{ + if (status.mState == UNSTARTED) + return desc + " was never launched"; + + if (status.mState == RUNNING) + return desc + " running"; + + if (status.mState == EXITED) + return STRINGIZE(desc << " exited with code " << status.mData); + + if (status.mState == KILLED) +#if LL_WINDOWS + return STRINGIZE(desc << " killed with exception " << std::hex << status.mData); +#else + return STRINGIZE(desc << " killed by signal " << status.mData + << " (" << apr_signal_description_get(status.mData) << ")"); +#endif + + return STRINGIZE(desc << " in unknown state " << status.mState << " (" << status.mData << ")"); +} + +// Classic-C-style APR callback +void LLProcess::status_callback(int reason, void* data, int status) +{ + // Our only role is to bounce this static method call back into object + // space. + static_cast<LLProcess*>(data)->handle_status(reason, status); +} + +#define tabent(symbol) { symbol, #symbol } +static struct ReasonCode +{ + int code; + const char* name; +} reasons[] = +{ + tabent(APR_OC_REASON_DEATH), + tabent(APR_OC_REASON_UNWRITABLE), + tabent(APR_OC_REASON_RESTART), + tabent(APR_OC_REASON_UNREGISTER), + tabent(APR_OC_REASON_LOST), + tabent(APR_OC_REASON_RUNNING) +}; +#undef tabent + +// Object-oriented callback +void LLProcess::handle_status(int reason, int status) +{ + { + // This odd appearance of LL_DEBUGS is just to bracket a lookup that will + // only be performed if in fact we're going to produce the log message. + LL_DEBUGS("LLProcess") << empty; + std::string reason_str; + for (const ReasonCode& rcp : reasons) + { + if (reason == rcp.code) + { + reason_str = rcp.name; + break; + } + } + if (reason_str.empty()) + { + reason_str = STRINGIZE("unknown reason " << reason); + } + LL_CONT << mDesc << ": handle_status(" << reason_str << ", " << status << ")" << LL_ENDL; + } + + if (! (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST)) + { + // We're only interested in the call when the child terminates. + return; + } + + // Somewhat oddly, APR requires that you explicitly unregister even when + // it already knows the child has terminated. We must pass the same 'data' + // pointer as for the register() call, which was our 'this'. + apr_proc_other_child_unregister(this); + // don't keep polling for a terminated process + sProcessListener.dropPoll(*this); + // We overload mStatus.mState to indicate whether the child is registered + // for APR callback: only RUNNING means registered. Track that we've + // unregistered. We know the child has terminated; might be EXITED or + // KILLED; refine below. + mStatus.mState = EXITED; + + // Make last-gasp calls for each of the ReadPipes we have on hand. Since + // they're listening on "mainloop", we can be sure they'll eventually + // collect all pending data from the child. But we want to be able to + // guarantee to our consumer that by the time we post on the "postend" + // LLEventPump, our ReadPipes are already buffering all the data there + // will ever be from the child. That lets the "postend" listener decide + // what to do with that final data. + for (size_t i = 0; i < mPipes.size(); ++i) + { + std::string error; + ReadPipeImpl* ppipe = getPipePtr<ReadPipeImpl>(error, FILESLOT(i)); + if (ppipe) + { + static LLSD trivial; + ppipe->tick(trivial); + } + } + +// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); + // It's just wrong to call apr_proc_wait() here. The only way APR knows to + // call us with APR_OC_REASON_DEATH is that it's already reaped this child + // process, so calling wait() will only produce "huh?" from the OS. We + // must rely on the status param passed in, which unfortunately comes + // straight from the OS wait() call, which means we have to decode it by + // hand. + mStatus = interpret_status(status); + LL_INFOS("LLProcess") << getStatusString() << LL_ENDL; + + // If caller requested notification on child termination, send it. + if (! mPostend.empty()) + { + LLEventPumps::instance().obtain(mPostend) + .post(LLSDMap + ("id", getProcessID()) + ("desc", mDesc) + ("state", mStatus.mState) + ("data", mStatus.mData) + ("string", getStatusString()) + ); + } +} + +LLProcess::id LLProcess::getProcessID() const +{ + return mProcess.pid; +} + +LLProcess::handle LLProcess::getProcessHandle() const +{ +#if LL_WINDOWS + return mProcess.hproc; +#else + return mProcess.pid; +#endif +} + +std::string LLProcess::getPipeName(FILESLOT) const +{ + // LLProcess::FileParam::type "npipe" is not yet implemented + return ""; +} + +template<class PIPETYPE> +PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot) +{ + if (slot >= NSLOTS) + { + error = STRINGIZE(mDesc << " has no slot " << slot); + return NULL; + } + if (mPipes.is_null(slot)) + { + error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a monitored pipe"); + return NULL; + } + // Make sure we dynamic_cast in pointer domain so we can test, rather than + // accepting runtime's exception. + PIPETYPE* ppipe = dynamic_cast<PIPETYPE*>(&mPipes[slot]); + if (! ppipe) + { + error = STRINGIZE(mDesc << ' ' << whichfile(slot) << " not a " << typeid(PIPETYPE).name()); + return NULL; + } + + error.clear(); + return ppipe; +} + +template <class PIPETYPE> +PIPETYPE& LLProcess::getPipe(FILESLOT slot) +{ + std::string error; + PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot); + if (! wp) + { + LLTHROW(NoPipe(error)); + } + return *wp; +} + +template <class PIPETYPE> +boost::optional<PIPETYPE&> LLProcess::getOptPipe(FILESLOT slot) +{ + std::string error; + PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot); + if (! wp) + { + LL_DEBUGS("LLProcess") << error << LL_ENDL; + return boost::optional<PIPETYPE&>(); + } + return *wp; +} + +LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot) +{ + return getPipe<WritePipe>(slot); +} + +boost::optional<LLProcess::WritePipe&> LLProcess::getOptWritePipe(FILESLOT slot) +{ + return getOptPipe<WritePipe>(slot); +} + +LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot) +{ + return getPipe<ReadPipe>(slot); +} + +boost::optional<LLProcess::ReadPipe&> LLProcess::getOptReadPipe(FILESLOT slot) +{ + return getOptPipe<ReadPipe>(slot); +} + +//static +std::string LLProcess::getline(std::istream& in) +{ + std::string line; + std::getline(in, line); + // Blur the distinction between "\r\n" and plain "\n". std::getline() will + // have eaten the "\n", but we could still end up with a trailing "\r". + std::string::size_type lastpos = line.find_last_not_of("\r"); + if (lastpos != std::string::npos) + { + // Found at least one character that's not a trailing '\r'. SKIP OVER + // IT and erase the rest of the line. + line.erase(lastpos+1); + } + return line; +} + +std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) +{ + if (params.cwd.isProvided()) + { + out << "cd " << LLStringUtil::quote(params.cwd) << ": "; + } + out << LLStringUtil::quote(params.executable); + for (const std::string& arg : params.args) + { + out << ' ' << LLStringUtil::quote(arg); + } + return out; +} + +/***************************************************************************** +* Windows specific +*****************************************************************************/ +#if LL_WINDOWS + +static std::string WindowsErrorString(const std::string& operation); + +void LLProcess::autokill() +{ + // hopefully now handled by apr_procattr_autokill_set() +} + +LLProcess::handle LLProcess::isRunning(handle h, const std::string& desc) +{ + // This direct Windows implementation is because we have no access to the + // apr_proc_t struct: we expect it's been destroyed. + if (! h) + return 0; + + DWORD waitresult = WaitForSingleObject(h, 0); + if(waitresult == WAIT_OBJECT_0) + { + // the process has completed. + if (! desc.empty()) + { + DWORD status = 0; + if (! GetExitCodeProcess(h, &status)) + { + LL_WARNS("LLProcess") << desc << " terminated, but " + << WindowsErrorString("GetExitCodeProcess()") << LL_ENDL; + } + { + LL_INFOS("LLProcess") << getStatusString(desc, interpret_status(status)) + << LL_ENDL; + } + } + CloseHandle(h); + return 0; + } + + return h; +} + +static LLProcess::Status interpret_status(int status) +{ + LLProcess::Status result; + + // This bit of code is cribbed from apr/threadproc/win32/proc.c, a + // function (unfortunately static) called why_from_exit_code(): + /* See WinNT.h STATUS_ACCESS_VIOLATION and family for how + * this class of failures was determined + */ + if ((status & 0xFFFF0000) == 0xC0000000) + { + result.mState = LLProcess::KILLED; + } + else + { + result.mState = LLProcess::EXITED; + } + result.mData = status; + + return result; +} + +/// GetLastError()/FormatMessage() boilerplate +static std::string WindowsErrorString(const std::string& operation) +{ + auto result = GetLastError(); + return STRINGIZE(operation << " failed (" << result << "): " + << windows_message<std::string>(result)); +} + +/***************************************************************************** +* Posix specific +*****************************************************************************/ +#else // Mac and linux + +#include <signal.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/wait.h> + +void LLProcess::autokill() +{ + // What we ought to do here is to: + // 1. create a unique process group and run all autokill children in that + // group (see https://jira.secondlife.com/browse/SWAT-563); + // 2. figure out a way to intercept control when the viewer exits -- + // gracefully or not; + // 3. when the viewer exits, kill off the aforementioned process group. + + // It's point 2 that's troublesome. Although I've seen some signal- + // handling logic in the Posix viewer code, I haven't yet found any bit of + // code that's run no matter how the viewer exits (a try/finally for the + // whole process, as it were). +} + +// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise. +static bool reap_pid(pid_t pid, LLProcess::Status* pstatus=NULL) +{ + LLProcess::Status dummy; + if (! pstatus) + { + // If caller doesn't want to see Status, give us a target anyway so we + // don't have to have a bunch of conditionals. + pstatus = &dummy; + } + + int status = 0; + pid_t wait_result = ::waitpid(pid, &status, WNOHANG); + if (wait_result == pid) + { + *pstatus = interpret_status(status); + return true; + } + if (wait_result == 0) + { + pstatus->mState = LLProcess::RUNNING; + pstatus->mData = 0; + return false; + } + + // Clear caller's Status block; caller must interpret UNSTARTED to mean + // "if this PID was ever valid, it no longer is." + *pstatus = LLProcess::Status(); + + // We've dealt with the success cases: we were able to reap the child + // (wait_result == pid) or it's still running (wait_result == 0). It may + // be that the child terminated but didn't hang around long enough for us + // to reap. In that case we still have no Status to report, but we can at + // least state that it's not running. + if (wait_result == -1 && errno == ECHILD) + { + // No such process -- this may mean we're ignoring SIGCHILD. + return true; + } + + // Uh, should never happen?! + LL_WARNS("LLProcess") << "LLProcess::reap_pid(): waitpid(" << pid << ") returned " + << wait_result << "; not meaningful?" << LL_ENDL; + // If caller is looping until this pid terminates, and if we can't find + // out, better to break the loop than to claim it's still running. + return true; +} + +LLProcess::id LLProcess::isRunning(id pid, const std::string& desc) +{ + // This direct Posix implementation is because we have no access to the + // apr_proc_t struct: we expect it's been destroyed. + if (! pid) + return 0; + + // Check whether the process has exited, and reap it if it has. + LLProcess::Status status; + if(reap_pid(pid, &status)) + { + // the process has exited. + if (! desc.empty()) + { + std::string statstr(desc + " apparently terminated: no status available"); + // We don't just pass UNSTARTED to getStatusString() because, in + // the context of reap_pid(), that state has special meaning. + if (status.mState != UNSTARTED) + { + statstr = getStatusString(desc, status); + } + LL_INFOS("LLProcess") << statstr << LL_ENDL; + } + return 0; + } + + return pid; +} + +static LLProcess::Status interpret_status(int status) +{ + LLProcess::Status result; + + if (WIFEXITED(status)) + { + result.mState = LLProcess::EXITED; + result.mData = WEXITSTATUS(status); + } + else if (WIFSIGNALED(status)) + { + result.mState = LLProcess::KILLED; + result.mData = WTERMSIG(status); + } + else // uh, shouldn't happen? + { + result.mState = LLProcess::EXITED; + result.mData = status; // someone else will have to decode + } + + return result; +} + +#endif // Posix diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 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 <chrono>
-
-#include "llstl.h"
-#include "lltimer.h" // ms_sleep()
-#include "llmutex.h"
-
-//============================================================================
-
-// MAIN THREAD
-LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) :
- LLThread(name),
- mIdleThread(true),
- mNextHandle(0),
- mStarted(false),
- mThreaded(threaded),
- mRequestQueue(name, 1024 * 1024)
-{
- llassert(threaded); // not threaded implementation is deprecated
- mMainQueue = LL::WorkQueue::getInstance("mainloop");
-
- if (mThreaded)
- {
- if(should_pause)
- {
- pause() ; //call this before start the thread.
- }
-
- start();
- }
-}
-
-// MAIN THREAD
-LLQueuedThread::~LLQueuedThread()
-{
- if (!mThreaded)
- {
- endThread();
- }
- shutdown();
- // ~LLThread() will be called here
-}
-
-void LLQueuedThread::shutdown()
-{
- setQuitting();
-
- unpause(); // MAIN THREAD
- if (mThreaded)
- {
- if (mRequestQueue.size() == 0)
- {
- mRequestQueue.close();
- }
-
- S32 timeout = 100;
- for ( ; timeout>0; timeout--)
- {
- if (isStopped())
- {
- break;
- }
- ms_sleep(100);
- LLThread::yield();
- }
- if (timeout == 0)
- {
- LL_WARNS() << "~LLQueuedThread (" << mName << ") timed out!" << LL_ENDL;
- }
- }
- else
- {
- mStatus = STOPPED;
- }
-
- QueuedRequest* req;
- S32 active_count = 0;
- while ( (req = (QueuedRequest*)mRequestHash.pop_element()) )
- {
- if (req->getStatus() == STATUS_QUEUED || req->getStatus() == STATUS_INPROGRESS)
- {
- ++active_count;
- req->setStatus(STATUS_ABORTED); // avoid assert in deleteRequest
- }
- req->deleteRequest();
- }
- if (active_count)
- {
- LL_WARNS() << "~LLQueuedThread() called with active requests: " << active_count << LL_ENDL;
- }
-
- mRequestQueue.close();
-}
-
-//----------------------------------------------------------------------------
-
-// MAIN THREAD
-// virtual
-size_t LLQueuedThread::update(F32 max_time_ms)
-{
- LL_PROFILE_ZONE_SCOPED;
- if (!mStarted)
- {
- if (!mThreaded)
- {
- startThread();
- mStarted = true;
- }
- }
- return updateQueue(max_time_ms);
-}
-
-size_t LLQueuedThread::updateQueue(F32 max_time_ms)
-{
- LL_PROFILE_ZONE_SCOPED;
- // Frame Update
- if (mThreaded)
- {
- // schedule a call to threadedUpdate for every call to updateQueue
- if (!isQuitting())
- {
- mRequestQueue.post([=]()
- {
- LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update");
- mIdleThread = false;
- threadedUpdate();
- mIdleThread = true;
- }
- );
- }
-
- if(getPending() > 0)
- {
- unpause();
- }
- }
- else
- {
- mRequestQueue.runFor(std::chrono::microseconds((int) (max_time_ms*1000.f)));
- threadedUpdate();
- }
- return getPending();
-}
-
-void LLQueuedThread::incQueue()
-{
- // Something has been added to the queue
- if (!isPaused())
- {
- if (mThreaded)
- {
- wake(); // Wake the thread up if necessary.
- }
- }
-}
-
-//virtual
-// May be called from any thread
-size_t LLQueuedThread::getPending()
-{
- return mRequestQueue.size();
-}
-
-// MAIN thread
-void LLQueuedThread::waitOnPending()
-{
- while(1)
- {
- update(0);
-
- if (mIdleThread)
- {
- break;
- }
- if (mThreaded)
- {
- yield();
- }
- }
- return;
-}
-
-// MAIN thread
-void LLQueuedThread::printQueueStats()
-{
- U32 size = mRequestQueue.size();
- if (size > 0)
- {
- LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL;
- }
- else
- {
- LL_INFOS() << "Queued Thread Idle" << LL_ENDL;
- }
-}
-
-// MAIN thread
-LLQueuedThread::handle_t LLQueuedThread::generateHandle()
-{
- U32 res = ++mNextHandle;
- return res;
-}
-
-// MAIN thread
-bool LLQueuedThread::addRequest(QueuedRequest* req)
-{
- LL_PROFILE_ZONE_SCOPED;
- if (mStatus == QUITTING)
- {
- return false;
- }
-
- lockData();
- req->setStatus(STATUS_QUEUED);
- mRequestHash.insert(req);
-#if _DEBUG
-// LL_INFOS() << llformat("LLQueuedThread::Added req [%08d]",handle) << LL_ENDL;
-#endif
- unlockData();
-
- llassert(!mDataLock->isSelfLocked());
- mRequestQueue.post([this, req]() { processRequest(req); });
-
- return true;
-}
-
-// MAIN thread
-bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete)
-{
- LL_PROFILE_ZONE_SCOPED;
- llassert (handle != nullHandle());
- bool res = false;
- bool waspaused = isPaused();
- bool done = false;
- while(!done)
- {
- update(0); // unpauses
- lockData();
- QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
- if (!req)
- {
- done = true; // request does not exist
- }
- else if (req->getStatus() == STATUS_COMPLETE)
- {
- res = true;
- if (auto_complete)
- {
- mRequestHash.erase(handle);
- req->deleteRequest();
-// check();
- }
- done = true;
- }
- unlockData();
-
- if (!done && mThreaded)
- {
- yield();
- }
- }
- if (waspaused)
- {
- pause();
- }
- return res;
-}
-
-// MAIN thread
-LLQueuedThread::QueuedRequest* LLQueuedThread::getRequest(handle_t handle)
-{
- if (handle == nullHandle())
- {
- return 0;
- }
- lockData();
- QueuedRequest* res = (QueuedRequest*)mRequestHash.find(handle);
- unlockData();
- return res;
-}
-
-LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle)
-{
- status_t res = STATUS_EXPIRED;
- lockData();
- QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
- if (req)
- {
- res = req->getStatus();
- }
- unlockData();
- return res;
-}
-
-void LLQueuedThread::abortRequest(handle_t handle, bool autocomplete)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
- lockData();
- QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
- if (req)
- {
- req->setFlags(FLAG_ABORT | (autocomplete ? FLAG_AUTO_COMPLETE : 0));
- }
- unlockData();
-}
-
-// MAIN thread
-void LLQueuedThread::setFlags(handle_t handle, U32 flags)
-{
- lockData();
- QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
- if (req)
- {
- req->setFlags(flags);
- }
- unlockData();
-}
-
-bool LLQueuedThread::completeRequest(handle_t handle)
-{
- LL_PROFILE_ZONE_SCOPED;
- bool res = false;
- lockData();
- QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle);
- if (req)
- {
- llassert_always(req->getStatus() != STATUS_QUEUED);
- llassert_always(req->getStatus() != STATUS_INPROGRESS);
-#if _DEBUG
-// LL_INFOS() << llformat("LLQueuedThread::Completed req [%08d]",handle) << LL_ENDL;
-#endif
- mRequestHash.erase(handle);
- req->deleteRequest();
-// check();
- res = true;
- }
- unlockData();
- return res;
-}
-
-bool LLQueuedThread::check()
-{
-#if 0 // not a reliable check once mNextHandle wraps, just for quick and dirty debugging
- for (int i=0; i<REQUEST_HASH_SIZE; i++)
- {
- LLSimpleHashEntry<handle_t>* entry = mRequestHash.get_element_at_index(i);
- while (entry)
- {
- if (entry->getHashKey() > mNextHandle)
- {
- LL_ERRS() << "Hash Error" << LL_ENDL;
- return false;
- }
- entry = entry->getNextEntry();
- }
- }
-#endif
- return true;
-}
-
-//============================================================================
-// Runs on its OWN thread
-
-void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD;
-
- mIdleThread = false;
- //threadedUpdate();
-
- // Get next request from pool
- lockData();
-
- if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING))
- {
- LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - abort");
- req->setStatus(STATUS_ABORTED);
- req->finishRequest(false);
- if (req->getFlags() & FLAG_AUTO_COMPLETE)
- {
- mRequestHash.erase(req);
- req->deleteRequest();
-// check();
- }
- unlockData();
- }
- else
- {
- llassert_always(req->getStatus() == STATUS_QUEUED);
-
- if (req)
- {
- req->setStatus(STATUS_INPROGRESS);
- }
- unlockData();
-
- // This is the only place we will call req->setStatus() after
- // it has initially been seet to STATUS_QUEUED, so it is
- // safe to access req.
- if (req)
- {
- // process request
- bool complete = req->processRequest();
-
- if (complete)
- {
- LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - complete");
- lockData();
- req->setStatus(STATUS_COMPLETE);
- req->finishRequest(true);
- if (req->getFlags() & FLAG_AUTO_COMPLETE)
- {
- mRequestHash.erase(req);
- req->deleteRequest();
- // check();
- }
- unlockData();
- }
- else
- {
- LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - retry");
- //put back on queue and try again in 0.1ms
- lockData();
- req->setStatus(STATUS_QUEUED);
-
- unlockData();
-
- llassert(!mDataLock->isSelfLocked());
-
-#if 0
- // try again on next frame
- // NOTE: tried using "post" with a time in the future, but this
- // would invariably cause this thread to wait for a long time (10+ ms)
- // while work is pending
- bool ret = LL::WorkQueue::postMaybe(
- mMainQueue,
- [=]()
- {
- LL_PROFILE_ZONE_NAMED("processRequest - retry");
- mRequestQueue.post([=]()
- {
- LL_PROFILE_ZONE_NAMED("processRequest - retry"); // <-- not redundant, track retry on both queues
- processRequest(req);
- });
- });
- llassert(ret);
-#else
- using namespace std::chrono_literals;
- auto retry_time = LL::WorkQueue::TimePoint::clock::now() + 16ms;
- mRequestQueue.post([=]
- {
- LL_PROFILE_ZONE_NAMED("processRequest - retry");
- if (LL::WorkQueue::TimePoint::clock::now() < retry_time)
- {
- auto sleep_time = std::chrono::duration_cast<std::chrono::milliseconds>(retry_time - LL::WorkQueue::TimePoint::clock::now());
-
- if (sleep_time.count() > 0)
- {
- ms_sleep(sleep_time.count());
- }
- }
- processRequest(req);
- });
-#endif
-
- }
- }
- }
-
- mIdleThread = true;
-}
-
-// virtual
-bool LLQueuedThread::runCondition()
-{
- // mRunCondition must be locked here
- if (mRequestQueue.size() == 0 && mIdleThread)
- return false;
- else
- return true;
-}
-
-// virtual
-void LLQueuedThread::run()
-{
- // call checPause() immediately so we don't try to do anything before the class is fully constructed
- checkPause();
- startThread();
- mStarted = true;
-
-
- /*while (1)
- {
- LL_PROFILE_ZONE_SCOPED;
- // this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state.
- checkPause();
-
- mIdleThread = false;
-
- threadedUpdate();
-
- auto pending_work = processNextRequest();
-
- if (pending_work == 0)
- {
- //LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep");
- mIdleThread = true;
- //ms_sleep(1);
- }
- //LLThread::yield(); // thread should yield after each request
- }*/
- mRequestQueue.runUntilClose();
-
- endThread();
- LL_INFOS() << "LLQueuedThread " << mName << " EXITING." << LL_ENDL;
-
-
-}
-
-// virtual
-void LLQueuedThread::startThread()
-{
-}
-
-// virtual
-void LLQueuedThread::endThread()
-{
-}
-
-// virtual
-void LLQueuedThread::threadedUpdate()
-{
-}
-
-//============================================================================
-
-LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 flags) :
- LLSimpleHashEntry<LLQueuedThread::handle_t>(handle),
- mStatus(STATUS_UNKNOWN),
- mFlags(flags)
-{
-}
-
-LLQueuedThread::QueuedRequest::~QueuedRequest()
-{
- llassert_always(mStatus == STATUS_DELETE);
-}
-
-//virtual
-void LLQueuedThread::QueuedRequest::finishRequest(bool completed)
-{
-}
-
-//virtual
-void LLQueuedThread::QueuedRequest::deleteRequest()
-{
- llassert_always(mStatus != STATUS_INPROGRESS);
- setStatus(STATUS_DELETE);
- delete this;
-}
+/** + * @file llqueuedthread.cpp + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llqueuedthread.h" + +#include <chrono> + +#include "llstl.h" +#include "lltimer.h" // ms_sleep() +#include "llmutex.h" + +//============================================================================ + +// MAIN THREAD +LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) : + LLThread(name), + mIdleThread(true), + mNextHandle(0), + mStarted(false), + mThreaded(threaded), + mRequestQueue(name, 1024 * 1024) +{ + llassert(threaded); // not threaded implementation is deprecated + mMainQueue = LL::WorkQueue::getInstance("mainloop"); + + if (mThreaded) + { + if(should_pause) + { + pause() ; //call this before start the thread. + } + + start(); + } +} + +// MAIN THREAD +LLQueuedThread::~LLQueuedThread() +{ + if (!mThreaded) + { + endThread(); + } + shutdown(); + // ~LLThread() will be called here +} + +void LLQueuedThread::shutdown() +{ + setQuitting(); + + unpause(); // MAIN THREAD + if (mThreaded) + { + if (mRequestQueue.size() == 0) + { + mRequestQueue.close(); + } + + S32 timeout = 100; + for ( ; timeout>0; timeout--) + { + if (isStopped()) + { + break; + } + ms_sleep(100); + LLThread::yield(); + } + if (timeout == 0) + { + LL_WARNS() << "~LLQueuedThread (" << mName << ") timed out!" << LL_ENDL; + } + } + else + { + mStatus = STOPPED; + } + + QueuedRequest* req; + S32 active_count = 0; + while ( (req = (QueuedRequest*)mRequestHash.pop_element()) ) + { + if (req->getStatus() == STATUS_QUEUED || req->getStatus() == STATUS_INPROGRESS) + { + ++active_count; + req->setStatus(STATUS_ABORTED); // avoid assert in deleteRequest + } + req->deleteRequest(); + } + if (active_count) + { + LL_WARNS() << "~LLQueuedThread() called with active requests: " << active_count << LL_ENDL; + } + + mRequestQueue.close(); +} + +//---------------------------------------------------------------------------- + +// MAIN THREAD +// virtual +size_t LLQueuedThread::update(F32 max_time_ms) +{ + LL_PROFILE_ZONE_SCOPED; + if (!mStarted) + { + if (!mThreaded) + { + startThread(); + mStarted = true; + } + } + return updateQueue(max_time_ms); +} + +size_t LLQueuedThread::updateQueue(F32 max_time_ms) +{ + LL_PROFILE_ZONE_SCOPED; + // Frame Update + if (mThreaded) + { + // schedule a call to threadedUpdate for every call to updateQueue + if (!isQuitting()) + { + mRequestQueue.post([=]() + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); + mIdleThread = false; + threadedUpdate(); + mIdleThread = true; + } + ); + } + + if(getPending() > 0) + { + unpause(); + } + } + else + { + mRequestQueue.runFor(std::chrono::microseconds((int) (max_time_ms*1000.f))); + threadedUpdate(); + } + return getPending(); +} + +void LLQueuedThread::incQueue() +{ + // Something has been added to the queue + if (!isPaused()) + { + if (mThreaded) + { + wake(); // Wake the thread up if necessary. + } + } +} + +//virtual +// May be called from any thread +size_t LLQueuedThread::getPending() +{ + return mRequestQueue.size(); +} + +// MAIN thread +void LLQueuedThread::waitOnPending() +{ + while(1) + { + update(0); + + if (mIdleThread) + { + break; + } + if (mThreaded) + { + yield(); + } + } + return; +} + +// MAIN thread +void LLQueuedThread::printQueueStats() +{ + U32 size = mRequestQueue.size(); + if (size > 0) + { + LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL; + } + else + { + LL_INFOS() << "Queued Thread Idle" << LL_ENDL; + } +} + +// MAIN thread +LLQueuedThread::handle_t LLQueuedThread::generateHandle() +{ + U32 res = ++mNextHandle; + return res; +} + +// MAIN thread +bool LLQueuedThread::addRequest(QueuedRequest* req) +{ + LL_PROFILE_ZONE_SCOPED; + if (mStatus == QUITTING) + { + return false; + } + + lockData(); + req->setStatus(STATUS_QUEUED); + mRequestHash.insert(req); +#if _DEBUG +// LL_INFOS() << llformat("LLQueuedThread::Added req [%08d]",handle) << LL_ENDL; +#endif + unlockData(); + + llassert(!mDataLock->isSelfLocked()); + mRequestQueue.post([this, req]() { processRequest(req); }); + + return true; +} + +// MAIN thread +bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete) +{ + LL_PROFILE_ZONE_SCOPED; + llassert (handle != nullHandle()); + bool res = false; + bool waspaused = isPaused(); + bool done = false; + while(!done) + { + update(0); // unpauses + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (!req) + { + done = true; // request does not exist + } + else if (req->getStatus() == STATUS_COMPLETE) + { + res = true; + if (auto_complete) + { + mRequestHash.erase(handle); + req->deleteRequest(); +// check(); + } + done = true; + } + unlockData(); + + if (!done && mThreaded) + { + yield(); + } + } + if (waspaused) + { + pause(); + } + return res; +} + +// MAIN thread +LLQueuedThread::QueuedRequest* LLQueuedThread::getRequest(handle_t handle) +{ + if (handle == nullHandle()) + { + return 0; + } + lockData(); + QueuedRequest* res = (QueuedRequest*)mRequestHash.find(handle); + unlockData(); + return res; +} + +LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle) +{ + status_t res = STATUS_EXPIRED; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + res = req->getStatus(); + } + unlockData(); + return res; +} + +void LLQueuedThread::abortRequest(handle_t handle, bool autocomplete) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + req->setFlags(FLAG_ABORT | (autocomplete ? FLAG_AUTO_COMPLETE : 0)); + } + unlockData(); +} + +// MAIN thread +void LLQueuedThread::setFlags(handle_t handle, U32 flags) +{ + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + req->setFlags(flags); + } + unlockData(); +} + +bool LLQueuedThread::completeRequest(handle_t handle) +{ + LL_PROFILE_ZONE_SCOPED; + bool res = false; + lockData(); + QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); + if (req) + { + llassert_always(req->getStatus() != STATUS_QUEUED); + llassert_always(req->getStatus() != STATUS_INPROGRESS); +#if _DEBUG +// LL_INFOS() << llformat("LLQueuedThread::Completed req [%08d]",handle) << LL_ENDL; +#endif + mRequestHash.erase(handle); + req->deleteRequest(); +// check(); + res = true; + } + unlockData(); + return res; +} + +bool LLQueuedThread::check() +{ +#if 0 // not a reliable check once mNextHandle wraps, just for quick and dirty debugging + for (int i=0; i<REQUEST_HASH_SIZE; i++) + { + LLSimpleHashEntry<handle_t>* entry = mRequestHash.get_element_at_index(i); + while (entry) + { + if (entry->getHashKey() > mNextHandle) + { + LL_ERRS() << "Hash Error" << LL_ENDL; + return false; + } + entry = entry->getNextEntry(); + } + } +#endif + return true; +} + +//============================================================================ +// Runs on its OWN thread + +void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; + + mIdleThread = false; + //threadedUpdate(); + + // Get next request from pool + lockData(); + + if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING)) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - abort"); + req->setStatus(STATUS_ABORTED); + req->finishRequest(false); + if (req->getFlags() & FLAG_AUTO_COMPLETE) + { + mRequestHash.erase(req); + req->deleteRequest(); +// check(); + } + unlockData(); + } + else + { + llassert_always(req->getStatus() == STATUS_QUEUED); + + if (req) + { + req->setStatus(STATUS_INPROGRESS); + } + unlockData(); + + // This is the only place we will call req->setStatus() after + // it has initially been seet to STATUS_QUEUED, so it is + // safe to access req. + if (req) + { + // process request + bool complete = req->processRequest(); + + if (complete) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - complete"); + lockData(); + req->setStatus(STATUS_COMPLETE); + req->finishRequest(true); + if (req->getFlags() & FLAG_AUTO_COMPLETE) + { + mRequestHash.erase(req); + req->deleteRequest(); + // check(); + } + unlockData(); + } + else + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - retry"); + //put back on queue and try again in 0.1ms + lockData(); + req->setStatus(STATUS_QUEUED); + + unlockData(); + + llassert(!mDataLock->isSelfLocked()); + +#if 0 + // try again on next frame + // NOTE: tried using "post" with a time in the future, but this + // would invariably cause this thread to wait for a long time (10+ ms) + // while work is pending + bool ret = LL::WorkQueue::postMaybe( + mMainQueue, + [=]() + { + LL_PROFILE_ZONE_NAMED("processRequest - retry"); + mRequestQueue.post([=]() + { + LL_PROFILE_ZONE_NAMED("processRequest - retry"); // <-- not redundant, track retry on both queues + processRequest(req); + }); + }); + llassert(ret); +#else + using namespace std::chrono_literals; + auto retry_time = LL::WorkQueue::TimePoint::clock::now() + 16ms; + mRequestQueue.post([=] + { + LL_PROFILE_ZONE_NAMED("processRequest - retry"); + if (LL::WorkQueue::TimePoint::clock::now() < retry_time) + { + auto sleep_time = std::chrono::duration_cast<std::chrono::milliseconds>(retry_time - LL::WorkQueue::TimePoint::clock::now()); + + if (sleep_time.count() > 0) + { + ms_sleep(sleep_time.count()); + } + } + processRequest(req); + }); +#endif + + } + } + } + + mIdleThread = true; +} + +// virtual +bool LLQueuedThread::runCondition() +{ + // mRunCondition must be locked here + if (mRequestQueue.size() == 0 && mIdleThread) + return false; + else + return true; +} + +// virtual +void LLQueuedThread::run() +{ + // call checPause() immediately so we don't try to do anything before the class is fully constructed + checkPause(); + startThread(); + mStarted = true; + + + /*while (1) + { + LL_PROFILE_ZONE_SCOPED; + // this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state. + checkPause(); + + mIdleThread = false; + + threadedUpdate(); + + auto pending_work = processNextRequest(); + + if (pending_work == 0) + { + //LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep"); + mIdleThread = true; + //ms_sleep(1); + } + //LLThread::yield(); // thread should yield after each request + }*/ + mRequestQueue.runUntilClose(); + + endThread(); + LL_INFOS() << "LLQueuedThread " << mName << " EXITING." << LL_ENDL; + + +} + +// virtual +void LLQueuedThread::startThread() +{ +} + +// virtual +void LLQueuedThread::endThread() +{ +} + +// virtual +void LLQueuedThread::threadedUpdate() +{ +} + +//============================================================================ + +LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 flags) : + LLSimpleHashEntry<LLQueuedThread::handle_t>(handle), + mStatus(STATUS_UNKNOWN), + mFlags(flags) +{ +} + +LLQueuedThread::QueuedRequest::~QueuedRequest() +{ + llassert_always(mStatus == STATUS_DELETE); +} + +//virtual +void LLQueuedThread::QueuedRequest::finishRequest(bool completed) +{ +} + +//virtual +void LLQueuedThread::QueuedRequest::deleteRequest() +{ + llassert_always(mStatus != STATUS_INPROGRESS); + setStatus(STATUS_DELETE); + delete this; +} diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index 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 <queue>
-#include <string>
-#include <map>
-#include <set>
-
-#include "llatomic.h"
-
-#include "llthread.h"
-#include "llsimplehash.h"
-#include "workqueue.h"
-
-//============================================================================
-// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small
-// It is assumed that LLQueuedThreads are rarely created/destroyed.
-
-class LL_COMMON_API LLQueuedThread : public LLThread
-{
- //------------------------------------------------------------------------
-public:
- enum status_t {
- STATUS_EXPIRED = -1,
- STATUS_UNKNOWN = 0,
- STATUS_QUEUED = 1,
- STATUS_INPROGRESS = 2,
- STATUS_COMPLETE = 3,
- STATUS_ABORTED = 4,
- STATUS_DELETE = 5
- };
- enum flags_t {
- FLAG_AUTO_COMPLETE = 1,
- FLAG_AUTO_DELETE = 2, // child-class dependent
- FLAG_ABORT = 4
- };
-
- typedef U32 handle_t;
-
- //------------------------------------------------------------------------
-public:
-
- class LL_COMMON_API QueuedRequest : public LLSimpleHashEntry<handle_t>
- {
- friend class LLQueuedThread;
-
- protected:
- virtual ~QueuedRequest(); // use deleteRequest()
-
- public:
- QueuedRequest(handle_t handle, U32 flags = 0);
-
- status_t getStatus()
- {
- return mStatus;
- }
- U32 getFlags() const
- {
- return mFlags;
- }
-
- protected:
- status_t setStatus(status_t newstatus)
- {
- status_t oldstatus = mStatus;
- mStatus = newstatus;
- return oldstatus;
- }
- void setFlags(U32 flags)
- {
- // NOTE: flags are |'d
- mFlags |= flags;
- }
-
- virtual bool processRequest() = 0; // Return true when request has completed
- virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted
- virtual void deleteRequest(); // Only method to delete a request
-
- protected:
- LLAtomicBase<status_t> mStatus;
- U32 mFlags;
- };
-
- //------------------------------------------------------------------------
-
-public:
- static handle_t nullHandle() { return handle_t(0); }
-
-public:
- LLQueuedThread(const std::string& name, bool threaded = true, bool should_pause = false);
- virtual ~LLQueuedThread();
- virtual void shutdown();
-
-private:
- // No copy constructor or copy assignment
- LLQueuedThread(const LLQueuedThread&);
- LLQueuedThread& operator=(const LLQueuedThread&);
-
- virtual bool runCondition(void);
- virtual void run(void);
- virtual void startThread(void);
- virtual void endThread(void);
- virtual void threadedUpdate(void);
-
-protected:
- handle_t generateHandle();
- bool addRequest(QueuedRequest* req);
- void processRequest(QueuedRequest* req);
- void incQueue();
-
-public:
- bool waitForResult(handle_t handle, bool auto_complete = true);
-
- virtual size_t update(F32 max_time_ms);
- size_t updateQueue(F32 max_time_ms);
-
- void waitOnPending();
- void printQueueStats();
-
- virtual size_t getPending();
- bool getThreaded() { return mThreaded; }
-
- // Request accessors
- status_t getRequestStatus(handle_t handle);
- void abortRequest(handle_t handle, bool autocomplete);
- void setFlags(handle_t handle, U32 flags);
- bool completeRequest(handle_t handle);
- // This is public for support classes like LLWorkerThread,
- // but generally the methods above should be used.
- QueuedRequest* getRequest(handle_t handle);
-
- // debug (see source)
- bool check();
-
-protected:
- bool mThreaded; // if false, run on main thread and do updates during update()
- bool mStarted; // required when mThreaded is false to call startThread() from update()
- LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle
-
- //typedef std::set<QueuedRequest*, queued_request_less> request_queue_t;
- //request_queue_t mRequestQueue;
- LL::WorkQueue mRequestQueue;
- LL::WorkQueue::weak_t mMainQueue;
-
- enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2
- typedef LLSimpleHash<handle_t, REQUEST_HASH_SIZE> request_hash_t;
- request_hash_t mRequestHash;
-
- handle_t mNextHandle;
-};
-
-#endif // LL_LLQUEUEDTHREAD_H
+/** + * @file llqueuedthread.h + * @brief + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLQUEUEDTHREAD_H +#define LL_LLQUEUEDTHREAD_H + +#include <queue> +#include <string> +#include <map> +#include <set> + +#include "llatomic.h" + +#include "llthread.h" +#include "llsimplehash.h" +#include "workqueue.h" + +//============================================================================ +// Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small +// It is assumed that LLQueuedThreads are rarely created/destroyed. + +class LL_COMMON_API LLQueuedThread : public LLThread +{ + //------------------------------------------------------------------------ +public: + enum status_t { + STATUS_EXPIRED = -1, + STATUS_UNKNOWN = 0, + STATUS_QUEUED = 1, + STATUS_INPROGRESS = 2, + STATUS_COMPLETE = 3, + STATUS_ABORTED = 4, + STATUS_DELETE = 5 + }; + enum flags_t { + FLAG_AUTO_COMPLETE = 1, + FLAG_AUTO_DELETE = 2, // child-class dependent + FLAG_ABORT = 4 + }; + + typedef U32 handle_t; + + //------------------------------------------------------------------------ +public: + + class LL_COMMON_API QueuedRequest : public LLSimpleHashEntry<handle_t> + { + friend class LLQueuedThread; + + protected: + virtual ~QueuedRequest(); // use deleteRequest() + + public: + QueuedRequest(handle_t handle, U32 flags = 0); + + status_t getStatus() + { + return mStatus; + } + U32 getFlags() const + { + return mFlags; + } + + protected: + status_t setStatus(status_t newstatus) + { + status_t oldstatus = mStatus; + mStatus = newstatus; + return oldstatus; + } + void setFlags(U32 flags) + { + // NOTE: flags are |'d + mFlags |= flags; + } + + virtual bool processRequest() = 0; // Return true when request has completed + virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted + virtual void deleteRequest(); // Only method to delete a request + + protected: + LLAtomicBase<status_t> mStatus; + U32 mFlags; + }; + + //------------------------------------------------------------------------ + +public: + static handle_t nullHandle() { return handle_t(0); } + +public: + LLQueuedThread(const std::string& name, bool threaded = true, bool should_pause = false); + virtual ~LLQueuedThread(); + virtual void shutdown(); + +private: + // No copy constructor or copy assignment + LLQueuedThread(const LLQueuedThread&); + LLQueuedThread& operator=(const LLQueuedThread&); + + virtual bool runCondition(void); + virtual void run(void); + virtual void startThread(void); + virtual void endThread(void); + virtual void threadedUpdate(void); + +protected: + handle_t generateHandle(); + bool addRequest(QueuedRequest* req); + void processRequest(QueuedRequest* req); + void incQueue(); + +public: + bool waitForResult(handle_t handle, bool auto_complete = true); + + virtual size_t update(F32 max_time_ms); + size_t updateQueue(F32 max_time_ms); + + void waitOnPending(); + void printQueueStats(); + + virtual size_t getPending(); + bool getThreaded() { return mThreaded; } + + // Request accessors + status_t getRequestStatus(handle_t handle); + void abortRequest(handle_t handle, bool autocomplete); + void setFlags(handle_t handle, U32 flags); + bool completeRequest(handle_t handle); + // This is public for support classes like LLWorkerThread, + // but generally the methods above should be used. + QueuedRequest* getRequest(handle_t handle); + + // debug (see source) + bool check(); + +protected: + bool mThreaded; // if false, run on main thread and do updates during update() + bool mStarted; // required when mThreaded is false to call startThread() from update() + LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle + + //typedef std::set<QueuedRequest*, queued_request_less> request_queue_t; + //request_queue_t mRequestQueue; + LL::WorkQueue mRequestQueue; + LL::WorkQueue::weak_t mMainQueue; + + enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2 + typedef LLSimpleHash<handle_t, REQUEST_HASH_SIZE> request_hash_t; + request_hash_t mRequestHash; + + handle_t mNextHandle; +}; + +#endif // LL_LLQUEUEDTHREAD_H diff --git a/indra/llcommon/llregistry.h b/indra/llcommon/llregistry.h index 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 <list>
-
-#include "llsingleton.h"
-#include "llstl.h"
-
-template <typename T>
-struct LLRegistryDefaultComparator
-{
- bool operator()(const T& lhs, const T& rhs) const
- {
- using std::less;
- return less<T>()(lhs, rhs);
- }
-};
-
-template <typename KEY, typename VALUE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> >
-class LLRegistry
-{
-public:
- typedef LLRegistry<KEY, VALUE, COMPARATOR> registry_t;
- typedef const KEY& ref_const_key_t;
- typedef const VALUE& ref_const_value_t;
- typedef const VALUE* ptr_const_value_t;
- typedef VALUE* ptr_value_t;
-
- class Registrar
- {
- friend class LLRegistry<KEY, VALUE, COMPARATOR>;
- public:
- typedef std::map<KEY, VALUE, COMPARATOR> registry_map_t;
-
- bool add(ref_const_key_t key, ref_const_value_t value)
- {
- if (!mMap.insert(std::make_pair(key, value)).second)
- {
- LL_WARNS() << "Tried to register " << key << " but it was already registered!" << LL_ENDL;
- return false;
- }
- return true;
- }
-
- void remove(ref_const_key_t key)
- {
- mMap.erase(key);
- }
-
- void replace(ref_const_key_t key, ref_const_value_t value)
- {
- mMap[key] = value;
- }
-
- typename registry_map_t::const_iterator beginItems() const
- {
- return mMap.begin();
- }
-
- typename registry_map_t::const_iterator endItems() const
- {
- return mMap.end();
- }
-
- protected:
- ptr_value_t getValue(ref_const_key_t key)
- {
- typename registry_map_t::iterator found_it = mMap.find(key);
- if (found_it != mMap.end())
- {
- return &(found_it->second);
- }
- return NULL;
- }
-
- ptr_const_value_t getValue(ref_const_key_t key) const
- {
- typename registry_map_t::const_iterator found_it = mMap.find(key);
- if (found_it != mMap.end())
- {
- return &(found_it->second);
- }
- return NULL;
- }
-
- // if the registry is used to store pointers, and null values are valid entries
- // then use this function to check the existence of an entry
- bool exists(ref_const_key_t key) const
- {
- return mMap.find(key) != mMap.end();
- }
-
- bool empty() const
- {
- return mMap.empty();
- }
-
- protected:
- // use currentRegistrar() or defaultRegistrar()
- Registrar() {}
- ~Registrar() {}
-
- private:
- registry_map_t mMap;
- };
-
- typedef typename std::list<Registrar*> scope_list_t;
- typedef typename std::list<Registrar*>::iterator scope_list_iterator_t;
- typedef typename std::list<Registrar*>::const_iterator scope_list_const_iterator_t;
-
- LLRegistry()
- {}
-
- ~LLRegistry() {}
-
- ptr_value_t getValue(ref_const_key_t key)
- {
- for(Registrar* scope : mActiveScopes)
- {
- ptr_value_t valuep = scope->getValue(key);
- if (valuep != NULL) return valuep;
- }
- return mDefaultRegistrar.getValue(key);
- }
-
- ptr_const_value_t getValue(ref_const_key_t key) const
- {
- for(const Registrar* scope : mActiveScopes)
- {
- ptr_const_value_t valuep = scope->getValue(key);
- if (valuep != NULL) return valuep;
- }
- return mDefaultRegistrar.getValue(key);
- }
-
- bool exists(ref_const_key_t key) const
- {
- for(const Registrar* scope : mActiveScopes)
- {
- if (scope->exists(key)) return true;
- }
-
- return mDefaultRegistrar.exists(key);
- }
-
- bool empty() const
- {
- for(const Registrar* scope : mActiveScopes)
- {
- if (!scope->empty()) return false;
- }
-
- return mDefaultRegistrar.empty();
- }
-
-
- Registrar& defaultRegistrar()
- {
- return mDefaultRegistrar;
- }
-
- const Registrar& defaultRegistrar() const
- {
- return mDefaultRegistrar;
- }
-
-
- Registrar& currentRegistrar()
- {
- if (!mActiveScopes.empty())
- {
- return *mActiveScopes.front();
- }
-
- return mDefaultRegistrar;
- }
-
- const Registrar& currentRegistrar() const
- {
- if (!mActiveScopes.empty())
- {
- return *mActiveScopes.front();
- }
-
- return mDefaultRegistrar;
- }
-
-
-protected:
- void addScope(Registrar* scope)
- {
- // newer scopes go up front
- mActiveScopes.insert(mActiveScopes.begin(), scope);
- }
-
- void removeScope(Registrar* scope)
- {
- // O(N) but should be near the beggining and N should be small and this is safer than storing iterators
- scope_list_iterator_t iter = std::find(mActiveScopes.begin(), mActiveScopes.end(), scope);
- if (iter != mActiveScopes.end())
- {
- mActiveScopes.erase(iter);
- }
- }
-
-private:
- scope_list_t mActiveScopes;
- Registrar mDefaultRegistrar;
-};
-
-template <typename KEY, typename VALUE, typename DERIVED_TYPE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> >
-class LLRegistrySingleton
- : public LLRegistry<KEY, VALUE, COMPARATOR>,
- public LLSingleton<DERIVED_TYPE>
-{
- // This LLRegistrySingleton doesn't use LLSINGLETON(LLRegistrySingleton)
- // because the concrete class is actually DERIVED_TYPE, not
- // LLRegistrySingleton. So each concrete subclass needs
- // LLSINGLETON(whatever) -- not this intermediate base class.
-public:
- typedef LLRegistry<KEY, VALUE, COMPARATOR> registry_t;
- typedef const KEY& ref_const_key_t;
- typedef const VALUE& ref_const_value_t;
- typedef VALUE* ptr_value_t;
- typedef const VALUE* ptr_const_value_t;
- typedef LLSingleton<DERIVED_TYPE> singleton_t;
-
- class ScopedRegistrar : public registry_t::Registrar
- {
- public:
- ScopedRegistrar(bool push_scope = true)
- {
- if (push_scope)
- {
- pushScope();
- }
- }
-
- ~ScopedRegistrar()
- {
- if (singleton_t::instanceExists())
- {
- popScope();
- }
- }
-
- void pushScope()
- {
- singleton_t::instance().addScope(this);
- }
-
- void popScope()
- {
- singleton_t::instance().removeScope(this);
- }
-
- ptr_value_t getValueFromScope(ref_const_key_t key)
- {
- return getValue(key);
- }
-
- ptr_const_value_t getValueFromScope(ref_const_key_t key) const
- {
- return getValue(key);
- }
-
- private:
- typename std::list<typename registry_t::Registrar*>::iterator mListIt;
- };
-
- class StaticRegistrar : public registry_t::Registrar
- {
- public:
- virtual ~StaticRegistrar() {}
- StaticRegistrar(ref_const_key_t key, ref_const_value_t value)
- {
- if (singleton_t::instance().exists(key))
- {
- LL_ERRS() << "Duplicate registry entry under key \"" << key << "\"" << LL_ENDL;
- }
- singleton_t::instance().mStaticScope->add(key, value);
- }
- };
-
- // convenience functions
- typedef typename LLRegistry<KEY, VALUE, COMPARATOR>::Registrar& ref_registrar_t;
- static ref_registrar_t currentRegistrar()
- {
- return singleton_t::instance().registry_t::currentRegistrar();
- }
-
- static ref_registrar_t defaultRegistrar()
- {
- return singleton_t::instance().registry_t::defaultRegistrar();
- }
-
- static ptr_value_t getValue(ref_const_key_t key)
- {
- return singleton_t::instance().registry_t::getValue(key);
- }
-
-protected:
- // DERIVED_TYPE needs to derive from LLRegistrySingleton
- LLRegistrySingleton()
- : mStaticScope(NULL)
- {}
-
- virtual void initSingleton()
- {
- mStaticScope = new ScopedRegistrar();
- }
-
- virtual ~LLRegistrySingleton()
- {
- delete mStaticScope;
- }
-
-private:
- ScopedRegistrar* mStaticScope;
-};
-
-// helper macro for doing static registration
-#define GLUED_TOKEN(x, y) x ## y
-#define GLUE_TOKENS(x, y) GLUED_TOKEN(x, y)
-#define LLREGISTER_STATIC(REGISTRY, KEY, VALUE) static REGISTRY::StaticRegistrar GLUE_TOKENS(reg, __LINE__)(KEY, VALUE);
-
-#endif
+/** + * @file llregistry.h + * @brief template classes for registering name, value pairs in nested scopes, statically, etc. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLREGISTRY_H +#define LL_LLREGISTRY_H + +#include <list> + +#include "llsingleton.h" +#include "llstl.h" + +template <typename T> +struct LLRegistryDefaultComparator +{ + bool operator()(const T& lhs, const T& rhs) const + { + using std::less; + return less<T>()(lhs, rhs); + } +}; + +template <typename KEY, typename VALUE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> > +class LLRegistry +{ +public: + typedef LLRegistry<KEY, VALUE, COMPARATOR> registry_t; + typedef const KEY& ref_const_key_t; + typedef const VALUE& ref_const_value_t; + typedef const VALUE* ptr_const_value_t; + typedef VALUE* ptr_value_t; + + class Registrar + { + friend class LLRegistry<KEY, VALUE, COMPARATOR>; + public: + typedef std::map<KEY, VALUE, COMPARATOR> registry_map_t; + + bool add(ref_const_key_t key, ref_const_value_t value) + { + if (!mMap.insert(std::make_pair(key, value)).second) + { + LL_WARNS() << "Tried to register " << key << " but it was already registered!" << LL_ENDL; + return false; + } + return true; + } + + void remove(ref_const_key_t key) + { + mMap.erase(key); + } + + void replace(ref_const_key_t key, ref_const_value_t value) + { + mMap[key] = value; + } + + typename registry_map_t::const_iterator beginItems() const + { + return mMap.begin(); + } + + typename registry_map_t::const_iterator endItems() const + { + return mMap.end(); + } + + protected: + ptr_value_t getValue(ref_const_key_t key) + { + typename registry_map_t::iterator found_it = mMap.find(key); + if (found_it != mMap.end()) + { + return &(found_it->second); + } + return NULL; + } + + ptr_const_value_t getValue(ref_const_key_t key) const + { + typename registry_map_t::const_iterator found_it = mMap.find(key); + if (found_it != mMap.end()) + { + return &(found_it->second); + } + return NULL; + } + + // if the registry is used to store pointers, and null values are valid entries + // then use this function to check the existence of an entry + bool exists(ref_const_key_t key) const + { + return mMap.find(key) != mMap.end(); + } + + bool empty() const + { + return mMap.empty(); + } + + protected: + // use currentRegistrar() or defaultRegistrar() + Registrar() {} + ~Registrar() {} + + private: + registry_map_t mMap; + }; + + typedef typename std::list<Registrar*> scope_list_t; + typedef typename std::list<Registrar*>::iterator scope_list_iterator_t; + typedef typename std::list<Registrar*>::const_iterator scope_list_const_iterator_t; + + LLRegistry() + {} + + ~LLRegistry() {} + + ptr_value_t getValue(ref_const_key_t key) + { + for(Registrar* scope : mActiveScopes) + { + ptr_value_t valuep = scope->getValue(key); + if (valuep != NULL) return valuep; + } + return mDefaultRegistrar.getValue(key); + } + + ptr_const_value_t getValue(ref_const_key_t key) const + { + for(const Registrar* scope : mActiveScopes) + { + ptr_const_value_t valuep = scope->getValue(key); + if (valuep != NULL) return valuep; + } + return mDefaultRegistrar.getValue(key); + } + + bool exists(ref_const_key_t key) const + { + for(const Registrar* scope : mActiveScopes) + { + if (scope->exists(key)) return true; + } + + return mDefaultRegistrar.exists(key); + } + + bool empty() const + { + for(const Registrar* scope : mActiveScopes) + { + if (!scope->empty()) return false; + } + + return mDefaultRegistrar.empty(); + } + + + Registrar& defaultRegistrar() + { + return mDefaultRegistrar; + } + + const Registrar& defaultRegistrar() const + { + return mDefaultRegistrar; + } + + + Registrar& currentRegistrar() + { + if (!mActiveScopes.empty()) + { + return *mActiveScopes.front(); + } + + return mDefaultRegistrar; + } + + const Registrar& currentRegistrar() const + { + if (!mActiveScopes.empty()) + { + return *mActiveScopes.front(); + } + + return mDefaultRegistrar; + } + + +protected: + void addScope(Registrar* scope) + { + // newer scopes go up front + mActiveScopes.insert(mActiveScopes.begin(), scope); + } + + void removeScope(Registrar* scope) + { + // O(N) but should be near the beggining and N should be small and this is safer than storing iterators + scope_list_iterator_t iter = std::find(mActiveScopes.begin(), mActiveScopes.end(), scope); + if (iter != mActiveScopes.end()) + { + mActiveScopes.erase(iter); + } + } + +private: + scope_list_t mActiveScopes; + Registrar mDefaultRegistrar; +}; + +template <typename KEY, typename VALUE, typename DERIVED_TYPE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> > +class LLRegistrySingleton + : public LLRegistry<KEY, VALUE, COMPARATOR>, + public LLSingleton<DERIVED_TYPE> +{ + // This LLRegistrySingleton doesn't use LLSINGLETON(LLRegistrySingleton) + // because the concrete class is actually DERIVED_TYPE, not + // LLRegistrySingleton. So each concrete subclass needs + // LLSINGLETON(whatever) -- not this intermediate base class. +public: + typedef LLRegistry<KEY, VALUE, COMPARATOR> registry_t; + typedef const KEY& ref_const_key_t; + typedef const VALUE& ref_const_value_t; + typedef VALUE* ptr_value_t; + typedef const VALUE* ptr_const_value_t; + typedef LLSingleton<DERIVED_TYPE> singleton_t; + + class ScopedRegistrar : public registry_t::Registrar + { + public: + ScopedRegistrar(bool push_scope = true) + { + if (push_scope) + { + pushScope(); + } + } + + ~ScopedRegistrar() + { + if (singleton_t::instanceExists()) + { + popScope(); + } + } + + void pushScope() + { + singleton_t::instance().addScope(this); + } + + void popScope() + { + singleton_t::instance().removeScope(this); + } + + ptr_value_t getValueFromScope(ref_const_key_t key) + { + return getValue(key); + } + + ptr_const_value_t getValueFromScope(ref_const_key_t key) const + { + return getValue(key); + } + + private: + typename std::list<typename registry_t::Registrar*>::iterator mListIt; + }; + + class StaticRegistrar : public registry_t::Registrar + { + public: + virtual ~StaticRegistrar() {} + StaticRegistrar(ref_const_key_t key, ref_const_value_t value) + { + if (singleton_t::instance().exists(key)) + { + LL_ERRS() << "Duplicate registry entry under key \"" << key << "\"" << LL_ENDL; + } + singleton_t::instance().mStaticScope->add(key, value); + } + }; + + // convenience functions + typedef typename LLRegistry<KEY, VALUE, COMPARATOR>::Registrar& ref_registrar_t; + static ref_registrar_t currentRegistrar() + { + return singleton_t::instance().registry_t::currentRegistrar(); + } + + static ref_registrar_t defaultRegistrar() + { + return singleton_t::instance().registry_t::defaultRegistrar(); + } + + static ptr_value_t getValue(ref_const_key_t key) + { + return singleton_t::instance().registry_t::getValue(key); + } + +protected: + // DERIVED_TYPE needs to derive from LLRegistrySingleton + LLRegistrySingleton() + : mStaticScope(NULL) + {} + + virtual void initSingleton() + { + mStaticScope = new ScopedRegistrar(); + } + + virtual ~LLRegistrySingleton() + { + delete mStaticScope; + } + +private: + ScopedRegistrar* mStaticScope; +}; + +// helper macro for doing static registration +#define GLUED_TOKEN(x, y) x ## y +#define GLUE_TOKENS(x, y) GLUED_TOKEN(x, y) +#define LLREGISTER_STATIC(REGISTRY, KEY, VALUE) static REGISTRY::StaticRegistrar GLUE_TOKENS(reg, __LINE__)(KEY, VALUE); + +#endif diff --git a/indra/llcommon/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 <iostream>
-#include "apr_base64.h"
-
-#include <boost/iostreams/device/array.hpp>
-#include <boost/iostreams/stream.hpp>
-
-#ifdef LL_USESYSTEMLIBS
-# include <zlib.h>
-#else
-# include "zlib-ng/zlib.h" // for davep's dirty little zip functions
-#endif
-
-#if !LL_WINDOWS
-#include <netinet/in.h> // htonl & ntohl
-#endif
-
-#include "lldate.h"
-#include "llmemorystream.h"
-#include "llsd.h"
-#include "llstring.h"
-#include "lluri.h"
-
-// File constants
-static const size_t MAX_HDR_LEN = 20;
-static const S32 UNZIP_LLSD_MAX_DEPTH = 96;
-static const char LEGACY_NON_HEADER[] = "<llsd>";
-const std::string LLSD_BINARY_HEADER("LLSD/Binary");
-const std::string LLSD_XML_HEADER("LLSD/XML");
-const std::string LLSD_NOTATION_HEADER("llsd/notation");
-
-//used to deflate a gzipped asset (currently used for navmeshes)
-#define windowBits 15
-#define ENABLE_ZLIB_GZIP 32
-
-// If we published this in llsdserialize.h, we could use it in the
-// implementation of LLSDOStreamer's operator<<().
-template <class Formatter>
-void format_using(const LLSD& data, std::ostream& ostr,
- LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY)
-{
- LLPointer<Formatter> f{ new Formatter };
- f->format(data, ostr, options);
-}
-
-template <class Parser>
-S32 parse_using(std::istream& istr, LLSD& data, size_t max_bytes, S32 max_depth=-1)
-{
- LLPointer<Parser> p{ new Parser };
- return p->parse(istr, data, max_bytes, max_depth);
-}
-
-/**
- * LLSDSerialize
- */
-
-// static
-void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type,
- LLSDFormatter::EFormatterOptions options)
-{
- LLPointer<LLSDFormatter> f = NULL;
-
- switch (type)
- {
- case LLSD_BINARY:
- str << "<? " << LLSD_BINARY_HEADER << " ?>\n";
- f = new LLSDBinaryFormatter;
- break;
-
- case LLSD_XML:
- str << "<? " << LLSD_XML_HEADER << " ?>\n";
- f = new LLSDXMLFormatter;
- break;
-
- case LLSD_NOTATION:
- str << "<? " << LLSD_NOTATION_HEADER << " ?>\n";
- f = new LLSDNotationFormatter;
- break;
-
- default:
- LL_WARNS() << "serialize request for unknown ELLSD_Serialize" << LL_ENDL;
- }
-
- if (f.notNull())
- {
- f->format(sd, str, options);
- }
-}
-
-// static
-bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str, llssize max_bytes)
-{
- char hdr_buf[MAX_HDR_LEN + 1] = ""; /* Flawfinder: ignore */
- bool fail_if_not_legacy = false;
-
- /*
- * Get the first line before anything. Don't read more than max_bytes:
- * this get() overload reads no more than (count-1) bytes into the
- * specified buffer. In the usual case when max_bytes exceeds
- * sizeof(hdr_buf), get() will read no more than sizeof(hdr_buf)-2.
- */
- llssize max_hdr_read = MAX_HDR_LEN;
- if (max_bytes != LLSDSerialize::SIZE_UNLIMITED)
- {
- max_hdr_read = llmin(max_bytes + 1, max_hdr_read);
- }
- str.get(hdr_buf, max_hdr_read, '\n');
- auto inbuf = str.gcount();
-
- // https://en.cppreference.com/w/cpp/io/basic_istream/get
- // When the get() above sees the specified delimiter '\n', it stops there
- // without pulling it from the stream. If it turns out that the stream
- // does NOT contain a header, and the content includes meaningful '\n',
- // it's important to pull that into hdr_buf too.
- if (inbuf < max_bytes && str.get(hdr_buf[inbuf]))
- {
- // got the delimiting '\n'
- ++inbuf;
- // None of the following requires that hdr_buf contain a final '\0'
- // byte. We could store one if needed, since even the incremented
- // inbuf won't exceed sizeof(hdr_buf)-1, but there's no need.
- }
- std::string header{ hdr_buf, static_cast<std::string::size_type>(inbuf) };
- if (str.fail())
- {
- str.clear();
- fail_if_not_legacy = true;
- }
-
- if (!strncasecmp(LEGACY_NON_HEADER, hdr_buf, strlen(LEGACY_NON_HEADER))) /* Flawfinder: ignore */
- { // Create a LLSD XML parser, and parse the first chunk read above.
- LLSDXMLParser x;
- x.parsePart(hdr_buf, inbuf); // Parse the first part that was already read
- auto parsed = x.parse(str, sd, max_bytes - inbuf); // Parse the rest of it
- // Formally we should probably check (parsed != PARSE_FAILURE &&
- // parsed > 0), but since PARSE_FAILURE is -1, this suffices.
- return (parsed > 0);
- }
-
- if (fail_if_not_legacy)
- {
- LL_WARNS() << "deserialize LLSD parse failure" << LL_ENDL;
- return false;
- }
-
- /*
- * Remove the newline chars
- */
- std::string::size_type lastchar = header.find_last_not_of("\r\n");
- if (lastchar != std::string::npos)
- {
- // It's important that find_last_not_of() returns size_type, which is
- // why lastchar explicitly declares the type above. erase(size_type)
- // erases from that offset to the end of the string, whereas
- // erase(iterator) erases only a single character.
- header.erase(lastchar+1);
- }
-
- // trim off the <? ... ?> header syntax
- auto start = header.find_first_not_of("<? ");
- if (start != std::string::npos)
- {
- auto end = header.find_first_of(" ?", start);
- if (end != std::string::npos)
- {
- header = header.substr(start, end - start);
- ws(str);
- }
- }
- /*
- * Create the parser as appropriate
- */
- if (0 == LLStringUtil::compareInsensitive(header, LLSD_BINARY_HEADER))
- {
- return (parse_using<LLSDBinaryParser>(str, sd, max_bytes-inbuf) > 0);
- }
- else if (0 == LLStringUtil::compareInsensitive(header, LLSD_XML_HEADER))
- {
- return (parse_using<LLSDXMLParser>(str, sd, max_bytes-inbuf) > 0);
- }
- else if (0 == LLStringUtil::compareInsensitive(header, LLSD_NOTATION_HEADER))
- {
- return (parse_using<LLSDNotationParser>(str, sd, max_bytes-inbuf) > 0);
- }
- else // no header we recognize
- {
- LLPointer<LLSDParser> p;
- if (inbuf && hdr_buf[0] == '<')
- {
- // looks like XML
- LL_DEBUGS() << "deserialize request with no header, assuming XML" << LL_ENDL;
- p = new LLSDXMLParser;
- }
- else
- {
- // assume notation
- LL_DEBUGS() << "deserialize request with no header, assuming notation" << LL_ENDL;
- p = new LLSDNotationParser;
- }
- // Since we've already read 'inbuf' bytes into 'hdr_buf', prepend that
- // data to whatever remains in 'str'.
- LLMemoryStreamBuf already(reinterpret_cast<const U8*>(hdr_buf), inbuf);
- cat_streambuf prebuff(&already, str.rdbuf());
- std::istream prepend(&prebuff);
-#if 1
- return (p->parse(prepend, sd, max_bytes) > 0);
-#else
- // debugging the reconstituted 'prepend' stream
- // allocate a buffer that we hope is big enough for the whole thing
- std::vector<char> wholemsg((max_bytes == size_t(SIZE_UNLIMITED))? 1024 : max_bytes);
- prepend.read(wholemsg.data(), std::min(max_bytes, wholemsg.size()));
- LLMemoryStream replay(reinterpret_cast<const U8*>(wholemsg.data()), prepend.gcount());
- auto success{ p->parse(replay, sd, prepend.gcount()) > 0 };
- {
- LL_DEBUGS() << (success? "parsed: $$" : "failed: '")
- << std::string(wholemsg.data(), llmin(prepend.gcount(), 100)) << "$$"
- << LL_ENDL;
- }
- return success;
-#endif
- }
-}
-
-/**
- * Endian handlers
- */
-#if LL_BIG_ENDIAN
-U64 ll_htonll(U64 hostlonglong) { return hostlonglong; }
-U64 ll_ntohll(U64 netlonglong) { return netlonglong; }
-F64 ll_htond(F64 hostlonglong) { return hostlonglong; }
-F64 ll_ntohd(F64 netlonglong) { return netlonglong; }
-#else
-// I read some comments one a indicating that doing an integer add
-// here would be faster than a bitwise or. For now, the or has
-// programmer clarity, since the intended outcome matches the
-// operation.
-U64 ll_htonll(U64 hostlonglong)
-{
- return ((U64)(htonl((U32)((hostlonglong >> 32) & 0xFFFFFFFF))) |
- ((U64)(htonl((U32)(hostlonglong & 0xFFFFFFFF))) << 32));
-}
-U64 ll_ntohll(U64 netlonglong)
-{
- return ((U64)(ntohl((U32)((netlonglong >> 32) & 0xFFFFFFFF))) |
- ((U64)(ntohl((U32)(netlonglong & 0xFFFFFFFF))) << 32));
-}
-union LLEndianSwapper
-{
- F64 d;
- U64 i;
-};
-F64 ll_htond(F64 hostdouble)
-{
- LLEndianSwapper tmp;
- tmp.d = hostdouble;
- tmp.i = ll_htonll(tmp.i);
- return tmp.d;
-}
-F64 ll_ntohd(F64 netdouble)
-{
- LLEndianSwapper tmp;
- tmp.d = netdouble;
- tmp.i = ll_ntohll(tmp.i);
- return tmp.d;
-}
-#endif
-
-/**
- * Local functions.
- */
-/**
- * @brief Figure out what kind of string it is (raw or delimited) and handoff.
- *
- * @param istr The stream to read from.
- * @param value [out] The string which was found.
- * @param max_bytes The maximum possible length of the string. Passing in
- * a negative value will skip this check.
- * @return Returns number of bytes read off of the stream. Returns
- * PARSE_FAILURE (-1) on failure.
- */
-llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes);
-
-/**
- * @brief Parse a delimited string.
- *
- * @param istr The stream to read from, with the delimiter already popped.
- * @param value [out] The string which was found.
- * @param d The delimiter to use.
- * @return Returns number of bytes read off of the stream. Returns
- * PARSE_FAILURE (-1) on failure.
- */
-llssize deserialize_string_delim(std::istream& istr, std::string& value, char d);
-
-/**
- * @brief Read a raw string off the stream.
- *
- * @param istr The stream to read from, with the (len) parameter
- * leading the stream.
- * @param value [out] The string which was found.
- * @param d The delimiter to use.
- * @param max_bytes The maximum possible length of the string. Passing in
- * a negative value will skip this check.
- * @return Returns number of bytes read off of the stream. Returns
- * PARSE_FAILURE (-1) on failure.
- */
-llssize deserialize_string_raw(
- std::istream& istr,
- std::string& value,
- llssize max_bytes);
-
-/**
- * @brief helper method for dealing with the different notation boolean format.
- *
- * @param istr The stream to read from with the leading character stripped.
- * @param data [out] the result of the parse.
- * @param compare The string to compare the boolean against
- * @param vale The value to assign to data if the parse succeeds.
- * @return Returns number of bytes read off of the stream. Returns
- * PARSE_FAILURE (-1) on failure.
- */
-llssize deserialize_boolean(
- std::istream& istr,
- LLSD& data,
- const std::string& compare,
- bool value);
-
-/**
- * @brief Do notation escaping of a string to an ostream.
- *
- * @param value The string to escape and serialize
- * @param str The stream to serialize to.
- */
-void serialize_string(const std::string& value, std::ostream& str);
-
-
-/**
- * Local constants.
- */
-static const std::string NOTATION_TRUE_SERIAL("true");
-static const std::string NOTATION_FALSE_SERIAL("false");
-
-static const char BINARY_TRUE_SERIAL = '1';
-static const char BINARY_FALSE_SERIAL = '0';
-
-
-/**
- * LLSDParser
- */
-LLSDParser::LLSDParser()
- : mCheckLimits(true), mMaxBytesLeft(0), mParseLines(false)
-{
-}
-
-// virtual
-LLSDParser::~LLSDParser()
-{ }
-
-S32 LLSDParser::parse(std::istream& istr, LLSD& data, llssize max_bytes, S32 max_depth)
-{
- mCheckLimits = LLSDSerialize::SIZE_UNLIMITED != max_bytes;
- mMaxBytesLeft = max_bytes;
- return doParse(istr, data, max_depth);
-}
-
-
-// Parse using routine to get() lines, faster than parse()
-S32 LLSDParser::parseLines(std::istream& istr, LLSD& data)
-{
- mCheckLimits = false;
- mParseLines = true;
- return doParse(istr, data);
-}
-
-
-int LLSDParser::get(std::istream& istr) const
-{
- if(mCheckLimits) --mMaxBytesLeft;
- return istr.get();
-}
-
-std::istream& LLSDParser::get(
- std::istream& istr,
- char* s,
- std::streamsize n,
- char delim) const
-{
- istr.get(s, n, delim);
- if(mCheckLimits) mMaxBytesLeft -= istr.gcount();
- return istr;
-}
-
-std::istream& LLSDParser::get(
- std::istream& istr,
- std::streambuf& sb,
- char delim) const
-{
- istr.get(sb, delim);
- if(mCheckLimits) mMaxBytesLeft -= istr.gcount();
- return istr;
-}
-
-std::istream& LLSDParser::ignore(std::istream& istr) const
-{
- istr.ignore();
- if(mCheckLimits) --mMaxBytesLeft;
- return istr;
-}
-
-std::istream& LLSDParser::putback(std::istream& istr, char c) const
-{
- istr.putback(c);
- if(mCheckLimits) ++mMaxBytesLeft;
- return istr;
-}
-
-std::istream& LLSDParser::read(
- std::istream& istr,
- char* s,
- std::streamsize n) const
-{
- istr.read(s, n);
- if(mCheckLimits) mMaxBytesLeft -= istr.gcount();
- return istr;
-}
-
-void LLSDParser::account(llssize bytes) const
-{
- if(mCheckLimits) mMaxBytesLeft -= bytes;
-}
-
-
-/**
- * LLSDNotationParser
- */
-LLSDNotationParser::LLSDNotationParser()
-{
-}
-
-// virtual
-LLSDNotationParser::~LLSDNotationParser()
-{ }
-
-// virtual
-S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
- // map: { string:object, string:object }
- // array: [ object, object, object ]
- // undef: !
- // boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE
- // integer: i####
- // real: r####
- // uuid: u####
- // string: "g'day" | 'have a "nice" day' | s(size)"raw data"
- // uri: l"escaped"
- // date: d"YYYY-MM-DDTHH:MM:SS.FFZ"
- // binary: b##"ff3120ab1" | b(size)"raw data"
- char c;
- c = istr.peek();
- if (max_depth == 0)
- {
- return PARSE_FAILURE;
- }
- while(isspace(c))
- {
- // pop the whitespace.
- c = get(istr);
- c = istr.peek();
- continue;
- }
- if(!istr.good())
- {
- return 0;
- }
- S32 parse_count = 1;
- switch(c)
- {
- case '{':
- {
- S32 child_count = parseMap(istr, data, max_depth - 1);
- if((child_count == PARSE_FAILURE) || data.isUndefined())
- {
- parse_count = PARSE_FAILURE;
- }
- else
- {
- parse_count += child_count;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading map." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case '[':
- {
- S32 child_count = parseArray(istr, data, max_depth - 1);
- if((child_count == PARSE_FAILURE) || data.isUndefined())
- {
- parse_count = PARSE_FAILURE;
- }
- else
- {
- parse_count += child_count;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading array." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case '!':
- c = get(istr);
- data.clear();
- break;
-
- case '0':
- c = get(istr);
- data = false;
- break;
-
- case 'F':
- case 'f':
- ignore(istr);
- c = istr.peek();
- if(isalpha(c))
- {
- auto cnt = deserialize_boolean(
- istr,
- data,
- NOTATION_FALSE_SERIAL,
- false);
- if(PARSE_FAILURE == cnt) parse_count = cnt;
- else account(cnt);
- }
- else
- {
- data = false;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
-
- case '1':
- c = get(istr);
- data = true;
- break;
-
- case 'T':
- case 't':
- ignore(istr);
- c = istr.peek();
- if(isalpha(c))
- {
- auto cnt = deserialize_boolean(istr,data,NOTATION_TRUE_SERIAL,true);
- if(PARSE_FAILURE == cnt) parse_count = cnt;
- else account(cnt);
- }
- else
- {
- data = true;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
-
- case 'i':
- {
- c = get(istr);
- S32 integer = 0;
- istr >> integer;
- data = integer;
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading integer." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case 'r':
- {
- c = get(istr);
- F64 real = 0.0;
- istr >> real;
- data = real;
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading real." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case 'u':
- {
- c = get(istr);
- LLUUID id;
- istr >> id;
- data = id;
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading uuid." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case '\"':
- case '\'':
- case 's':
- if(!parseString(istr, data))
- {
- parse_count = PARSE_FAILURE;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading string." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
-
- case 'l':
- {
- c = get(istr); // pop the 'l'
- c = get(istr); // pop the delimiter
- std::string str;
- auto cnt = deserialize_string_delim(istr, str, c);
- if(PARSE_FAILURE == cnt)
- {
- parse_count = PARSE_FAILURE;
- }
- else
- {
- data = LLURI(str);
- account(cnt);
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading link." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case 'd':
- {
- c = get(istr); // pop the 'd'
- c = get(istr); // pop the delimiter
- std::string str;
- auto cnt = deserialize_string_delim(istr, str, c);
- if(PARSE_FAILURE == cnt)
- {
- parse_count = PARSE_FAILURE;
- }
- else
- {
- data = LLDate(str);
- account(cnt);
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading date." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case 'b':
- if(!parseBinary(istr, data))
- {
- parse_count = PARSE_FAILURE;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading data." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
-
- default:
- parse_count = PARSE_FAILURE;
- LL_INFOS() << "Unrecognized character while parsing: int(" << int(c)
- << ")" << LL_ENDL;
- break;
- }
- if(PARSE_FAILURE == parse_count)
- {
- data.clear();
- }
- return parse_count;
-}
-
-S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
- // map: { string:object, string:object }
- map = LLSD::emptyMap();
- S32 parse_count = 0;
- char c = get(istr);
- if(c == '{')
- {
- // eat commas, white
- bool found_name = false;
- std::string name;
- c = get(istr);
- while(c != '}' && istr.good())
- {
- if(!found_name)
- {
- if((c == '\"') || (c == '\'') || (c == 's'))
- {
- putback(istr, c);
- found_name = true;
- auto count = deserialize_string(istr, name, mMaxBytesLeft);
- if(PARSE_FAILURE == count) return PARSE_FAILURE;
- account(count);
- }
- c = get(istr);
- }
- else
- {
- if(isspace(c) || (c == ':'))
- {
- c = get(istr);
- continue;
- }
- putback(istr, c);
- LLSD child;
- S32 count = doParse(istr, child, max_depth);
- if(count > 0)
- {
- // There must be a value for every key, thus
- // child_count must be greater than 0.
- parse_count += count;
- map.insert(name, child);
- }
- else
- {
- return PARSE_FAILURE;
- }
- found_name = false;
- c = get(istr);
- }
- }
- if(c != '}')
- {
- map.clear();
- return PARSE_FAILURE;
- }
- }
- return parse_count;
-}
-
-S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
- // array: [ object, object, object ]
- array = LLSD::emptyArray();
- S32 parse_count = 0;
- char c = get(istr);
- if(c == '[')
- {
- // eat commas, white
- c = get(istr);
- while((c != ']') && istr.good())
- {
- LLSD child;
- if(isspace(c) || (c == ','))
- {
- c = get(istr);
- continue;
- }
- putback(istr, c);
- S32 count = doParse(istr, child, max_depth);
- if(PARSE_FAILURE == count)
- {
- return PARSE_FAILURE;
- }
- else
- {
- parse_count += count;
- array.append(child);
- }
- c = get(istr);
- }
- if(c != ']')
- {
- return PARSE_FAILURE;
- }
- }
- return parse_count;
-}
-
-bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
- std::string value;
- auto count = deserialize_string(istr, value, mMaxBytesLeft);
- if(PARSE_FAILURE == count) return false;
- account(count);
- data = value;
- return true;
-}
-
-bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
- // binary: b##"ff3120ab1"
- // or: b(len)"..."
-
- // I want to manually control those values here to make sure the
- // parser doesn't break when someone changes a constant somewhere
- // else.
- const U32 BINARY_BUFFER_SIZE = 256;
- const U32 STREAM_GET_COUNT = 255;
-
- // need to read the base out.
- char buf[BINARY_BUFFER_SIZE]; /* Flawfinder: ignore */
- get(istr, buf, STREAM_GET_COUNT, '"');
- char c = get(istr);
- if(c != '"') return false;
- if(0 == strncmp("b(", buf, 2))
- {
- // We probably have a valid raw binary stream. determine
- // the size, and read it.
- auto len = strtol(buf + 2, NULL, 0);
- if(mCheckLimits && (len > mMaxBytesLeft)) return false;
- std::vector<U8> value;
- if(len)
- {
- value.resize(len);
- account(fullread(istr, (char *)&value[0], len));
- }
- c = get(istr); // strip off the trailing double-quote
- data = value;
- }
- else if(0 == strncmp("b64", buf, 3))
- {
- // *FIX: A bit inefficient, but works for now. To make the
- // format better, I would need to add a hint into the
- // serialization format that indicated how long it was.
- std::stringstream coded_stream;
- get(istr, *(coded_stream.rdbuf()), '\"');
- c = get(istr);
- std::string encoded(coded_stream.str());
- S32 len = apr_base64_decode_len(encoded.c_str());
- std::vector<U8> value;
- if(len)
- {
- value.resize(len);
- len = apr_base64_decode_binary(&value[0], encoded.c_str());
- value.resize(len);
- }
- data = value;
- }
- else if(0 == strncmp("b16", buf, 3))
- {
- // yay, base 16. We pop the next character which is either a
- // double quote or base 16 data. If it's a double quote, we're
- // done parsing. If it's not, put the data back, and read the
- // stream until the next double quote.
- char* read; /*Flawfinder: ignore*/
- U8 byte;
- U8 byte_buffer[BINARY_BUFFER_SIZE];
- U8* write;
- std::vector<U8> value;
- c = get(istr);
- while(c != '"')
- {
- putback(istr, c);
- read = buf;
- write = byte_buffer;
- get(istr, buf, STREAM_GET_COUNT, '"');
- c = get(istr);
- while(*read != '\0') /*Flawfinder: ignore*/
- {
- byte = hex_as_nybble(*read++);
- byte = byte << 4;
- byte |= hex_as_nybble(*read++);
- *write++ = byte;
- }
- // copy the data out of the byte buffer
- value.insert(value.end(), byte_buffer, write);
- }
- data = value;
- }
- else
- {
- return false;
- }
- return true;
-}
-
-
-/**
- * LLSDBinaryParser
- */
-LLSDBinaryParser::LLSDBinaryParser()
-{
-}
-
-// virtual
-LLSDBinaryParser::~LLSDBinaryParser()
-{
-}
-
-// virtual
-S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD
-/**
- * Undefined: '!'<br>
- * Boolean: '1' for true '0' for false<br>
- * Integer: 'i' + 4 bytes network byte order<br>
- * Real: 'r' + 8 bytes IEEE double<br>
- * UUID: 'u' + 16 byte unsigned integer<br>
- * String: 's' + 4 byte integer size + string<br>
- * strings also secretly support the notation format
- * Date: 'd' + 8 byte IEEE double for seconds since epoch<br>
- * URI: 'l' + 4 byte integer size + string uri<br>
- * Binary: 'b' + 4 byte integer size + binary data<br>
- * Array: '[' + 4 byte integer size + all values + ']'<br>
- * Map: '{' + 4 byte integer size every(key + value) + '}'<br>
- * map keys are serialized as s + 4 byte integer size + string or in the
- * notation format.
- */
- char c;
- c = get(istr);
- if(!istr.good())
- {
- return 0;
- }
- if (max_depth == 0)
- {
- return PARSE_FAILURE;
- }
- S32 parse_count = 1;
- switch(c)
- {
- case '{':
- {
- S32 child_count = parseMap(istr, data, max_depth - 1);
- if((child_count == PARSE_FAILURE) || data.isUndefined())
- {
- parse_count = PARSE_FAILURE;
- }
- else
- {
- parse_count += child_count;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary map." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case '[':
- {
- S32 child_count = parseArray(istr, data, max_depth - 1);
- if((child_count == PARSE_FAILURE) || data.isUndefined())
- {
- parse_count = PARSE_FAILURE;
- }
- else
- {
- parse_count += child_count;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary array." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case '!':
- data.clear();
- break;
-
- case '0':
- data = false;
- break;
-
- case '1':
- data = true;
- break;
-
- case 'i':
- {
- U32 value_nbo = 0;
- read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/
- data = (S32)ntohl(value_nbo);
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary integer." << LL_ENDL;
- }
- break;
- }
-
- case 'r':
- {
- F64 real_nbo = 0.0;
- read(istr, (char*)&real_nbo, sizeof(F64)); /*Flawfinder: ignore*/
- data = ll_ntohd(real_nbo);
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary real." << LL_ENDL;
- }
- break;
- }
-
- case 'u':
- {
- LLUUID id;
- read(istr, (char*)(&id.mData), UUID_BYTES); /*Flawfinder: ignore*/
- data = id;
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary uuid." << LL_ENDL;
- }
- break;
- }
-
- case '\'':
- case '"':
- {
- std::string value;
- auto cnt = deserialize_string_delim(istr, value, c);
- if(PARSE_FAILURE == cnt)
- {
- parse_count = PARSE_FAILURE;
- }
- else
- {
- data = value;
- account(cnt);
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary (notation-style) string."
- << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case 's':
- {
- std::string value;
- if(parseString(istr, value))
- {
- data = value;
- }
- else
- {
- parse_count = PARSE_FAILURE;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary string." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case 'l':
- {
- std::string value;
- if(parseString(istr, value))
- {
- data = LLURI(value);
- }
- else
- {
- parse_count = PARSE_FAILURE;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary link." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case 'd':
- {
- F64 real = 0.0;
- read(istr, (char*)&real, sizeof(F64)); /*Flawfinder: ignore*/
- data = LLDate(real);
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary date." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- case 'b':
- {
- // We probably have a valid raw binary stream. determine
- // the size, and read it.
- U32 size_nbo = 0;
- read(istr, (char*)&size_nbo, sizeof(U32)); /*Flawfinder: ignore*/
- S32 size = (S32)ntohl(size_nbo);
- if(mCheckLimits && (size > mMaxBytesLeft))
- {
- parse_count = PARSE_FAILURE;
- }
- else
- {
- std::vector<U8> value;
- if(size > 0)
- {
- value.resize(size);
- account(fullread(istr, (char*)&value[0], size));
- }
- data = value;
- }
- if(istr.fail())
- {
- LL_INFOS() << "STREAM FAILURE reading binary." << LL_ENDL;
- parse_count = PARSE_FAILURE;
- }
- break;
- }
-
- default:
- parse_count = PARSE_FAILURE;
- LL_INFOS() << "Unrecognized character while parsing: int(" << int(c)
- << ")" << LL_ENDL;
- break;
- }
- if(PARSE_FAILURE == parse_count)
- {
- data.clear();
- }
- return parse_count;
-}
-
-S32 LLSDBinaryParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const
-{
- map = LLSD::emptyMap();
- U32 value_nbo = 0;
- read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/
- S32 size = (S32)ntohl(value_nbo);
- S32 parse_count = 0;
- S32 count = 0;
- char c = get(istr);
- while(c != '}' && (count < size) && istr.good())
- {
- std::string name;
- switch(c)
- {
- case 'k':
- if(!parseString(istr, name))
- {
- return PARSE_FAILURE;
- }
- break;
- case '\'':
- case '"':
- {
- auto cnt = deserialize_string_delim(istr, name, c);
- if(PARSE_FAILURE == cnt) return PARSE_FAILURE;
- account(cnt);
- break;
- }
- }
- LLSD child;
- S32 child_count = doParse(istr, child, max_depth);
- if(child_count > 0)
- {
- // There must be a value for every key, thus child_count
- // must be greater than 0.
- parse_count += child_count;
- map.insert(name, child);
- }
- else
- {
- return PARSE_FAILURE;
- }
- ++count;
- c = get(istr);
- }
- if((c != '}') || (count < size))
- {
- // Make sure it is correctly terminated and we parsed as many
- // as were said to be there.
- return PARSE_FAILURE;
- }
- return parse_count;
-}
-
-S32 LLSDBinaryParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const
-{
- array = LLSD::emptyArray();
- U32 value_nbo = 0;
- read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/
- S32 size = (S32)ntohl(value_nbo);
-
- // *FIX: This would be a good place to reserve some space in the
- // array...
-
- S32 parse_count = 0;
- S32 count = 0;
- char c = istr.peek();
- while((c != ']') && (count < size) && istr.good())
- {
- LLSD child;
- S32 child_count = doParse(istr, child, max_depth);
- if(PARSE_FAILURE == child_count)
- {
- return PARSE_FAILURE;
- }
- if(child_count)
- {
- parse_count += child_count;
- array.append(child);
- }
- ++count;
- c = istr.peek();
- }
- c = get(istr);
- if((c != ']') || (count < size))
- {
- // Make sure it is correctly terminated and we parsed as many
- // as were said to be there.
- return PARSE_FAILURE;
- }
- return parse_count;
-}
-
-bool LLSDBinaryParser::parseString(
- std::istream& istr,
- std::string& value) const
-{
- // *FIX: This is memory inefficient.
- U32 value_nbo = 0;
- read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/
- S32 size = (S32)ntohl(value_nbo);
- if(mCheckLimits && (size > mMaxBytesLeft)) return false;
- if(size < 0) return false;
- std::vector<char> buf;
- if(size)
- {
- buf.resize(size);
- account(fullread(istr, &buf[0], size));
- value.assign(buf.begin(), buf.end());
- }
- return true;
-}
-
-
-/**
- * LLSDFormatter
- */
-LLSDFormatter::LLSDFormatter(bool boolAlpha, const std::string& realFmt, EFormatterOptions options):
- mOptions(options)
-{
- boolalpha(boolAlpha);
- realFormat(realFmt);
-}
-
-// virtual
-LLSDFormatter::~LLSDFormatter()
-{ }
-
-void LLSDFormatter::boolalpha(bool alpha)
-{
- mBoolAlpha = alpha;
-}
-
-void LLSDFormatter::realFormat(const std::string& format)
-{
- mRealFormat = format;
-}
-
-S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr) const
-{
- // pass options captured by constructor
- return format(data, ostr, mOptions);
-}
-
-S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const
-{
- return format_impl(data, ostr, options, 0);
-}
-
-void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const
-{
- std::string buffer = llformat(mRealFormat.c_str(), real);
- ostr << buffer;
-}
-
-/**
- * LLSDNotationFormatter
- */
-LLSDNotationFormatter::LLSDNotationFormatter(bool boolAlpha, const std::string& realFormat,
- EFormatterOptions options):
- LLSDFormatter(boolAlpha, realFormat, options)
-{
-}
-
-// virtual
-LLSDNotationFormatter::~LLSDNotationFormatter()
-{ }
-
-// static
-std::string LLSDNotationFormatter::escapeString(const std::string& in)
-{
- std::ostringstream ostr;
- serialize_string(in, ostr);
- return ostr.str();
-}
-
-S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr,
- EFormatterOptions options, U32 level) const
-{
- S32 format_count = 1;
- std::string pre;
- std::string post;
-
- if (options & LLSDFormatter::OPTIONS_PRETTY)
- {
- for (U32 i = 0; i < level; i++)
- {
- pre += " ";
- }
- post = "\n";
- }
-
- switch(data.type())
- {
- case LLSD::TypeMap:
- {
- if (0 != level) ostr << post << pre;
- ostr << "{";
- std::string inner_pre;
- if (options & LLSDFormatter::OPTIONS_PRETTY)
- {
- inner_pre = pre + " ";
- }
-
- bool need_comma = false;
- LLSD::map_const_iterator iter = data.beginMap();
- LLSD::map_const_iterator end = data.endMap();
- for(; iter != end; ++iter)
- {
- if(need_comma) ostr << ",";
- need_comma = true;
- ostr << post << inner_pre << '\'';
- serialize_string((*iter).first, ostr);
- ostr << "':";
- format_count += format_impl((*iter).second, ostr, options, level + 2);
- }
- ostr << post << pre << "}";
- break;
- }
-
- case LLSD::TypeArray:
- {
- ostr << post << pre << "[";
- bool need_comma = false;
- LLSD::array_const_iterator iter = data.beginArray();
- LLSD::array_const_iterator end = data.endArray();
- for(; iter != end; ++iter)
- {
- if(need_comma) ostr << ",";
- need_comma = true;
- format_count += format_impl(*iter, ostr, options, level + 1);
- }
- ostr << "]";
- break;
- }
-
- case LLSD::TypeUndefined:
- ostr << "!";
- break;
-
- case LLSD::TypeBoolean:
- if(mBoolAlpha ||
-#if( LL_WINDOWS || __GNUC__ > 2)
- (ostr.flags() & std::ios::boolalpha)
-#else
- (ostr.flags() & 0x0100)
-#endif
- )
- {
- ostr << (data.asBoolean()
- ? NOTATION_TRUE_SERIAL : NOTATION_FALSE_SERIAL);
- }
- else
- {
- ostr << (data.asBoolean() ? 1 : 0);
- }
- break;
-
- case LLSD::TypeInteger:
- ostr << "i" << data.asInteger();
- break;
-
- case LLSD::TypeReal:
- ostr << "r";
- if(mRealFormat.empty())
- {
- ostr << data.asReal();
- }
- else
- {
- formatReal(data.asReal(), ostr);
- }
- break;
-
- case LLSD::TypeUUID:
- ostr << "u" << data.asUUID();
- break;
-
- case LLSD::TypeString:
- ostr << '\'';
- serialize_string(data.asStringRef(), ostr);
- ostr << '\'';
- break;
-
- case LLSD::TypeDate:
- ostr << "d\"" << data.asDate() << "\"";
- break;
-
- case LLSD::TypeURI:
- ostr << "l\"";
- serialize_string(data.asString(), ostr);
- ostr << "\"";
- break;
-
- case LLSD::TypeBinary:
- {
- // *FIX: memory inefficient.
- const std::vector<U8>& buffer = data.asBinary();
- if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY)
- {
- ostr << "b16\"";
- if (! buffer.empty())
- {
- std::ios_base::fmtflags old_flags = ostr.flags();
- ostr.setf( std::ios::hex, std::ios::basefield );
- // It shouldn't strictly matter whether the emitted hex digits
- // are uppercase; LLSDNotationParser handles either; but as of
- // 2020-05-13, Python's llbase.llsd requires uppercase hex.
- ostr << std::uppercase;
- auto oldfill(ostr.fill('0'));
- auto oldwidth(ostr.width());
- for (size_t i = 0; i < buffer.size(); i++)
- {
- // have to restate setw() before every conversion
- ostr << std::setw(2) << (int) buffer[i];
- }
- ostr.width(oldwidth);
- ostr.fill(oldfill);
- ostr.flags(old_flags);
- }
- }
- else // ! OPTIONS_PRETTY_BINARY
- {
- ostr << "b(" << buffer.size() << ")\"";
- if (! buffer.empty())
- {
- ostr.write((const char*)&buffer[0], buffer.size());
- }
- }
- ostr << "\"";
- break;
- }
-
- default:
- // *NOTE: This should never happen.
- ostr << "!";
- break;
- }
- return format_count;
-}
-
-/**
- * LLSDBinaryFormatter
- */
-LLSDBinaryFormatter::LLSDBinaryFormatter(bool boolAlpha, const std::string& realFormat,
- EFormatterOptions options):
- LLSDFormatter(boolAlpha, realFormat, options)
-{
-}
-
-// virtual
-LLSDBinaryFormatter::~LLSDBinaryFormatter()
-{ }
-
-// virtual
-S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr,
- EFormatterOptions options, U32 level) const
-{
- S32 format_count = 1;
- switch(data.type())
- {
- case LLSD::TypeMap:
- {
- ostr.put('{');
- U32 size_nbo = htonl(data.size());
- ostr.write((const char*)(&size_nbo), sizeof(U32));
- LLSD::map_const_iterator iter = data.beginMap();
- LLSD::map_const_iterator end = data.endMap();
- for(; iter != end; ++iter)
- {
- ostr.put('k');
- formatString((*iter).first, ostr);
- format_count += format_impl((*iter).second, ostr, options, level+1);
- }
- ostr.put('}');
- break;
- }
-
- case LLSD::TypeArray:
- {
- ostr.put('[');
- U32 size_nbo = htonl(data.size());
- ostr.write((const char*)(&size_nbo), sizeof(U32));
- LLSD::array_const_iterator iter = data.beginArray();
- LLSD::array_const_iterator end = data.endArray();
- for(; iter != end; ++iter)
- {
- format_count += format_impl(*iter, ostr, options, level+1);
- }
- ostr.put(']');
- break;
- }
-
- case LLSD::TypeUndefined:
- ostr.put('!');
- break;
-
- case LLSD::TypeBoolean:
- if(data.asBoolean()) ostr.put(BINARY_TRUE_SERIAL);
- else ostr.put(BINARY_FALSE_SERIAL);
- break;
-
- case LLSD::TypeInteger:
- {
- ostr.put('i');
- U32 value_nbo = htonl(data.asInteger());
- ostr.write((const char*)(&value_nbo), sizeof(U32));
- break;
- }
-
- case LLSD::TypeReal:
- {
- ostr.put('r');
- F64 value_nbo = ll_htond(data.asReal());
- ostr.write((const char*)(&value_nbo), sizeof(F64));
- break;
- }
-
- case LLSD::TypeUUID:
- {
- ostr.put('u');
- LLUUID temp = data.asUUID();
- ostr.write((const char*)(&(temp.mData)), UUID_BYTES);
- break;
- }
-
- case LLSD::TypeString:
- ostr.put('s');
- formatString(data.asStringRef(), ostr);
- break;
-
- case LLSD::TypeDate:
- {
- ostr.put('d');
- F64 value = data.asReal();
- ostr.write((const char*)(&value), sizeof(F64));
- break;
- }
-
- case LLSD::TypeURI:
- ostr.put('l');
- formatString(data.asString(), ostr);
- break;
-
- case LLSD::TypeBinary:
- {
- ostr.put('b');
- const std::vector<U8>& buffer = data.asBinary();
- U32 size_nbo = htonl(buffer.size());
- ostr.write((const char*)(&size_nbo), sizeof(U32));
- if(buffer.size()) ostr.write((const char*)&buffer[0], buffer.size());
- break;
- }
-
- default:
- // *NOTE: This should never happen.
- ostr.put('!');
- break;
- }
- return format_count;
-}
-
-void LLSDBinaryFormatter::formatString(
- const std::string& string,
- std::ostream& ostr) const
-{
- U32 size_nbo = htonl(string.size());
- ostr.write((const char*)(&size_nbo), sizeof(U32));
- ostr.write(string.c_str(), string.size());
-}
-
-/**
- * local functions
- */
-llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes)
-{
- int c = istr.get();
- if(istr.fail())
- {
- // No data in stream, bail out but mention the character we
- // grabbed.
- return LLSDParser::PARSE_FAILURE;
- }
-
- llssize rv = LLSDParser::PARSE_FAILURE;
- switch(c)
- {
- case '\'':
- case '"':
- rv = deserialize_string_delim(istr, value, c);
- break;
- case 's':
- // technically, less than max_bytes, but this is just meant to
- // catch egregious protocol errors. parse errors will be
- // caught in the case of incorrect counts.
- rv = deserialize_string_raw(istr, value, max_bytes);
- break;
- default:
- break;
- }
- if(LLSDParser::PARSE_FAILURE == rv) return rv;
- return rv + 1; // account for the character grabbed at the top.
-}
-
-llssize deserialize_string_delim(
- std::istream& istr,
- std::string& value,
- char delim)
-{
- std::ostringstream write_buffer;
- bool found_escape = false;
- bool found_hex = false;
- bool found_digit = false;
- U8 byte = 0;
- llssize count = 0;
-
- while (true)
- {
- int next_byte = istr.get();
- ++count;
-
- if(istr.fail())
- {
- // If our stream is empty, break out
- value = write_buffer.str();
- return LLSDParser::PARSE_FAILURE;
- }
-
- char next_char = (char)next_byte; // Now that we know it's not EOF
-
- if(found_escape)
- {
- // next character(s) is a special sequence.
- if(found_hex)
- {
- if(found_digit)
- {
- found_digit = false;
- found_hex = false;
- found_escape = false;
- byte = byte << 4;
- byte |= hex_as_nybble(next_char);
- write_buffer << byte;
- byte = 0;
- }
- else
- {
- // next character is the first nybble of
- //
- found_digit = true;
- byte = hex_as_nybble(next_char);
- }
- }
- else if(next_char == 'x')
- {
- found_hex = true;
- }
- else
- {
- switch(next_char)
- {
- case 'a':
- write_buffer << '\a';
- break;
- case 'b':
- write_buffer << '\b';
- break;
- case 'f':
- write_buffer << '\f';
- break;
- case 'n':
- write_buffer << '\n';
- break;
- case 'r':
- write_buffer << '\r';
- break;
- case 't':
- write_buffer << '\t';
- break;
- case 'v':
- write_buffer << '\v';
- break;
- default:
- write_buffer << next_char;
- break;
- }
- found_escape = false;
- }
- }
- else if(next_char == '\\')
- {
- found_escape = true;
- }
- else if(next_char == delim)
- {
- break;
- }
- else
- {
- write_buffer << next_char;
- }
- }
-
- value = write_buffer.str();
- return count;
-}
-
-llssize deserialize_string_raw(
- std::istream& istr,
- std::string& value,
- llssize max_bytes)
-{
- llssize count = 0;
- const S32 BUF_LEN = 20;
- char buf[BUF_LEN]; /* Flawfinder: ignore */
- istr.get(buf, BUF_LEN - 1, ')');
- count += istr.gcount();
- int c = istr.get();
- c = istr.get();
- count += 2;
- if(((c == '"') || (c == '\'')) && (buf[0] == '('))
- {
- // We probably have a valid raw string. determine
- // the size, and read it.
- // *FIX: This is memory inefficient.
- auto len = strtol(buf + 1, NULL, 0);
- if((max_bytes>0)&&(len>max_bytes)) return LLSDParser::PARSE_FAILURE;
- std::vector<char> buf;
- if(len)
- {
- buf.resize(len);
- count += fullread(istr, (char *)&buf[0], len);
- value.assign(buf.begin(), buf.end());
- }
- c = istr.get();
- ++count;
- if(!((c == '"') || (c == '\'')))
- {
- return LLSDParser::PARSE_FAILURE;
- }
- }
- else
- {
- return LLSDParser::PARSE_FAILURE;
- }
- return count;
-}
-
-static const char* NOTATION_STRING_CHARACTERS[256] =
-{
- "\\x00", // 0
- "\\x01", // 1
- "\\x02", // 2
- "\\x03", // 3
- "\\x04", // 4
- "\\x05", // 5
- "\\x06", // 6
- "\\a", // 7
- "\\b", // 8
- "\\t", // 9
- "\\n", // 10
- "\\v", // 11
- "\\f", // 12
- "\\r", // 13
- "\\x0e", // 14
- "\\x0f", // 15
- "\\x10", // 16
- "\\x11", // 17
- "\\x12", // 18
- "\\x13", // 19
- "\\x14", // 20
- "\\x15", // 21
- "\\x16", // 22
- "\\x17", // 23
- "\\x18", // 24
- "\\x19", // 25
- "\\x1a", // 26
- "\\x1b", // 27
- "\\x1c", // 28
- "\\x1d", // 29
- "\\x1e", // 30
- "\\x1f", // 31
- " ", // 32
- "!", // 33
- "\"", // 34
- "#", // 35
- "$", // 36
- "%", // 37
- "&", // 38
- "\\'", // 39
- "(", // 40
- ")", // 41
- "*", // 42
- "+", // 43
- ",", // 44
- "-", // 45
- ".", // 46
- "/", // 47
- "0", // 48
- "1", // 49
- "2", // 50
- "3", // 51
- "4", // 52
- "5", // 53
- "6", // 54
- "7", // 55
- "8", // 56
- "9", // 57
- ":", // 58
- ";", // 59
- "<", // 60
- "=", // 61
- ">", // 62
- "?", // 63
- "@", // 64
- "A", // 65
- "B", // 66
- "C", // 67
- "D", // 68
- "E", // 69
- "F", // 70
- "G", // 71
- "H", // 72
- "I", // 73
- "J", // 74
- "K", // 75
- "L", // 76
- "M", // 77
- "N", // 78
- "O", // 79
- "P", // 80
- "Q", // 81
- "R", // 82
- "S", // 83
- "T", // 84
- "U", // 85
- "V", // 86
- "W", // 87
- "X", // 88
- "Y", // 89
- "Z", // 90
- "[", // 91
- "\\\\", // 92
- "]", // 93
- "^", // 94
- "_", // 95
- "`", // 96
- "a", // 97
- "b", // 98
- "c", // 99
- "d", // 100
- "e", // 101
- "f", // 102
- "g", // 103
- "h", // 104
- "i", // 105
- "j", // 106
- "k", // 107
- "l", // 108
- "m", // 109
- "n", // 110
- "o", // 111
- "p", // 112
- "q", // 113
- "r", // 114
- "s", // 115
- "t", // 116
- "u", // 117
- "v", // 118
- "w", // 119
- "x", // 120
- "y", // 121
- "z", // 122
- "{", // 123
- "|", // 124
- "}", // 125
- "~", // 126
- "\\x7f", // 127
- "\\x80", // 128
- "\\x81", // 129
- "\\x82", // 130
- "\\x83", // 131
- "\\x84", // 132
- "\\x85", // 133
- "\\x86", // 134
- "\\x87", // 135
- "\\x88", // 136
- "\\x89", // 137
- "\\x8a", // 138
- "\\x8b", // 139
- "\\x8c", // 140
- "\\x8d", // 141
- "\\x8e", // 142
- "\\x8f", // 143
- "\\x90", // 144
- "\\x91", // 145
- "\\x92", // 146
- "\\x93", // 147
- "\\x94", // 148
- "\\x95", // 149
- "\\x96", // 150
- "\\x97", // 151
- "\\x98", // 152
- "\\x99", // 153
- "\\x9a", // 154
- "\\x9b", // 155
- "\\x9c", // 156
- "\\x9d", // 157
- "\\x9e", // 158
- "\\x9f", // 159
- "\\xa0", // 160
- "\\xa1", // 161
- "\\xa2", // 162
- "\\xa3", // 163
- "\\xa4", // 164
- "\\xa5", // 165
- "\\xa6", // 166
- "\\xa7", // 167
- "\\xa8", // 168
- "\\xa9", // 169
- "\\xaa", // 170
- "\\xab", // 171
- "\\xac", // 172
- "\\xad", // 173
- "\\xae", // 174
- "\\xaf", // 175
- "\\xb0", // 176
- "\\xb1", // 177
- "\\xb2", // 178
- "\\xb3", // 179
- "\\xb4", // 180
- "\\xb5", // 181
- "\\xb6", // 182
- "\\xb7", // 183
- "\\xb8", // 184
- "\\xb9", // 185
- "\\xba", // 186
- "\\xbb", // 187
- "\\xbc", // 188
- "\\xbd", // 189
- "\\xbe", // 190
- "\\xbf", // 191
- "\\xc0", // 192
- "\\xc1", // 193
- "\\xc2", // 194
- "\\xc3", // 195
- "\\xc4", // 196
- "\\xc5", // 197
- "\\xc6", // 198
- "\\xc7", // 199
- "\\xc8", // 200
- "\\xc9", // 201
- "\\xca", // 202
- "\\xcb", // 203
- "\\xcc", // 204
- "\\xcd", // 205
- "\\xce", // 206
- "\\xcf", // 207
- "\\xd0", // 208
- "\\xd1", // 209
- "\\xd2", // 210
- "\\xd3", // 211
- "\\xd4", // 212
- "\\xd5", // 213
- "\\xd6", // 214
- "\\xd7", // 215
- "\\xd8", // 216
- "\\xd9", // 217
- "\\xda", // 218
- "\\xdb", // 219
- "\\xdc", // 220
- "\\xdd", // 221
- "\\xde", // 222
- "\\xdf", // 223
- "\\xe0", // 224
- "\\xe1", // 225
- "\\xe2", // 226
- "\\xe3", // 227
- "\\xe4", // 228
- "\\xe5", // 229
- "\\xe6", // 230
- "\\xe7", // 231
- "\\xe8", // 232
- "\\xe9", // 233
- "\\xea", // 234
- "\\xeb", // 235
- "\\xec", // 236
- "\\xed", // 237
- "\\xee", // 238
- "\\xef", // 239
- "\\xf0", // 240
- "\\xf1", // 241
- "\\xf2", // 242
- "\\xf3", // 243
- "\\xf4", // 244
- "\\xf5", // 245
- "\\xf6", // 246
- "\\xf7", // 247
- "\\xf8", // 248
- "\\xf9", // 249
- "\\xfa", // 250
- "\\xfb", // 251
- "\\xfc", // 252
- "\\xfd", // 253
- "\\xfe", // 254
- "\\xff" // 255
-};
-
-void serialize_string(const std::string& value, std::ostream& str)
-{
- std::string::const_iterator it = value.begin();
- std::string::const_iterator end = value.end();
- U8 c;
- for(; it != end; ++it)
- {
- c = (U8)(*it);
- str << NOTATION_STRING_CHARACTERS[c];
- }
-}
-
-llssize deserialize_boolean(
- std::istream& istr,
- LLSD& data,
- const std::string& compare,
- bool value)
-{
- //
- // this method is a little goofy, because it gets the stream at
- // the point where the t or f has already been
- // consumed. Basically, parse for a patch to the string passed in
- // starting at index 1. If it's a match:
- // * assign data to value
- // * return the number of bytes read
- // otherwise:
- // * set data to LLSD::null
- // * return LLSDParser::PARSE_FAILURE (-1)
- //
- llssize bytes_read = 0;
- std::string::size_type ii = 0;
- char c = istr.peek();
- while((++ii < compare.size())
- && (tolower(c) == (int)compare[ii])
- && istr.good())
- {
- istr.ignore();
- ++bytes_read;
- c = istr.peek();
- }
- if(compare.size() != ii)
- {
- data.clear();
- return LLSDParser::PARSE_FAILURE;
- }
- data = value;
- return bytes_read;
-}
-
-std::ostream& operator<<(std::ostream& s, const LLSD& llsd)
-{
- s << LLSDNotationStreamer(llsd);
- return s;
-}
-
-
-//dirty little zippers -- yell at davep if these are horrid
-
-//return a string containing gzipped bytes of binary serialized LLSD
-// VERY inefficient -- creates several copies of LLSD block in memory
-std::string zip_llsd(LLSD& data)
-{
- std::stringstream llsd_strm;
-
- LLSDSerialize::toBinary(data, llsd_strm);
-
- const U32 CHUNK = 65536;
-
- z_stream strm;
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
-
- S32 ret = deflateInit(&strm, Z_BEST_COMPRESSION);
- if (ret != Z_OK)
- {
- LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL;
- return std::string();
- }
-
- std::string source = llsd_strm.str();
-
- U8 out[CHUNK];
-
- strm.avail_in = narrow<size_t>(source.size());
- strm.next_in = (U8*) source.data();
- U8* output = NULL;
-
- U32 cur_size = 0;
-
- U32 have = 0;
-
- do
- {
- strm.avail_out = CHUNK;
- strm.next_out = out;
-
- ret = deflate(&strm, Z_FINISH);
- if (ret == Z_OK || ret == Z_STREAM_END)
- { //copy result into output
- if (strm.avail_out >= CHUNK)
- {
- deflateEnd(&strm);
- if(output)
- free(output);
- LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL;
- return std::string();
- }
-
- have = CHUNK-strm.avail_out;
- U8* new_output = (U8*) realloc(output, cur_size+have);
- if (new_output == NULL)
- {
- LL_WARNS() << "Failed to compress LLSD block: can't reallocate memory, current size: " << cur_size << " bytes; requested " << cur_size + have << " bytes." << LL_ENDL;
- deflateEnd(&strm);
- if (output)
- {
- free(output);
- }
- return std::string();
- }
- output = new_output;
- memcpy(output+cur_size, out, have);
- cur_size += have;
- }
- else
- {
- deflateEnd(&strm);
- if(output)
- free(output);
- LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL;
- return std::string();
- }
- }
- while (ret == Z_OK);
-
- std::string::size_type size = cur_size;
-
- std::string result((char*) output, size);
- deflateEnd(&strm);
- if(output)
- free(output);
-
- return result;
-}
-
-//decompress a block of LLSD from provided istream
-// not very efficient -- creats a copy of decompressed LLSD block in memory
-// and deserializes from that copy using LLSDSerialize
-LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, std::istream& is, S32 size)
-{
- std::unique_ptr<U8[]> in = std::unique_ptr<U8[]>(new(std::nothrow) U8[size]);
- if (!in)
- {
- return ZR_MEM_ERROR;
- }
- is.read((char*) in.get(), size);
-
- return unzip_llsd(data, in.get(), size);
-}
-
-LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, const U8* in, S32 size)
-{
- U8* result = NULL;
- llssize cur_size = 0;
- z_stream strm;
-
- constexpr U32 CHUNK = 1024 * 512;
-
- static thread_local std::unique_ptr<U8[]> out;
- if (!out)
- {
- out = std::unique_ptr<U8[]>(new(std::nothrow) U8[CHUNK]);
- }
-
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
- strm.avail_in = size;
- strm.next_in = const_cast<U8*>(in);
-
- S32 ret = inflateInit(&strm);
-
- do
- {
- strm.avail_out = CHUNK;
- strm.next_out = out.get();
- ret = inflate(&strm, Z_NO_FLUSH);
- switch (ret)
- {
- case Z_NEED_DICT:
- case Z_DATA_ERROR:
- {
- inflateEnd(&strm);
- free(result);
- return ZR_DATA_ERROR;
- }
- case Z_STREAM_ERROR:
- case Z_BUF_ERROR:
- {
- inflateEnd(&strm);
- free(result);
- return ZR_BUFFER_ERROR;
- }
-
- case Z_MEM_ERROR:
- {
- inflateEnd(&strm);
- free(result);
- return ZR_MEM_ERROR;
- }
- }
-
- U32 have = CHUNK-strm.avail_out;
-
- U8* new_result = (U8*)realloc(result, cur_size + have);
- if (new_result == NULL)
- {
- inflateEnd(&strm);
- if (result)
- {
- free(result);
- }
- return ZR_MEM_ERROR;
- }
- result = new_result;
- memcpy(result+cur_size, out.get(), have);
- cur_size += have;
-
- } while (ret == Z_OK && ret != Z_STREAM_END);
-
- inflateEnd(&strm);
-
- if (ret != Z_STREAM_END)
- {
- free(result);
- return ZR_DATA_ERROR;
- }
-
- //result now points to the decompressed LLSD block
- {
- char* result_ptr = strip_deprecated_header((char*)result, cur_size);
-
- boost::iostreams::stream<boost::iostreams::array_source> istrm(result_ptr, cur_size);
-
- if (!LLSDSerialize::fromBinary(data, istrm, cur_size, UNZIP_LLSD_MAX_DEPTH))
- {
- free(result);
- return ZR_PARSE_ERROR;
- }
- }
-
- free(result);
- return ZR_OK;
-}
-//This unzip function will only work with a gzip header and trailer - while the contents
-//of the actual compressed data is the same for either format (gzip vs zlib ), the headers
-//and trailers are different for the formats.
-U8* unzip_llsdNavMesh( bool& valid, size_t& outsize, std::istream& is, S32 size )
-{
- if (size == 0)
- {
- LL_WARNS() << "No data to unzip." << LL_ENDL;
- return NULL;
- }
-
- U8* result = NULL;
- U32 cur_size = 0;
- z_stream strm;
-
- const U32 CHUNK = 0x4000;
-
- U8 *in = new(std::nothrow) U8[size];
- if (in == NULL)
- {
- LL_WARNS() << "Memory allocation failure." << LL_ENDL;
- return NULL;
- }
- is.read((char*) in, size);
-
- U8 out[CHUNK];
-
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
- strm.avail_in = size;
- strm.next_in = in;
-
-
- S32 ret = inflateInit2(&strm, windowBits | ENABLE_ZLIB_GZIP );
- do
- {
- strm.avail_out = CHUNK;
- strm.next_out = out;
- ret = inflate(&strm, Z_NO_FLUSH);
- if (ret == Z_STREAM_ERROR)
- {
- inflateEnd(&strm);
- free(result);
- delete [] in;
- valid = false;
- }
-
- switch (ret)
- {
- case Z_NEED_DICT:
- ret = Z_DATA_ERROR;
- case Z_DATA_ERROR:
- case Z_MEM_ERROR:
- inflateEnd(&strm);
- free(result);
- delete [] in;
- valid = false;
- break;
- }
-
- U32 have = CHUNK-strm.avail_out;
-
- U8* new_result = (U8*) realloc(result, cur_size + have);
- if (new_result == NULL)
- {
- LL_WARNS() << "Failed to unzip LLSD NavMesh block: can't reallocate memory, current size: " << cur_size
- << " bytes; requested " << cur_size + have
- << " bytes; total syze: ." << size << " bytes."
- << LL_ENDL;
- inflateEnd(&strm);
- if (result)
- {
- free(result);
- }
- delete[] in;
- valid = false;
- return NULL;
- }
- result = new_result;
- memcpy(result+cur_size, out, have);
- cur_size += have;
-
- } while (ret == Z_OK);
-
- inflateEnd(&strm);
- delete [] in;
-
- if (ret != Z_STREAM_END)
- {
- free(result);
- valid = false;
- return NULL;
- }
-
- //result now points to the decompressed LLSD block
- {
- outsize= cur_size;
- valid = true;
- }
-
- return result;
-}
-
-char* strip_deprecated_header(char* in, llssize& cur_size, llssize* header_size)
-{
- const char* deprecated_header = "<? LLSD/Binary ?>";
- constexpr size_t deprecated_header_size = 17;
-
- if (cur_size > deprecated_header_size
- && memcmp(in, deprecated_header, deprecated_header_size) == 0)
- {
- in = in + deprecated_header_size;
- cur_size = cur_size - deprecated_header_size;
- if (header_size)
- {
- *header_size = deprecated_header_size + 1;
- }
- }
-
- return in;
-}
-
+/** + * @file llsdserialize.cpp + * @author Phoenix + * @date 2006-03-05 + * @brief Implementation of LLSD parsers and formatters + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llsdserialize.h" +#include "llpointer.h" +#include "llstreamtools.h" // for fullread + +#include <iostream> +#include "apr_base64.h" + +#include <boost/iostreams/device/array.hpp> +#include <boost/iostreams/stream.hpp> + +#ifdef LL_USESYSTEMLIBS +# include <zlib.h> +#else +# include "zlib-ng/zlib.h" // for davep's dirty little zip functions +#endif + +#if !LL_WINDOWS +#include <netinet/in.h> // htonl & ntohl +#endif + +#include "lldate.h" +#include "llmemorystream.h" +#include "llsd.h" +#include "llstring.h" +#include "lluri.h" + +// File constants +static const size_t MAX_HDR_LEN = 20; +static const S32 UNZIP_LLSD_MAX_DEPTH = 96; +static const char LEGACY_NON_HEADER[] = "<llsd>"; +const std::string LLSD_BINARY_HEADER("LLSD/Binary"); +const std::string LLSD_XML_HEADER("LLSD/XML"); +const std::string LLSD_NOTATION_HEADER("llsd/notation"); + +//used to deflate a gzipped asset (currently used for navmeshes) +#define windowBits 15 +#define ENABLE_ZLIB_GZIP 32 + +// If we published this in llsdserialize.h, we could use it in the +// implementation of LLSDOStreamer's operator<<(). +template <class Formatter> +void format_using(const LLSD& data, std::ostream& ostr, + LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY) +{ + LLPointer<Formatter> f{ new Formatter }; + f->format(data, ostr, options); +} + +template <class Parser> +S32 parse_using(std::istream& istr, LLSD& data, size_t max_bytes, S32 max_depth=-1) +{ + LLPointer<Parser> p{ new Parser }; + return p->parse(istr, data, max_bytes, max_depth); +} + +/** + * LLSDSerialize + */ + +// static +void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type, + LLSDFormatter::EFormatterOptions options) +{ + LLPointer<LLSDFormatter> f = NULL; + + switch (type) + { + case LLSD_BINARY: + str << "<? " << LLSD_BINARY_HEADER << " ?>\n"; + f = new LLSDBinaryFormatter; + break; + + case LLSD_XML: + str << "<? " << LLSD_XML_HEADER << " ?>\n"; + f = new LLSDXMLFormatter; + break; + + case LLSD_NOTATION: + str << "<? " << LLSD_NOTATION_HEADER << " ?>\n"; + f = new LLSDNotationFormatter; + break; + + default: + LL_WARNS() << "serialize request for unknown ELLSD_Serialize" << LL_ENDL; + } + + if (f.notNull()) + { + f->format(sd, str, options); + } +} + +// static +bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str, llssize max_bytes) +{ + char hdr_buf[MAX_HDR_LEN + 1] = ""; /* Flawfinder: ignore */ + bool fail_if_not_legacy = false; + + /* + * Get the first line before anything. Don't read more than max_bytes: + * this get() overload reads no more than (count-1) bytes into the + * specified buffer. In the usual case when max_bytes exceeds + * sizeof(hdr_buf), get() will read no more than sizeof(hdr_buf)-2. + */ + llssize max_hdr_read = MAX_HDR_LEN; + if (max_bytes != LLSDSerialize::SIZE_UNLIMITED) + { + max_hdr_read = llmin(max_bytes + 1, max_hdr_read); + } + str.get(hdr_buf, max_hdr_read, '\n'); + auto inbuf = str.gcount(); + + // https://en.cppreference.com/w/cpp/io/basic_istream/get + // When the get() above sees the specified delimiter '\n', it stops there + // without pulling it from the stream. If it turns out that the stream + // does NOT contain a header, and the content includes meaningful '\n', + // it's important to pull that into hdr_buf too. + if (inbuf < max_bytes && str.get(hdr_buf[inbuf])) + { + // got the delimiting '\n' + ++inbuf; + // None of the following requires that hdr_buf contain a final '\0' + // byte. We could store one if needed, since even the incremented + // inbuf won't exceed sizeof(hdr_buf)-1, but there's no need. + } + std::string header{ hdr_buf, static_cast<std::string::size_type>(inbuf) }; + if (str.fail()) + { + str.clear(); + fail_if_not_legacy = true; + } + + if (!strncasecmp(LEGACY_NON_HEADER, hdr_buf, strlen(LEGACY_NON_HEADER))) /* Flawfinder: ignore */ + { // Create a LLSD XML parser, and parse the first chunk read above. + LLSDXMLParser x; + x.parsePart(hdr_buf, inbuf); // Parse the first part that was already read + auto parsed = x.parse(str, sd, max_bytes - inbuf); // Parse the rest of it + // Formally we should probably check (parsed != PARSE_FAILURE && + // parsed > 0), but since PARSE_FAILURE is -1, this suffices. + return (parsed > 0); + } + + if (fail_if_not_legacy) + { + LL_WARNS() << "deserialize LLSD parse failure" << LL_ENDL; + return false; + } + + /* + * Remove the newline chars + */ + std::string::size_type lastchar = header.find_last_not_of("\r\n"); + if (lastchar != std::string::npos) + { + // It's important that find_last_not_of() returns size_type, which is + // why lastchar explicitly declares the type above. erase(size_type) + // erases from that offset to the end of the string, whereas + // erase(iterator) erases only a single character. + header.erase(lastchar+1); + } + + // trim off the <? ... ?> header syntax + auto start = header.find_first_not_of("<? "); + if (start != std::string::npos) + { + auto end = header.find_first_of(" ?", start); + if (end != std::string::npos) + { + header = header.substr(start, end - start); + ws(str); + } + } + /* + * Create the parser as appropriate + */ + if (0 == LLStringUtil::compareInsensitive(header, LLSD_BINARY_HEADER)) + { + return (parse_using<LLSDBinaryParser>(str, sd, max_bytes-inbuf) > 0); + } + else if (0 == LLStringUtil::compareInsensitive(header, LLSD_XML_HEADER)) + { + return (parse_using<LLSDXMLParser>(str, sd, max_bytes-inbuf) > 0); + } + else if (0 == LLStringUtil::compareInsensitive(header, LLSD_NOTATION_HEADER)) + { + return (parse_using<LLSDNotationParser>(str, sd, max_bytes-inbuf) > 0); + } + else // no header we recognize + { + LLPointer<LLSDParser> p; + if (inbuf && hdr_buf[0] == '<') + { + // looks like XML + LL_DEBUGS() << "deserialize request with no header, assuming XML" << LL_ENDL; + p = new LLSDXMLParser; + } + else + { + // assume notation + LL_DEBUGS() << "deserialize request with no header, assuming notation" << LL_ENDL; + p = new LLSDNotationParser; + } + // Since we've already read 'inbuf' bytes into 'hdr_buf', prepend that + // data to whatever remains in 'str'. + LLMemoryStreamBuf already(reinterpret_cast<const U8*>(hdr_buf), inbuf); + cat_streambuf prebuff(&already, str.rdbuf()); + std::istream prepend(&prebuff); +#if 1 + return (p->parse(prepend, sd, max_bytes) > 0); +#else + // debugging the reconstituted 'prepend' stream + // allocate a buffer that we hope is big enough for the whole thing + std::vector<char> wholemsg((max_bytes == size_t(SIZE_UNLIMITED))? 1024 : max_bytes); + prepend.read(wholemsg.data(), std::min(max_bytes, wholemsg.size())); + LLMemoryStream replay(reinterpret_cast<const U8*>(wholemsg.data()), prepend.gcount()); + auto success{ p->parse(replay, sd, prepend.gcount()) > 0 }; + { + LL_DEBUGS() << (success? "parsed: $$" : "failed: '") + << std::string(wholemsg.data(), llmin(prepend.gcount(), 100)) << "$$" + << LL_ENDL; + } + return success; +#endif + } +} + +/** + * Endian handlers + */ +#if LL_BIG_ENDIAN +U64 ll_htonll(U64 hostlonglong) { return hostlonglong; } +U64 ll_ntohll(U64 netlonglong) { return netlonglong; } +F64 ll_htond(F64 hostlonglong) { return hostlonglong; } +F64 ll_ntohd(F64 netlonglong) { return netlonglong; } +#else +// I read some comments one a indicating that doing an integer add +// here would be faster than a bitwise or. For now, the or has +// programmer clarity, since the intended outcome matches the +// operation. +U64 ll_htonll(U64 hostlonglong) +{ + return ((U64)(htonl((U32)((hostlonglong >> 32) & 0xFFFFFFFF))) | + ((U64)(htonl((U32)(hostlonglong & 0xFFFFFFFF))) << 32)); +} +U64 ll_ntohll(U64 netlonglong) +{ + return ((U64)(ntohl((U32)((netlonglong >> 32) & 0xFFFFFFFF))) | + ((U64)(ntohl((U32)(netlonglong & 0xFFFFFFFF))) << 32)); +} +union LLEndianSwapper +{ + F64 d; + U64 i; +}; +F64 ll_htond(F64 hostdouble) +{ + LLEndianSwapper tmp; + tmp.d = hostdouble; + tmp.i = ll_htonll(tmp.i); + return tmp.d; +} +F64 ll_ntohd(F64 netdouble) +{ + LLEndianSwapper tmp; + tmp.d = netdouble; + tmp.i = ll_ntohll(tmp.i); + return tmp.d; +} +#endif + +/** + * Local functions. + */ +/** + * @brief Figure out what kind of string it is (raw or delimited) and handoff. + * + * @param istr The stream to read from. + * @param value [out] The string which was found. + * @param max_bytes The maximum possible length of the string. Passing in + * a negative value will skip this check. + * @return Returns number of bytes read off of the stream. Returns + * PARSE_FAILURE (-1) on failure. + */ +llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes); + +/** + * @brief Parse a delimited string. + * + * @param istr The stream to read from, with the delimiter already popped. + * @param value [out] The string which was found. + * @param d The delimiter to use. + * @return Returns number of bytes read off of the stream. Returns + * PARSE_FAILURE (-1) on failure. + */ +llssize deserialize_string_delim(std::istream& istr, std::string& value, char d); + +/** + * @brief Read a raw string off the stream. + * + * @param istr The stream to read from, with the (len) parameter + * leading the stream. + * @param value [out] The string which was found. + * @param d The delimiter to use. + * @param max_bytes The maximum possible length of the string. Passing in + * a negative value will skip this check. + * @return Returns number of bytes read off of the stream. Returns + * PARSE_FAILURE (-1) on failure. + */ +llssize deserialize_string_raw( + std::istream& istr, + std::string& value, + llssize max_bytes); + +/** + * @brief helper method for dealing with the different notation boolean format. + * + * @param istr The stream to read from with the leading character stripped. + * @param data [out] the result of the parse. + * @param compare The string to compare the boolean against + * @param vale The value to assign to data if the parse succeeds. + * @return Returns number of bytes read off of the stream. Returns + * PARSE_FAILURE (-1) on failure. + */ +llssize deserialize_boolean( + std::istream& istr, + LLSD& data, + const std::string& compare, + bool value); + +/** + * @brief Do notation escaping of a string to an ostream. + * + * @param value The string to escape and serialize + * @param str The stream to serialize to. + */ +void serialize_string(const std::string& value, std::ostream& str); + + +/** + * Local constants. + */ +static const std::string NOTATION_TRUE_SERIAL("true"); +static const std::string NOTATION_FALSE_SERIAL("false"); + +static const char BINARY_TRUE_SERIAL = '1'; +static const char BINARY_FALSE_SERIAL = '0'; + + +/** + * LLSDParser + */ +LLSDParser::LLSDParser() + : mCheckLimits(true), mMaxBytesLeft(0), mParseLines(false) +{ +} + +// virtual +LLSDParser::~LLSDParser() +{ } + +S32 LLSDParser::parse(std::istream& istr, LLSD& data, llssize max_bytes, S32 max_depth) +{ + mCheckLimits = LLSDSerialize::SIZE_UNLIMITED != max_bytes; + mMaxBytesLeft = max_bytes; + return doParse(istr, data, max_depth); +} + + +// Parse using routine to get() lines, faster than parse() +S32 LLSDParser::parseLines(std::istream& istr, LLSD& data) +{ + mCheckLimits = false; + mParseLines = true; + return doParse(istr, data); +} + + +int LLSDParser::get(std::istream& istr) const +{ + if(mCheckLimits) --mMaxBytesLeft; + return istr.get(); +} + +std::istream& LLSDParser::get( + std::istream& istr, + char* s, + std::streamsize n, + char delim) const +{ + istr.get(s, n, delim); + if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); + return istr; +} + +std::istream& LLSDParser::get( + std::istream& istr, + std::streambuf& sb, + char delim) const +{ + istr.get(sb, delim); + if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); + return istr; +} + +std::istream& LLSDParser::ignore(std::istream& istr) const +{ + istr.ignore(); + if(mCheckLimits) --mMaxBytesLeft; + return istr; +} + +std::istream& LLSDParser::putback(std::istream& istr, char c) const +{ + istr.putback(c); + if(mCheckLimits) ++mMaxBytesLeft; + return istr; +} + +std::istream& LLSDParser::read( + std::istream& istr, + char* s, + std::streamsize n) const +{ + istr.read(s, n); + if(mCheckLimits) mMaxBytesLeft -= istr.gcount(); + return istr; +} + +void LLSDParser::account(llssize bytes) const +{ + if(mCheckLimits) mMaxBytesLeft -= bytes; +} + + +/** + * LLSDNotationParser + */ +LLSDNotationParser::LLSDNotationParser() +{ +} + +// virtual +LLSDNotationParser::~LLSDNotationParser() +{ } + +// virtual +S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + // map: { string:object, string:object } + // array: [ object, object, object ] + // undef: ! + // boolean: true | false | 1 | 0 | T | F | t | f | TRUE | FALSE + // integer: i#### + // real: r#### + // uuid: u#### + // string: "g'day" | 'have a "nice" day' | s(size)"raw data" + // uri: l"escaped" + // date: d"YYYY-MM-DDTHH:MM:SS.FFZ" + // binary: b##"ff3120ab1" | b(size)"raw data" + char c; + c = istr.peek(); + if (max_depth == 0) + { + return PARSE_FAILURE; + } + while(isspace(c)) + { + // pop the whitespace. + c = get(istr); + c = istr.peek(); + continue; + } + if(!istr.good()) + { + return 0; + } + S32 parse_count = 1; + switch(c) + { + case '{': + { + S32 child_count = parseMap(istr, data, max_depth - 1); + if((child_count == PARSE_FAILURE) || data.isUndefined()) + { + parse_count = PARSE_FAILURE; + } + else + { + parse_count += child_count; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading map." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '[': + { + S32 child_count = parseArray(istr, data, max_depth - 1); + if((child_count == PARSE_FAILURE) || data.isUndefined()) + { + parse_count = PARSE_FAILURE; + } + else + { + parse_count += child_count; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading array." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '!': + c = get(istr); + data.clear(); + break; + + case '0': + c = get(istr); + data = false; + break; + + case 'F': + case 'f': + ignore(istr); + c = istr.peek(); + if(isalpha(c)) + { + auto cnt = deserialize_boolean( + istr, + data, + NOTATION_FALSE_SERIAL, + false); + if(PARSE_FAILURE == cnt) parse_count = cnt; + else account(cnt); + } + else + { + data = false; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + + case '1': + c = get(istr); + data = true; + break; + + case 'T': + case 't': + ignore(istr); + c = istr.peek(); + if(isalpha(c)) + { + auto cnt = deserialize_boolean(istr,data,NOTATION_TRUE_SERIAL,true); + if(PARSE_FAILURE == cnt) parse_count = cnt; + else account(cnt); + } + else + { + data = true; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading boolean." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + + case 'i': + { + c = get(istr); + S32 integer = 0; + istr >> integer; + data = integer; + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading integer." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'r': + { + c = get(istr); + F64 real = 0.0; + istr >> real; + data = real; + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading real." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'u': + { + c = get(istr); + LLUUID id; + istr >> id; + data = id; + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading uuid." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '\"': + case '\'': + case 's': + if(!parseString(istr, data)) + { + parse_count = PARSE_FAILURE; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading string." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + + case 'l': + { + c = get(istr); // pop the 'l' + c = get(istr); // pop the delimiter + std::string str; + auto cnt = deserialize_string_delim(istr, str, c); + if(PARSE_FAILURE == cnt) + { + parse_count = PARSE_FAILURE; + } + else + { + data = LLURI(str); + account(cnt); + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading link." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'd': + { + c = get(istr); // pop the 'd' + c = get(istr); // pop the delimiter + std::string str; + auto cnt = deserialize_string_delim(istr, str, c); + if(PARSE_FAILURE == cnt) + { + parse_count = PARSE_FAILURE; + } + else + { + data = LLDate(str); + account(cnt); + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading date." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'b': + if(!parseBinary(istr, data)) + { + parse_count = PARSE_FAILURE; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading data." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + + default: + parse_count = PARSE_FAILURE; + LL_INFOS() << "Unrecognized character while parsing: int(" << int(c) + << ")" << LL_ENDL; + break; + } + if(PARSE_FAILURE == parse_count) + { + data.clear(); + } + return parse_count; +} + +S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + // map: { string:object, string:object } + map = LLSD::emptyMap(); + S32 parse_count = 0; + char c = get(istr); + if(c == '{') + { + // eat commas, white + bool found_name = false; + std::string name; + c = get(istr); + while(c != '}' && istr.good()) + { + if(!found_name) + { + if((c == '\"') || (c == '\'') || (c == 's')) + { + putback(istr, c); + found_name = true; + auto count = deserialize_string(istr, name, mMaxBytesLeft); + if(PARSE_FAILURE == count) return PARSE_FAILURE; + account(count); + } + c = get(istr); + } + else + { + if(isspace(c) || (c == ':')) + { + c = get(istr); + continue; + } + putback(istr, c); + LLSD child; + S32 count = doParse(istr, child, max_depth); + if(count > 0) + { + // There must be a value for every key, thus + // child_count must be greater than 0. + parse_count += count; + map.insert(name, child); + } + else + { + return PARSE_FAILURE; + } + found_name = false; + c = get(istr); + } + } + if(c != '}') + { + map.clear(); + return PARSE_FAILURE; + } + } + return parse_count; +} + +S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + // array: [ object, object, object ] + array = LLSD::emptyArray(); + S32 parse_count = 0; + char c = get(istr); + if(c == '[') + { + // eat commas, white + c = get(istr); + while((c != ']') && istr.good()) + { + LLSD child; + if(isspace(c) || (c == ',')) + { + c = get(istr); + continue; + } + putback(istr, c); + S32 count = doParse(istr, child, max_depth); + if(PARSE_FAILURE == count) + { + return PARSE_FAILURE; + } + else + { + parse_count += count; + array.append(child); + } + c = get(istr); + } + if(c != ']') + { + return PARSE_FAILURE; + } + } + return parse_count; +} + +bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + std::string value; + auto count = deserialize_string(istr, value, mMaxBytesLeft); + if(PARSE_FAILURE == count) return false; + account(count); + data = value; + return true; +} + +bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + // binary: b##"ff3120ab1" + // or: b(len)"..." + + // I want to manually control those values here to make sure the + // parser doesn't break when someone changes a constant somewhere + // else. + const U32 BINARY_BUFFER_SIZE = 256; + const U32 STREAM_GET_COUNT = 255; + + // need to read the base out. + char buf[BINARY_BUFFER_SIZE]; /* Flawfinder: ignore */ + get(istr, buf, STREAM_GET_COUNT, '"'); + char c = get(istr); + if(c != '"') return false; + if(0 == strncmp("b(", buf, 2)) + { + // We probably have a valid raw binary stream. determine + // the size, and read it. + auto len = strtol(buf + 2, NULL, 0); + if(mCheckLimits && (len > mMaxBytesLeft)) return false; + std::vector<U8> value; + if(len) + { + value.resize(len); + account(fullread(istr, (char *)&value[0], len)); + } + c = get(istr); // strip off the trailing double-quote + data = value; + } + else if(0 == strncmp("b64", buf, 3)) + { + // *FIX: A bit inefficient, but works for now. To make the + // format better, I would need to add a hint into the + // serialization format that indicated how long it was. + std::stringstream coded_stream; + get(istr, *(coded_stream.rdbuf()), '\"'); + c = get(istr); + std::string encoded(coded_stream.str()); + S32 len = apr_base64_decode_len(encoded.c_str()); + std::vector<U8> value; + if(len) + { + value.resize(len); + len = apr_base64_decode_binary(&value[0], encoded.c_str()); + value.resize(len); + } + data = value; + } + else if(0 == strncmp("b16", buf, 3)) + { + // yay, base 16. We pop the next character which is either a + // double quote or base 16 data. If it's a double quote, we're + // done parsing. If it's not, put the data back, and read the + // stream until the next double quote. + char* read; /*Flawfinder: ignore*/ + U8 byte; + U8 byte_buffer[BINARY_BUFFER_SIZE]; + U8* write; + std::vector<U8> value; + c = get(istr); + while(c != '"') + { + putback(istr, c); + read = buf; + write = byte_buffer; + get(istr, buf, STREAM_GET_COUNT, '"'); + c = get(istr); + while(*read != '\0') /*Flawfinder: ignore*/ + { + byte = hex_as_nybble(*read++); + byte = byte << 4; + byte |= hex_as_nybble(*read++); + *write++ = byte; + } + // copy the data out of the byte buffer + value.insert(value.end(), byte_buffer, write); + } + data = value; + } + else + { + return false; + } + return true; +} + + +/** + * LLSDBinaryParser + */ +LLSDBinaryParser::LLSDBinaryParser() +{ +} + +// virtual +LLSDBinaryParser::~LLSDBinaryParser() +{ +} + +// virtual +S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD +/** + * Undefined: '!'<br> + * Boolean: '1' for true '0' for false<br> + * Integer: 'i' + 4 bytes network byte order<br> + * Real: 'r' + 8 bytes IEEE double<br> + * UUID: 'u' + 16 byte unsigned integer<br> + * String: 's' + 4 byte integer size + string<br> + * strings also secretly support the notation format + * Date: 'd' + 8 byte IEEE double for seconds since epoch<br> + * URI: 'l' + 4 byte integer size + string uri<br> + * Binary: 'b' + 4 byte integer size + binary data<br> + * Array: '[' + 4 byte integer size + all values + ']'<br> + * Map: '{' + 4 byte integer size every(key + value) + '}'<br> + * map keys are serialized as s + 4 byte integer size + string or in the + * notation format. + */ + char c; + c = get(istr); + if(!istr.good()) + { + return 0; + } + if (max_depth == 0) + { + return PARSE_FAILURE; + } + S32 parse_count = 1; + switch(c) + { + case '{': + { + S32 child_count = parseMap(istr, data, max_depth - 1); + if((child_count == PARSE_FAILURE) || data.isUndefined()) + { + parse_count = PARSE_FAILURE; + } + else + { + parse_count += child_count; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary map." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '[': + { + S32 child_count = parseArray(istr, data, max_depth - 1); + if((child_count == PARSE_FAILURE) || data.isUndefined()) + { + parse_count = PARSE_FAILURE; + } + else + { + parse_count += child_count; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary array." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case '!': + data.clear(); + break; + + case '0': + data = false; + break; + + case '1': + data = true; + break; + + case 'i': + { + U32 value_nbo = 0; + read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + data = (S32)ntohl(value_nbo); + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary integer." << LL_ENDL; + } + break; + } + + case 'r': + { + F64 real_nbo = 0.0; + read(istr, (char*)&real_nbo, sizeof(F64)); /*Flawfinder: ignore*/ + data = ll_ntohd(real_nbo); + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary real." << LL_ENDL; + } + break; + } + + case 'u': + { + LLUUID id; + read(istr, (char*)(&id.mData), UUID_BYTES); /*Flawfinder: ignore*/ + data = id; + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary uuid." << LL_ENDL; + } + break; + } + + case '\'': + case '"': + { + std::string value; + auto cnt = deserialize_string_delim(istr, value, c); + if(PARSE_FAILURE == cnt) + { + parse_count = PARSE_FAILURE; + } + else + { + data = value; + account(cnt); + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary (notation-style) string." + << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 's': + { + std::string value; + if(parseString(istr, value)) + { + data = value; + } + else + { + parse_count = PARSE_FAILURE; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary string." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'l': + { + std::string value; + if(parseString(istr, value)) + { + data = LLURI(value); + } + else + { + parse_count = PARSE_FAILURE; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary link." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'd': + { + F64 real = 0.0; + read(istr, (char*)&real, sizeof(F64)); /*Flawfinder: ignore*/ + data = LLDate(real); + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary date." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + case 'b': + { + // We probably have a valid raw binary stream. determine + // the size, and read it. + U32 size_nbo = 0; + read(istr, (char*)&size_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(size_nbo); + if(mCheckLimits && (size > mMaxBytesLeft)) + { + parse_count = PARSE_FAILURE; + } + else + { + std::vector<U8> value; + if(size > 0) + { + value.resize(size); + account(fullread(istr, (char*)&value[0], size)); + } + data = value; + } + if(istr.fail()) + { + LL_INFOS() << "STREAM FAILURE reading binary." << LL_ENDL; + parse_count = PARSE_FAILURE; + } + break; + } + + default: + parse_count = PARSE_FAILURE; + LL_INFOS() << "Unrecognized character while parsing: int(" << int(c) + << ")" << LL_ENDL; + break; + } + if(PARSE_FAILURE == parse_count) + { + data.clear(); + } + return parse_count; +} + +S32 LLSDBinaryParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const +{ + map = LLSD::emptyMap(); + U32 value_nbo = 0; + read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(value_nbo); + S32 parse_count = 0; + S32 count = 0; + char c = get(istr); + while(c != '}' && (count < size) && istr.good()) + { + std::string name; + switch(c) + { + case 'k': + if(!parseString(istr, name)) + { + return PARSE_FAILURE; + } + break; + case '\'': + case '"': + { + auto cnt = deserialize_string_delim(istr, name, c); + if(PARSE_FAILURE == cnt) return PARSE_FAILURE; + account(cnt); + break; + } + } + LLSD child; + S32 child_count = doParse(istr, child, max_depth); + if(child_count > 0) + { + // There must be a value for every key, thus child_count + // must be greater than 0. + parse_count += child_count; + map.insert(name, child); + } + else + { + return PARSE_FAILURE; + } + ++count; + c = get(istr); + } + if((c != '}') || (count < size)) + { + // Make sure it is correctly terminated and we parsed as many + // as were said to be there. + return PARSE_FAILURE; + } + return parse_count; +} + +S32 LLSDBinaryParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const +{ + array = LLSD::emptyArray(); + U32 value_nbo = 0; + read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(value_nbo); + + // *FIX: This would be a good place to reserve some space in the + // array... + + S32 parse_count = 0; + S32 count = 0; + char c = istr.peek(); + while((c != ']') && (count < size) && istr.good()) + { + LLSD child; + S32 child_count = doParse(istr, child, max_depth); + if(PARSE_FAILURE == child_count) + { + return PARSE_FAILURE; + } + if(child_count) + { + parse_count += child_count; + array.append(child); + } + ++count; + c = istr.peek(); + } + c = get(istr); + if((c != ']') || (count < size)) + { + // Make sure it is correctly terminated and we parsed as many + // as were said to be there. + return PARSE_FAILURE; + } + return parse_count; +} + +bool LLSDBinaryParser::parseString( + std::istream& istr, + std::string& value) const +{ + // *FIX: This is memory inefficient. + U32 value_nbo = 0; + read(istr, (char*)&value_nbo, sizeof(U32)); /*Flawfinder: ignore*/ + S32 size = (S32)ntohl(value_nbo); + if(mCheckLimits && (size > mMaxBytesLeft)) return false; + if(size < 0) return false; + std::vector<char> buf; + if(size) + { + buf.resize(size); + account(fullread(istr, &buf[0], size)); + value.assign(buf.begin(), buf.end()); + } + return true; +} + + +/** + * LLSDFormatter + */ +LLSDFormatter::LLSDFormatter(bool boolAlpha, const std::string& realFmt, EFormatterOptions options): + mOptions(options) +{ + boolalpha(boolAlpha); + realFormat(realFmt); +} + +// virtual +LLSDFormatter::~LLSDFormatter() +{ } + +void LLSDFormatter::boolalpha(bool alpha) +{ + mBoolAlpha = alpha; +} + +void LLSDFormatter::realFormat(const std::string& format) +{ + mRealFormat = format; +} + +S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr) const +{ + // pass options captured by constructor + return format(data, ostr, mOptions); +} + +S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const +{ + return format_impl(data, ostr, options, 0); +} + +void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const +{ + std::string buffer = llformat(mRealFormat.c_str(), real); + ostr << buffer; +} + +/** + * LLSDNotationFormatter + */ +LLSDNotationFormatter::LLSDNotationFormatter(bool boolAlpha, const std::string& realFormat, + EFormatterOptions options): + LLSDFormatter(boolAlpha, realFormat, options) +{ +} + +// virtual +LLSDNotationFormatter::~LLSDNotationFormatter() +{ } + +// static +std::string LLSDNotationFormatter::escapeString(const std::string& in) +{ + std::ostringstream ostr; + serialize_string(in, ostr); + return ostr.str(); +} + +S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, + EFormatterOptions options, U32 level) const +{ + S32 format_count = 1; + std::string pre; + std::string post; + + if (options & LLSDFormatter::OPTIONS_PRETTY) + { + for (U32 i = 0; i < level; i++) + { + pre += " "; + } + post = "\n"; + } + + switch(data.type()) + { + case LLSD::TypeMap: + { + if (0 != level) ostr << post << pre; + ostr << "{"; + std::string inner_pre; + if (options & LLSDFormatter::OPTIONS_PRETTY) + { + inner_pre = pre + " "; + } + + bool need_comma = false; + LLSD::map_const_iterator iter = data.beginMap(); + LLSD::map_const_iterator end = data.endMap(); + for(; iter != end; ++iter) + { + if(need_comma) ostr << ","; + need_comma = true; + ostr << post << inner_pre << '\''; + serialize_string((*iter).first, ostr); + ostr << "':"; + format_count += format_impl((*iter).second, ostr, options, level + 2); + } + ostr << post << pre << "}"; + break; + } + + case LLSD::TypeArray: + { + ostr << post << pre << "["; + bool need_comma = false; + LLSD::array_const_iterator iter = data.beginArray(); + LLSD::array_const_iterator end = data.endArray(); + for(; iter != end; ++iter) + { + if(need_comma) ostr << ","; + need_comma = true; + format_count += format_impl(*iter, ostr, options, level + 1); + } + ostr << "]"; + break; + } + + case LLSD::TypeUndefined: + ostr << "!"; + break; + + case LLSD::TypeBoolean: + if(mBoolAlpha || +#if( LL_WINDOWS || __GNUC__ > 2) + (ostr.flags() & std::ios::boolalpha) +#else + (ostr.flags() & 0x0100) +#endif + ) + { + ostr << (data.asBoolean() + ? NOTATION_TRUE_SERIAL : NOTATION_FALSE_SERIAL); + } + else + { + ostr << (data.asBoolean() ? 1 : 0); + } + break; + + case LLSD::TypeInteger: + ostr << "i" << data.asInteger(); + break; + + case LLSD::TypeReal: + ostr << "r"; + if(mRealFormat.empty()) + { + ostr << data.asReal(); + } + else + { + formatReal(data.asReal(), ostr); + } + break; + + case LLSD::TypeUUID: + ostr << "u" << data.asUUID(); + break; + + case LLSD::TypeString: + ostr << '\''; + serialize_string(data.asStringRef(), ostr); + ostr << '\''; + break; + + case LLSD::TypeDate: + ostr << "d\"" << data.asDate() << "\""; + break; + + case LLSD::TypeURI: + ostr << "l\""; + serialize_string(data.asString(), ostr); + ostr << "\""; + break; + + case LLSD::TypeBinary: + { + // *FIX: memory inefficient. + const std::vector<U8>& buffer = data.asBinary(); + if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY) + { + ostr << "b16\""; + if (! buffer.empty()) + { + std::ios_base::fmtflags old_flags = ostr.flags(); + ostr.setf( std::ios::hex, std::ios::basefield ); + // It shouldn't strictly matter whether the emitted hex digits + // are uppercase; LLSDNotationParser handles either; but as of + // 2020-05-13, Python's llbase.llsd requires uppercase hex. + ostr << std::uppercase; + auto oldfill(ostr.fill('0')); + auto oldwidth(ostr.width()); + for (size_t i = 0; i < buffer.size(); i++) + { + // have to restate setw() before every conversion + ostr << std::setw(2) << (int) buffer[i]; + } + ostr.width(oldwidth); + ostr.fill(oldfill); + ostr.flags(old_flags); + } + } + else // ! OPTIONS_PRETTY_BINARY + { + ostr << "b(" << buffer.size() << ")\""; + if (! buffer.empty()) + { + ostr.write((const char*)&buffer[0], buffer.size()); + } + } + ostr << "\""; + break; + } + + default: + // *NOTE: This should never happen. + ostr << "!"; + break; + } + return format_count; +} + +/** + * LLSDBinaryFormatter + */ +LLSDBinaryFormatter::LLSDBinaryFormatter(bool boolAlpha, const std::string& realFormat, + EFormatterOptions options): + LLSDFormatter(boolAlpha, realFormat, options) +{ +} + +// virtual +LLSDBinaryFormatter::~LLSDBinaryFormatter() +{ } + +// virtual +S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr, + EFormatterOptions options, U32 level) const +{ + S32 format_count = 1; + switch(data.type()) + { + case LLSD::TypeMap: + { + ostr.put('{'); + U32 size_nbo = htonl(data.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + LLSD::map_const_iterator iter = data.beginMap(); + LLSD::map_const_iterator end = data.endMap(); + for(; iter != end; ++iter) + { + ostr.put('k'); + formatString((*iter).first, ostr); + format_count += format_impl((*iter).second, ostr, options, level+1); + } + ostr.put('}'); + break; + } + + case LLSD::TypeArray: + { + ostr.put('['); + U32 size_nbo = htonl(data.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + LLSD::array_const_iterator iter = data.beginArray(); + LLSD::array_const_iterator end = data.endArray(); + for(; iter != end; ++iter) + { + format_count += format_impl(*iter, ostr, options, level+1); + } + ostr.put(']'); + break; + } + + case LLSD::TypeUndefined: + ostr.put('!'); + break; + + case LLSD::TypeBoolean: + if(data.asBoolean()) ostr.put(BINARY_TRUE_SERIAL); + else ostr.put(BINARY_FALSE_SERIAL); + break; + + case LLSD::TypeInteger: + { + ostr.put('i'); + U32 value_nbo = htonl(data.asInteger()); + ostr.write((const char*)(&value_nbo), sizeof(U32)); + break; + } + + case LLSD::TypeReal: + { + ostr.put('r'); + F64 value_nbo = ll_htond(data.asReal()); + ostr.write((const char*)(&value_nbo), sizeof(F64)); + break; + } + + case LLSD::TypeUUID: + { + ostr.put('u'); + LLUUID temp = data.asUUID(); + ostr.write((const char*)(&(temp.mData)), UUID_BYTES); + break; + } + + case LLSD::TypeString: + ostr.put('s'); + formatString(data.asStringRef(), ostr); + break; + + case LLSD::TypeDate: + { + ostr.put('d'); + F64 value = data.asReal(); + ostr.write((const char*)(&value), sizeof(F64)); + break; + } + + case LLSD::TypeURI: + ostr.put('l'); + formatString(data.asString(), ostr); + break; + + case LLSD::TypeBinary: + { + ostr.put('b'); + const std::vector<U8>& buffer = data.asBinary(); + U32 size_nbo = htonl(buffer.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + if(buffer.size()) ostr.write((const char*)&buffer[0], buffer.size()); + break; + } + + default: + // *NOTE: This should never happen. + ostr.put('!'); + break; + } + return format_count; +} + +void LLSDBinaryFormatter::formatString( + const std::string& string, + std::ostream& ostr) const +{ + U32 size_nbo = htonl(string.size()); + ostr.write((const char*)(&size_nbo), sizeof(U32)); + ostr.write(string.c_str(), string.size()); +} + +/** + * local functions + */ +llssize deserialize_string(std::istream& istr, std::string& value, llssize max_bytes) +{ + int c = istr.get(); + if(istr.fail()) + { + // No data in stream, bail out but mention the character we + // grabbed. + return LLSDParser::PARSE_FAILURE; + } + + llssize rv = LLSDParser::PARSE_FAILURE; + switch(c) + { + case '\'': + case '"': + rv = deserialize_string_delim(istr, value, c); + break; + case 's': + // technically, less than max_bytes, but this is just meant to + // catch egregious protocol errors. parse errors will be + // caught in the case of incorrect counts. + rv = deserialize_string_raw(istr, value, max_bytes); + break; + default: + break; + } + if(LLSDParser::PARSE_FAILURE == rv) return rv; + return rv + 1; // account for the character grabbed at the top. +} + +llssize deserialize_string_delim( + std::istream& istr, + std::string& value, + char delim) +{ + std::ostringstream write_buffer; + bool found_escape = false; + bool found_hex = false; + bool found_digit = false; + U8 byte = 0; + llssize count = 0; + + while (true) + { + int next_byte = istr.get(); + ++count; + + if(istr.fail()) + { + // If our stream is empty, break out + value = write_buffer.str(); + return LLSDParser::PARSE_FAILURE; + } + + char next_char = (char)next_byte; // Now that we know it's not EOF + + if(found_escape) + { + // next character(s) is a special sequence. + if(found_hex) + { + if(found_digit) + { + found_digit = false; + found_hex = false; + found_escape = false; + byte = byte << 4; + byte |= hex_as_nybble(next_char); + write_buffer << byte; + byte = 0; + } + else + { + // next character is the first nybble of + // + found_digit = true; + byte = hex_as_nybble(next_char); + } + } + else if(next_char == 'x') + { + found_hex = true; + } + else + { + switch(next_char) + { + case 'a': + write_buffer << '\a'; + break; + case 'b': + write_buffer << '\b'; + break; + case 'f': + write_buffer << '\f'; + break; + case 'n': + write_buffer << '\n'; + break; + case 'r': + write_buffer << '\r'; + break; + case 't': + write_buffer << '\t'; + break; + case 'v': + write_buffer << '\v'; + break; + default: + write_buffer << next_char; + break; + } + found_escape = false; + } + } + else if(next_char == '\\') + { + found_escape = true; + } + else if(next_char == delim) + { + break; + } + else + { + write_buffer << next_char; + } + } + + value = write_buffer.str(); + return count; +} + +llssize deserialize_string_raw( + std::istream& istr, + std::string& value, + llssize max_bytes) +{ + llssize count = 0; + const S32 BUF_LEN = 20; + char buf[BUF_LEN]; /* Flawfinder: ignore */ + istr.get(buf, BUF_LEN - 1, ')'); + count += istr.gcount(); + int c = istr.get(); + c = istr.get(); + count += 2; + if(((c == '"') || (c == '\'')) && (buf[0] == '(')) + { + // We probably have a valid raw string. determine + // the size, and read it. + // *FIX: This is memory inefficient. + auto len = strtol(buf + 1, NULL, 0); + if((max_bytes>0)&&(len>max_bytes)) return LLSDParser::PARSE_FAILURE; + std::vector<char> buf; + if(len) + { + buf.resize(len); + count += fullread(istr, (char *)&buf[0], len); + value.assign(buf.begin(), buf.end()); + } + c = istr.get(); + ++count; + if(!((c == '"') || (c == '\''))) + { + return LLSDParser::PARSE_FAILURE; + } + } + else + { + return LLSDParser::PARSE_FAILURE; + } + return count; +} + +static const char* NOTATION_STRING_CHARACTERS[256] = +{ + "\\x00", // 0 + "\\x01", // 1 + "\\x02", // 2 + "\\x03", // 3 + "\\x04", // 4 + "\\x05", // 5 + "\\x06", // 6 + "\\a", // 7 + "\\b", // 8 + "\\t", // 9 + "\\n", // 10 + "\\v", // 11 + "\\f", // 12 + "\\r", // 13 + "\\x0e", // 14 + "\\x0f", // 15 + "\\x10", // 16 + "\\x11", // 17 + "\\x12", // 18 + "\\x13", // 19 + "\\x14", // 20 + "\\x15", // 21 + "\\x16", // 22 + "\\x17", // 23 + "\\x18", // 24 + "\\x19", // 25 + "\\x1a", // 26 + "\\x1b", // 27 + "\\x1c", // 28 + "\\x1d", // 29 + "\\x1e", // 30 + "\\x1f", // 31 + " ", // 32 + "!", // 33 + "\"", // 34 + "#", // 35 + "$", // 36 + "%", // 37 + "&", // 38 + "\\'", // 39 + "(", // 40 + ")", // 41 + "*", // 42 + "+", // 43 + ",", // 44 + "-", // 45 + ".", // 46 + "/", // 47 + "0", // 48 + "1", // 49 + "2", // 50 + "3", // 51 + "4", // 52 + "5", // 53 + "6", // 54 + "7", // 55 + "8", // 56 + "9", // 57 + ":", // 58 + ";", // 59 + "<", // 60 + "=", // 61 + ">", // 62 + "?", // 63 + "@", // 64 + "A", // 65 + "B", // 66 + "C", // 67 + "D", // 68 + "E", // 69 + "F", // 70 + "G", // 71 + "H", // 72 + "I", // 73 + "J", // 74 + "K", // 75 + "L", // 76 + "M", // 77 + "N", // 78 + "O", // 79 + "P", // 80 + "Q", // 81 + "R", // 82 + "S", // 83 + "T", // 84 + "U", // 85 + "V", // 86 + "W", // 87 + "X", // 88 + "Y", // 89 + "Z", // 90 + "[", // 91 + "\\\\", // 92 + "]", // 93 + "^", // 94 + "_", // 95 + "`", // 96 + "a", // 97 + "b", // 98 + "c", // 99 + "d", // 100 + "e", // 101 + "f", // 102 + "g", // 103 + "h", // 104 + "i", // 105 + "j", // 106 + "k", // 107 + "l", // 108 + "m", // 109 + "n", // 110 + "o", // 111 + "p", // 112 + "q", // 113 + "r", // 114 + "s", // 115 + "t", // 116 + "u", // 117 + "v", // 118 + "w", // 119 + "x", // 120 + "y", // 121 + "z", // 122 + "{", // 123 + "|", // 124 + "}", // 125 + "~", // 126 + "\\x7f", // 127 + "\\x80", // 128 + "\\x81", // 129 + "\\x82", // 130 + "\\x83", // 131 + "\\x84", // 132 + "\\x85", // 133 + "\\x86", // 134 + "\\x87", // 135 + "\\x88", // 136 + "\\x89", // 137 + "\\x8a", // 138 + "\\x8b", // 139 + "\\x8c", // 140 + "\\x8d", // 141 + "\\x8e", // 142 + "\\x8f", // 143 + "\\x90", // 144 + "\\x91", // 145 + "\\x92", // 146 + "\\x93", // 147 + "\\x94", // 148 + "\\x95", // 149 + "\\x96", // 150 + "\\x97", // 151 + "\\x98", // 152 + "\\x99", // 153 + "\\x9a", // 154 + "\\x9b", // 155 + "\\x9c", // 156 + "\\x9d", // 157 + "\\x9e", // 158 + "\\x9f", // 159 + "\\xa0", // 160 + "\\xa1", // 161 + "\\xa2", // 162 + "\\xa3", // 163 + "\\xa4", // 164 + "\\xa5", // 165 + "\\xa6", // 166 + "\\xa7", // 167 + "\\xa8", // 168 + "\\xa9", // 169 + "\\xaa", // 170 + "\\xab", // 171 + "\\xac", // 172 + "\\xad", // 173 + "\\xae", // 174 + "\\xaf", // 175 + "\\xb0", // 176 + "\\xb1", // 177 + "\\xb2", // 178 + "\\xb3", // 179 + "\\xb4", // 180 + "\\xb5", // 181 + "\\xb6", // 182 + "\\xb7", // 183 + "\\xb8", // 184 + "\\xb9", // 185 + "\\xba", // 186 + "\\xbb", // 187 + "\\xbc", // 188 + "\\xbd", // 189 + "\\xbe", // 190 + "\\xbf", // 191 + "\\xc0", // 192 + "\\xc1", // 193 + "\\xc2", // 194 + "\\xc3", // 195 + "\\xc4", // 196 + "\\xc5", // 197 + "\\xc6", // 198 + "\\xc7", // 199 + "\\xc8", // 200 + "\\xc9", // 201 + "\\xca", // 202 + "\\xcb", // 203 + "\\xcc", // 204 + "\\xcd", // 205 + "\\xce", // 206 + "\\xcf", // 207 + "\\xd0", // 208 + "\\xd1", // 209 + "\\xd2", // 210 + "\\xd3", // 211 + "\\xd4", // 212 + "\\xd5", // 213 + "\\xd6", // 214 + "\\xd7", // 215 + "\\xd8", // 216 + "\\xd9", // 217 + "\\xda", // 218 + "\\xdb", // 219 + "\\xdc", // 220 + "\\xdd", // 221 + "\\xde", // 222 + "\\xdf", // 223 + "\\xe0", // 224 + "\\xe1", // 225 + "\\xe2", // 226 + "\\xe3", // 227 + "\\xe4", // 228 + "\\xe5", // 229 + "\\xe6", // 230 + "\\xe7", // 231 + "\\xe8", // 232 + "\\xe9", // 233 + "\\xea", // 234 + "\\xeb", // 235 + "\\xec", // 236 + "\\xed", // 237 + "\\xee", // 238 + "\\xef", // 239 + "\\xf0", // 240 + "\\xf1", // 241 + "\\xf2", // 242 + "\\xf3", // 243 + "\\xf4", // 244 + "\\xf5", // 245 + "\\xf6", // 246 + "\\xf7", // 247 + "\\xf8", // 248 + "\\xf9", // 249 + "\\xfa", // 250 + "\\xfb", // 251 + "\\xfc", // 252 + "\\xfd", // 253 + "\\xfe", // 254 + "\\xff" // 255 +}; + +void serialize_string(const std::string& value, std::ostream& str) +{ + std::string::const_iterator it = value.begin(); + std::string::const_iterator end = value.end(); + U8 c; + for(; it != end; ++it) + { + c = (U8)(*it); + str << NOTATION_STRING_CHARACTERS[c]; + } +} + +llssize deserialize_boolean( + std::istream& istr, + LLSD& data, + const std::string& compare, + bool value) +{ + // + // this method is a little goofy, because it gets the stream at + // the point where the t or f has already been + // consumed. Basically, parse for a patch to the string passed in + // starting at index 1. If it's a match: + // * assign data to value + // * return the number of bytes read + // otherwise: + // * set data to LLSD::null + // * return LLSDParser::PARSE_FAILURE (-1) + // + llssize bytes_read = 0; + std::string::size_type ii = 0; + char c = istr.peek(); + while((++ii < compare.size()) + && (tolower(c) == (int)compare[ii]) + && istr.good()) + { + istr.ignore(); + ++bytes_read; + c = istr.peek(); + } + if(compare.size() != ii) + { + data.clear(); + return LLSDParser::PARSE_FAILURE; + } + data = value; + return bytes_read; +} + +std::ostream& operator<<(std::ostream& s, const LLSD& llsd) +{ + s << LLSDNotationStreamer(llsd); + return s; +} + + +//dirty little zippers -- yell at davep if these are horrid + +//return a string containing gzipped bytes of binary serialized LLSD +// VERY inefficient -- creates several copies of LLSD block in memory +std::string zip_llsd(LLSD& data) +{ + std::stringstream llsd_strm; + + LLSDSerialize::toBinary(data, llsd_strm); + + const U32 CHUNK = 65536; + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + S32 ret = deflateInit(&strm, Z_BEST_COMPRESSION); + if (ret != Z_OK) + { + LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; + return std::string(); + } + + std::string source = llsd_strm.str(); + + U8 out[CHUNK]; + + strm.avail_in = narrow<size_t>(source.size()); + strm.next_in = (U8*) source.data(); + U8* output = NULL; + + U32 cur_size = 0; + + U32 have = 0; + + do + { + strm.avail_out = CHUNK; + strm.next_out = out; + + ret = deflate(&strm, Z_FINISH); + if (ret == Z_OK || ret == Z_STREAM_END) + { //copy result into output + if (strm.avail_out >= CHUNK) + { + deflateEnd(&strm); + if(output) + free(output); + LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; + return std::string(); + } + + have = CHUNK-strm.avail_out; + U8* new_output = (U8*) realloc(output, cur_size+have); + if (new_output == NULL) + { + LL_WARNS() << "Failed to compress LLSD block: can't reallocate memory, current size: " << cur_size << " bytes; requested " << cur_size + have << " bytes." << LL_ENDL; + deflateEnd(&strm); + if (output) + { + free(output); + } + return std::string(); + } + output = new_output; + memcpy(output+cur_size, out, have); + cur_size += have; + } + else + { + deflateEnd(&strm); + if(output) + free(output); + LL_WARNS() << "Failed to compress LLSD block." << LL_ENDL; + return std::string(); + } + } + while (ret == Z_OK); + + std::string::size_type size = cur_size; + + std::string result((char*) output, size); + deflateEnd(&strm); + if(output) + free(output); + + return result; +} + +//decompress a block of LLSD from provided istream +// not very efficient -- creats a copy of decompressed LLSD block in memory +// and deserializes from that copy using LLSDSerialize +LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, std::istream& is, S32 size) +{ + std::unique_ptr<U8[]> in = std::unique_ptr<U8[]>(new(std::nothrow) U8[size]); + if (!in) + { + return ZR_MEM_ERROR; + } + is.read((char*) in.get(), size); + + return unzip_llsd(data, in.get(), size); +} + +LLUZipHelper::EZipRresult LLUZipHelper::unzip_llsd(LLSD& data, const U8* in, S32 size) +{ + U8* result = NULL; + llssize cur_size = 0; + z_stream strm; + + constexpr U32 CHUNK = 1024 * 512; + + static thread_local std::unique_ptr<U8[]> out; + if (!out) + { + out = std::unique_ptr<U8[]>(new(std::nothrow) U8[CHUNK]); + } + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = size; + strm.next_in = const_cast<U8*>(in); + + S32 ret = inflateInit(&strm); + + do + { + strm.avail_out = CHUNK; + strm.next_out = out.get(); + ret = inflate(&strm, Z_NO_FLUSH); + switch (ret) + { + case Z_NEED_DICT: + case Z_DATA_ERROR: + { + inflateEnd(&strm); + free(result); + return ZR_DATA_ERROR; + } + case Z_STREAM_ERROR: + case Z_BUF_ERROR: + { + inflateEnd(&strm); + free(result); + return ZR_BUFFER_ERROR; + } + + case Z_MEM_ERROR: + { + inflateEnd(&strm); + free(result); + return ZR_MEM_ERROR; + } + } + + U32 have = CHUNK-strm.avail_out; + + U8* new_result = (U8*)realloc(result, cur_size + have); + if (new_result == NULL) + { + inflateEnd(&strm); + if (result) + { + free(result); + } + return ZR_MEM_ERROR; + } + result = new_result; + memcpy(result+cur_size, out.get(), have); + cur_size += have; + + } while (ret == Z_OK && ret != Z_STREAM_END); + + inflateEnd(&strm); + + if (ret != Z_STREAM_END) + { + free(result); + return ZR_DATA_ERROR; + } + + //result now points to the decompressed LLSD block + { + char* result_ptr = strip_deprecated_header((char*)result, cur_size); + + boost::iostreams::stream<boost::iostreams::array_source> istrm(result_ptr, cur_size); + + if (!LLSDSerialize::fromBinary(data, istrm, cur_size, UNZIP_LLSD_MAX_DEPTH)) + { + free(result); + return ZR_PARSE_ERROR; + } + } + + free(result); + return ZR_OK; +} +//This unzip function will only work with a gzip header and trailer - while the contents +//of the actual compressed data is the same for either format (gzip vs zlib ), the headers +//and trailers are different for the formats. +U8* unzip_llsdNavMesh( bool& valid, size_t& outsize, std::istream& is, S32 size ) +{ + if (size == 0) + { + LL_WARNS() << "No data to unzip." << LL_ENDL; + return NULL; + } + + U8* result = NULL; + U32 cur_size = 0; + z_stream strm; + + const U32 CHUNK = 0x4000; + + U8 *in = new(std::nothrow) U8[size]; + if (in == NULL) + { + LL_WARNS() << "Memory allocation failure." << LL_ENDL; + return NULL; + } + is.read((char*) in, size); + + U8 out[CHUNK]; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = size; + strm.next_in = in; + + + S32 ret = inflateInit2(&strm, windowBits | ENABLE_ZLIB_GZIP ); + do + { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR) + { + inflateEnd(&strm); + free(result); + delete [] in; + valid = false; + } + + switch (ret) + { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + free(result); + delete [] in; + valid = false; + break; + } + + U32 have = CHUNK-strm.avail_out; + + U8* new_result = (U8*) realloc(result, cur_size + have); + if (new_result == NULL) + { + LL_WARNS() << "Failed to unzip LLSD NavMesh block: can't reallocate memory, current size: " << cur_size + << " bytes; requested " << cur_size + have + << " bytes; total syze: ." << size << " bytes." + << LL_ENDL; + inflateEnd(&strm); + if (result) + { + free(result); + } + delete[] in; + valid = false; + return NULL; + } + result = new_result; + memcpy(result+cur_size, out, have); + cur_size += have; + + } while (ret == Z_OK); + + inflateEnd(&strm); + delete [] in; + + if (ret != Z_STREAM_END) + { + free(result); + valid = false; + return NULL; + } + + //result now points to the decompressed LLSD block + { + outsize= cur_size; + valid = true; + } + + return result; +} + +char* strip_deprecated_header(char* in, llssize& cur_size, llssize* header_size) +{ + const char* deprecated_header = "<? LLSD/Binary ?>"; + constexpr size_t deprecated_header_size = 17; + + if (cur_size > deprecated_header_size + && memcmp(in, deprecated_header, deprecated_header_size) == 0) + { + in = in + deprecated_header_size; + cur_size = cur_size - deprecated_header_size; + if (header_size) + { + *header_size = deprecated_header_size + 1; + } + } + + return in; +} + diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index 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 <sstream>
-
-#if LL_WINDOWS
-# define WIN32_LEAN_AND_MEAN
-# include <winsock2.h> // for htonl
-#elif LL_LINUX
-# include <netinet/in.h>
-#elif LL_DARWIN
-# include <arpa/inet.h>
-#endif
-
-#include "llsdserialize.h"
-#include "stringize.h"
-#include "is_approx_equal_fraction.h"
-
-#include <map>
-#include <set>
-#include <boost/range.hpp>
-
-// U32
-LLSD ll_sd_from_U32(const U32 val)
-{
- std::vector<U8> v;
- U32 net_order = htonl(val);
-
- v.resize(4);
- memcpy(&(v[0]), &net_order, 4); /* Flawfinder: ignore */
-
- return LLSD(v);
-}
-
-U32 ll_U32_from_sd(const LLSD& sd)
-{
- U32 ret;
- std::vector<U8> v = sd.asBinary();
- if (v.size() < 4)
- {
- return 0;
- }
- memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */
- ret = ntohl(ret);
- return ret;
-}
-
-//U64
-LLSD ll_sd_from_U64(const U64 val)
-{
- std::vector<U8> v;
- U32 high, low;
-
- high = (U32)(val >> 32);
- low = (U32)val;
- high = htonl(high);
- low = htonl(low);
-
- v.resize(8);
- memcpy(&(v[0]), &high, 4); /* Flawfinder: ignore */
- memcpy(&(v[4]), &low, 4); /* Flawfinder: ignore */
-
- return LLSD(v);
-}
-
-U64 ll_U64_from_sd(const LLSD& sd)
-{
- U32 high, low;
- std::vector<U8> v = sd.asBinary();
-
- if (v.size() < 8)
- {
- return 0;
- }
-
- memcpy(&high, &(v[0]), 4); /* Flawfinder: ignore */
- memcpy(&low, &(v[4]), 4); /* Flawfinder: ignore */
- high = ntohl(high);
- low = ntohl(low);
-
- return ((U64)high) << 32 | low;
-}
-
-// IP Address (stored in net order in a U32, so don't need swizzling)
-LLSD ll_sd_from_ipaddr(const U32 val)
-{
- std::vector<U8> v;
-
- v.resize(4);
- memcpy(&(v[0]), &val, 4); /* Flawfinder: ignore */
-
- return LLSD(v);
-}
-
-U32 ll_ipaddr_from_sd(const LLSD& sd)
-{
- U32 ret;
- std::vector<U8> v = sd.asBinary();
- if (v.size() < 4)
- {
- return 0;
- }
- memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */
- return ret;
-}
-
-// Converts an LLSD binary to an LLSD string
-LLSD ll_string_from_binary(const LLSD& sd)
-{
- std::vector<U8> value = sd.asBinary();
- std::string str;
- str.resize(value.size());
- memcpy(&str[0], &value[0], value.size());
- return str;
-}
-
-// Converts an LLSD string to an LLSD binary
-LLSD ll_binary_from_string(const LLSD& sd)
-{
- std::vector<U8> binary_value;
-
- std::string string_value = sd.asString();
- for (const U8 c : string_value)
- {
- binary_value.push_back(c);
- }
-
- binary_value.push_back('\0');
-
- return binary_value;
-}
-
-char* ll_print_sd(const LLSD& sd)
-{
- const U32 bufferSize = 10 * 1024;
- static char buffer[bufferSize];
- std::ostringstream stream;
- //stream.rdbuf()->pubsetbuf(buffer, bufferSize);
- stream << LLSDOStreamer<LLSDXMLFormatter>(sd);
- stream << std::ends;
- strncpy(buffer, stream.str().c_str(), bufferSize);
- buffer[bufferSize - 1] = '\0';
- return buffer;
-}
-
-char* ll_pretty_print_sd_ptr(const LLSD* sd)
-{
- if (sd)
- {
- return ll_pretty_print_sd(*sd);
- }
- return NULL;
-}
-
-char* ll_pretty_print_sd(const LLSD& sd)
-{
- const U32 bufferSize = 100 * 1024;
- static char buffer[bufferSize];
- std::ostringstream stream;
- //stream.rdbuf()->pubsetbuf(buffer, bufferSize);
- stream << LLSDOStreamer<LLSDXMLFormatter>(sd, LLSDFormatter::OPTIONS_PRETTY);
- stream << std::ends;
- strncpy(buffer, stream.str().c_str(), bufferSize);
- buffer[bufferSize - 1] = '\0';
- return buffer;
-}
-
-std::string ll_stream_notation_sd(const LLSD& sd)
-{
- std::ostringstream stream;
- stream << LLSDOStreamer<LLSDNotationFormatter>(sd);
- return stream.str();
-}
-
-
-//compares the structure of an LLSD to a template LLSD and stores the
-//"valid" values in a 3rd LLSD. Default values pulled from the template
-//if the tested LLSD does not contain the key/value pair.
-//Excess values in the test LLSD are ignored in the resultant_llsd.
-//If the llsd to test has a specific key to a map and the values
-//are not of the same type, false is returned or if the LLSDs are not
-//of the same value. Ordering of arrays matters
-//Otherwise, returns true
-bool compare_llsd_with_template(
- const LLSD& llsd_to_test,
- const LLSD& template_llsd,
- LLSD& resultant_llsd)
-{
- LL_PROFILE_ZONE_SCOPED
-
- if (
- llsd_to_test.isUndefined() &&
- template_llsd.isDefined() )
- {
- resultant_llsd = template_llsd;
- return true;
- }
- else if ( llsd_to_test.type() != template_llsd.type() )
- {
- resultant_llsd = LLSD();
- return false;
- }
-
- if ( llsd_to_test.isArray() )
- {
- //they are both arrays
- //we loop over all the items in the template
- //verifying that the to_test has a subset (in the same order)
- //any shortcoming in the testing_llsd are just taken
- //to be the rest of the template
- LLSD data;
- LLSD::array_const_iterator test_iter;
- LLSD::array_const_iterator template_iter;
-
- resultant_llsd = LLSD::emptyArray();
- test_iter = llsd_to_test.beginArray();
-
- for (
- template_iter = template_llsd.beginArray();
- (template_iter != template_llsd.endArray() &&
- test_iter != llsd_to_test.endArray());
- ++template_iter)
- {
- if ( !compare_llsd_with_template(
- *test_iter,
- *template_iter,
- data) )
- {
- resultant_llsd = LLSD();
- return false;
- }
- else
- {
- resultant_llsd.append(data);
- }
-
- ++test_iter;
- }
-
- //so either the test or the template ended
- //we do another loop now to the end of the template
- //grabbing the default values
- for (;
- template_iter != template_llsd.endArray();
- ++template_iter)
- {
- resultant_llsd.append(*template_iter);
- }
- }
- else if ( llsd_to_test.isMap() )
- {
- //now we loop over the keys of the two maps
- //any excess is taken from the template
- //excess is ignored in the test
- LLSD value;
- LLSD::map_const_iterator template_iter;
-
- resultant_llsd = LLSD::emptyMap();
- for (
- template_iter = template_llsd.beginMap();
- template_iter != template_llsd.endMap();
- ++template_iter)
- {
- if ( llsd_to_test.has(template_iter->first) )
- {
- //the test LLSD has the same key
- if ( !compare_llsd_with_template(
- llsd_to_test[template_iter->first],
- template_iter->second,
- value) )
- {
- resultant_llsd = LLSD();
- return false;
- }
- else
- {
- resultant_llsd[template_iter->first] = value;
- }
- }
- else
- {
- //test llsd doesn't have it...take the
- //template as default value
- resultant_llsd[template_iter->first] =
- template_iter->second;
- }
- }
- }
- else
- {
- //of same type...take the test llsd's value
- resultant_llsd = llsd_to_test;
- }
-
-
- return true;
-}
-
-// filter_llsd_with_template() is a direct clone (copy-n-paste) of
-// compare_llsd_with_template with the following differences:
-// (1) bool vs BOOL return types
-// (2) A map with the key value "*" is a special value and maps any key in the
-// test llsd that doesn't have an explicitly matching key in the template.
-// (3) The element of an array with exactly one element is taken as a template
-// for *all* the elements of the test array. If the template array is of
-// different size, compare_llsd_with_template() semantics apply.
-bool filter_llsd_with_template(
- const LLSD & llsd_to_test,
- const LLSD & template_llsd,
- LLSD & resultant_llsd)
-{
- LL_PROFILE_ZONE_SCOPED
-
- if (llsd_to_test.isUndefined() && template_llsd.isDefined())
- {
- resultant_llsd = template_llsd;
- return true;
- }
- else if (llsd_to_test.type() != template_llsd.type())
- {
- resultant_llsd = LLSD();
- return false;
- }
-
- if (llsd_to_test.isArray())
- {
- //they are both arrays
- //we loop over all the items in the template
- //verifying that the to_test has a subset (in the same order)
- //any shortcoming in the testing_llsd are just taken
- //to be the rest of the template
- LLSD data;
- LLSD::array_const_iterator test_iter;
- LLSD::array_const_iterator template_iter;
-
- resultant_llsd = LLSD::emptyArray();
- test_iter = llsd_to_test.beginArray();
-
- if (1 == template_llsd.size())
- {
- // If the template has a single item, treat it as
- // the template for *all* items in the test LLSD.
- template_iter = template_llsd.beginArray();
-
- for (; test_iter != llsd_to_test.endArray(); ++test_iter)
- {
- if (! filter_llsd_with_template(*test_iter, *template_iter, data))
- {
- resultant_llsd = LLSD();
- return false;
- }
- else
- {
- resultant_llsd.append(data);
- }
- }
- }
- else
- {
- // Traditional compare_llsd_with_template matching
-
- for (template_iter = template_llsd.beginArray();
- template_iter != template_llsd.endArray() &&
- test_iter != llsd_to_test.endArray();
- ++template_iter, ++test_iter)
- {
- if (! filter_llsd_with_template(*test_iter, *template_iter, data))
- {
- resultant_llsd = LLSD();
- return false;
- }
- else
- {
- resultant_llsd.append(data);
- }
- }
-
- //so either the test or the template ended
- //we do another loop now to the end of the template
- //grabbing the default values
- for (;
- template_iter != template_llsd.endArray();
- ++template_iter)
- {
- resultant_llsd.append(*template_iter);
- }
- }
- }
- else if (llsd_to_test.isMap())
- {
- resultant_llsd = LLSD::emptyMap();
-
- //now we loop over the keys of the two maps
- //any excess is taken from the template
- //excess is ignored in the test
-
- // Special tag for wildcarded LLSD map key templates
- const LLSD::String wildcard_tag("*");
-
- const bool template_has_wildcard = template_llsd.has(wildcard_tag);
- LLSD wildcard_value;
- LLSD value;
-
- const LLSD::map_const_iterator template_iter_end(template_llsd.endMap());
- for (LLSD::map_const_iterator template_iter(template_llsd.beginMap());
- template_iter_end != template_iter;
- ++template_iter)
- {
- if (wildcard_tag == template_iter->first)
- {
- wildcard_value = template_iter->second;
- }
- else if (llsd_to_test.has(template_iter->first))
- {
- //the test LLSD has the same key
- if (! filter_llsd_with_template(llsd_to_test[template_iter->first],
- template_iter->second,
- value))
- {
- resultant_llsd = LLSD();
- return false;
- }
- else
- {
- resultant_llsd[template_iter->first] = value;
- }
- }
- else if (! template_has_wildcard)
- {
- // test llsd doesn't have it...take the
- // template as default value
- resultant_llsd[template_iter->first] = template_iter->second;
- }
- }
- if (template_has_wildcard)
- {
- LLSD sub_value;
- LLSD::map_const_iterator test_iter;
-
- for (test_iter = llsd_to_test.beginMap();
- test_iter != llsd_to_test.endMap();
- ++test_iter)
- {
- if (resultant_llsd.has(test_iter->first))
- {
- // Final value has test key, assume more specific
- // template matched and we shouldn't modify it again.
- continue;
- }
- else if (! filter_llsd_with_template(test_iter->second,
- wildcard_value,
- sub_value))
- {
- // Test value doesn't match wildcarded template
- resultant_llsd = LLSD();
- return false;
- }
- else
- {
- // Test value matches template, add the actuals.
- resultant_llsd[test_iter->first] = sub_value;
- }
- }
- }
- }
- else
- {
- //of same type...take the test llsd's value
- resultant_llsd = llsd_to_test;
- }
-
- return true;
-}
-
-/*****************************************************************************
-* Helpers for llsd_matches()
-*****************************************************************************/
-// raw data used for LLSD::Type lookup
-struct Data
-{
- LLSD::Type type;
- const char* name;
-} typedata[] =
-{
-#define def(type) { LLSD::type, &#type[4] }
- def(TypeUndefined),
- def(TypeBoolean),
- def(TypeInteger),
- def(TypeReal),
- def(TypeString),
- def(TypeUUID),
- def(TypeDate),
- def(TypeURI),
- def(TypeBinary),
- def(TypeMap),
- def(TypeArray)
-#undef def
-};
-
-// LLSD::Type lookup class into which we load the above static data
-class TypeLookup
-{
- typedef std::map<LLSD::Type, std::string> MapType;
-
-public:
- TypeLookup()
- {
- LL_PROFILE_ZONE_SCOPED
-
- for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di)
- {
- mMap[di->type] = di->name;
- }
- }
-
- std::string lookup(LLSD::Type type) const
- {
- LL_PROFILE_ZONE_SCOPED
-
- MapType::const_iterator found = mMap.find(type);
- if (found != mMap.end())
- {
- return found->second;
- }
- return STRINGIZE("<unknown LLSD type " << type << ">");
- }
-
-private:
- MapType mMap;
-};
-
-// static instance of the lookup class
-static const TypeLookup sTypes;
-
-// describe a mismatch; phrasing may want tweaking
-const std::string op(" required instead of ");
-
-// llsd_matches() wants to identify specifically where in a complex prototype
-// structure the mismatch occurred. This entails passing a prefix string,
-// empty for the top-level call. If the prototype contains an array of maps,
-// and the mismatch occurs in the second map in a key 'foo', we want to
-// decorate the returned string with: "[1]['foo']: etc." On the other hand, we
-// want to omit the entire prefix -- including colon -- if the mismatch is at
-// top level. This helper accepts the (possibly empty) recursively-accumulated
-// prefix string, returning either empty or the original string with colon
-// appended.
-static std::string colon(const std::string& pfx)
-{
- if (pfx.empty())
- return pfx;
- return pfx + ": ";
-}
-
-// param type for match_types
-typedef std::vector<LLSD::Type> TypeVector;
-
-// The scalar cases in llsd_matches() use this helper. In most cases, we can
-// accept not only the exact type specified in the prototype, but also other
-// types convertible to the expected type. That implies looping over an array
-// of such types. If the actual type doesn't match any of them, we want to
-// provide a list of acceptable conversions as well as the exact type, e.g.:
-// "Integer (or Boolean, Real, String) required instead of UUID". Both the
-// implementation and the calling logic are simplified by separating out the
-// expected type from the convertible types.
-static std::string match_types(LLSD::Type expect, // prototype.type()
- const TypeVector& accept, // types convertible to that type
- LLSD::Type actual, // type we're checking
- const std::string& pfx) // as for llsd_matches
-{
- LL_PROFILE_ZONE_SCOPED
-
- // Trivial case: if the actual type is exactly what we expect, we're good.
- if (actual == expect)
- return "";
-
- // For the rest of the logic, build up a suitable error string as we go so
- // we only have to make a single pass over the list of acceptable types.
- // If we detect success along the way, we'll simply discard the partial
- // error string.
- std::ostringstream out;
- out << colon(pfx) << sTypes.lookup(expect);
-
- // If there are any convertible types, append that list.
- if (! accept.empty())
- {
- out << " (";
- const char* sep = "or ";
- for (TypeVector::const_iterator ai(accept.begin()), aend(accept.end());
- ai != aend; ++ai, sep = ", ")
- {
- // Don't forget to return success if we match any of those types...
- if (actual == *ai)
- return "";
- out << sep << sTypes.lookup(*ai);
- }
- out << ')';
- }
- // If we got this far, it's because 'actual' was not one of the acceptable
- // types, so we must return an error. 'out' already contains colon(pfx)
- // and the formatted list of acceptable types, so just append the mismatch
- // phrase and the actual type.
- out << op << sTypes.lookup(actual);
- return out.str();
-}
-
-// see docstring in .h file
-std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx)
-{
- LL_PROFILE_ZONE_SCOPED
-
- // An undefined prototype means that any data is valid.
- // An undefined slot in an array or map prototype means that any data
- // may fill that slot.
- if (prototype.isUndefined())
- return "";
- // A prototype array must match a data array with at least as many
- // entries. Moreover, every prototype entry must match the
- // corresponding data entry.
- if (prototype.isArray())
- {
- if (! data.isArray())
- {
- return STRINGIZE(colon(pfx) << "Array" << op << sTypes.lookup(data.type()));
- }
- if (data.size() < prototype.size())
- {
- return STRINGIZE(colon(pfx) << "Array size " << prototype.size() << op
- << "Array size " << data.size());
- }
- for (LLSD::Integer i = 0; i < prototype.size(); ++i)
- {
- std::string match(llsd_matches(prototype[i], data[i], STRINGIZE('[' << i << ']')));
- if (! match.empty())
- {
- return match;
- }
- }
- return "";
- }
- // A prototype map must match a data map. Every key in the prototype
- // must have a corresponding key in the data map; every value in the
- // prototype must match the corresponding key's value in the data.
- if (prototype.isMap())
- {
- if (! data.isMap())
- {
- return STRINGIZE(colon(pfx) << "Map" << op << sTypes.lookup(data.type()));
- }
- // If there are a number of keys missing from the data, it would be
- // frustrating to a coder to discover them one at a time, with a big
- // build each time. Enumerate all missing keys.
- std::ostringstream out;
- out << colon(pfx);
- const char* init = "Map missing keys: ";
- const char* sep = init;
- for (LLSD::map_const_iterator mi = prototype.beginMap(); mi != prototype.endMap(); ++mi)
- {
- if (! data.has(mi->first))
- {
- out << sep << mi->first;
- sep = ", ";
- }
- }
- // So... are we missing any keys?
- if (sep != init)
- {
- return out.str();
- }
- // Good, the data block contains all the keys required by the
- // prototype. Now match the prototype entries.
- for (LLSD::map_const_iterator mi2 = prototype.beginMap(); mi2 != prototype.endMap(); ++mi2)
- {
- std::string match(llsd_matches(mi2->second, data[mi2->first],
- STRINGIZE("['" << mi2->first << "']")));
- if (! match.empty())
- {
- return match;
- }
- }
- return "";
- }
- // A String prototype can match String, Boolean, Integer, Real, UUID,
- // Date and URI, because any of these can be converted to String.
- if (prototype.isString())
- {
- static LLSD::Type accept[] =
- {
- LLSD::TypeBoolean,
- LLSD::TypeInteger,
- LLSD::TypeReal,
- LLSD::TypeUUID,
- LLSD::TypeDate,
- LLSD::TypeURI
- };
- return match_types(prototype.type(),
- TypeVector(boost::begin(accept), boost::end(accept)),
- data.type(),
- pfx);
- }
- // Boolean, Integer, Real match each other or String. TBD: ensure that
- // a String value is numeric.
- if (prototype.isBoolean() || prototype.isInteger() || prototype.isReal())
- {
- static LLSD::Type all[] =
- {
- LLSD::TypeBoolean,
- LLSD::TypeInteger,
- LLSD::TypeReal,
- LLSD::TypeString
- };
- // Funny business: shuffle the set of acceptable types to include all
- // but the prototype's type. Get the acceptable types in a set.
- std::set<LLSD::Type> rest(boost::begin(all), boost::end(all));
- // Remove the prototype's type because we pass that separately.
- rest.erase(prototype.type());
- return match_types(prototype.type(),
- TypeVector(rest.begin(), rest.end()),
- data.type(),
- pfx);
- }
- // UUID, Date and URI match themselves or String.
- if (prototype.isUUID() || prototype.isDate() || prototype.isURI())
- {
- static LLSD::Type accept[] =
- {
- LLSD::TypeString
- };
- return match_types(prototype.type(),
- TypeVector(boost::begin(accept), boost::end(accept)),
- data.type(),
- pfx);
- }
- // We don't yet know the conversion semantics associated with any new LLSD
- // data type that might be added, so until we've been extended to handle
- // them, assume it's strict: the new type matches only itself. (This is
- // true of Binary, which is why we don't handle that case separately.) Too
- // bad LLSD doesn't define isConvertible(Type to, Type from).
- return match_types(prototype.type(), TypeVector(), data.type(), pfx);
-}
-
-bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits)
-{
- LL_PROFILE_ZONE_SCOPED
-
- // We're comparing strict equality of LLSD representation rather than
- // performing any conversions. So if the types aren't equal, the LLSD
- // values aren't equal.
- if (lhs.type() != rhs.type())
- {
- return false;
- }
-
- // Here we know both types are equal. Now compare values.
- switch (lhs.type())
- {
- case LLSD::TypeUndefined:
- // Both are TypeUndefined. There's nothing more to know.
- return true;
-
- case LLSD::TypeReal:
- // This is where the 'bits' argument comes in handy. If passed
- // explicitly, it means to use is_approx_equal_fraction() to compare.
- if (bits >= 0)
- {
- return is_approx_equal_fraction(lhs.asReal(), rhs.asReal(), bits);
- }
- // Otherwise we compare bit representations, and the usual caveats
- // about comparing floating-point numbers apply. Omitting 'bits' when
- // comparing Real values is only useful when we expect identical bit
- // representation for a given Real value, e.g. for integer-valued
- // Reals.
- return (lhs.asReal() == rhs.asReal());
-
-#define COMPARE_SCALAR(type) \
- case LLSD::Type##type: \
- /* LLSD::URI has operator!=() but not operator==() */ \
- /* rely on the optimizer for all others */ \
- return (! (lhs.as##type() != rhs.as##type()))
-
- COMPARE_SCALAR(Boolean);
- COMPARE_SCALAR(Integer);
- COMPARE_SCALAR(String);
- COMPARE_SCALAR(UUID);
- COMPARE_SCALAR(Date);
- COMPARE_SCALAR(URI);
- COMPARE_SCALAR(Binary);
-
-#undef COMPARE_SCALAR
-
- case LLSD::TypeArray:
- {
- LLSD::array_const_iterator
- lai(lhs.beginArray()), laend(lhs.endArray()),
- rai(rhs.beginArray()), raend(rhs.endArray());
- // Compare array elements, walking the two arrays in parallel.
- for ( ; lai != laend && rai != raend; ++lai, ++rai)
- {
- // If any one array element is unequal, the arrays are unequal.
- if (! llsd_equals(*lai, *rai, bits))
- return false;
- }
- // Here we've reached the end of one or the other array. They're equal
- // only if they're BOTH at end: that is, if they have equal length too.
- return (lai == laend && rai == raend);
- }
-
- case LLSD::TypeMap:
- {
- // Build a set of all rhs keys.
- std::set<LLSD::String> rhskeys;
- for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap());
- rmi != rmend; ++rmi)
- {
- rhskeys.insert(rmi->first);
- }
- // Now walk all the lhs keys.
- for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap());
- lmi != lmend; ++lmi)
- {
- // Try to erase this lhs key from the set of rhs keys. If rhs has
- // no such key, the maps are unequal. erase(key) returns count of
- // items erased.
- if (rhskeys.erase(lmi->first) != 1)
- return false;
- // Both maps have the current key. Compare values.
- if (! llsd_equals(lmi->second, rhs[lmi->first], bits))
- return false;
- }
- // We've now established that all the lhs keys have equal values in
- // both maps. The maps are equal unless rhs contains a superset of
- // those keys.
- return rhskeys.empty();
- }
-
- default:
- // We expect that every possible type() value is specifically handled
- // above. Failing to extend this switch to support a new LLSD type is
- // an error that must be brought to the coder's attention.
- LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << ", " << bits << "): "
- "unknown type " << lhs.type() << LL_ENDL;
- return false; // pacify the compiler
- }
-}
-
-/*****************************************************************************
-* llsd::drill()
-*****************************************************************************/
-namespace llsd
-{
-
-LLSD& drill_ref(LLSD& blob, const LLSD& rawPath)
-{
- LL_PROFILE_ZONE_SCOPED
-
- // Treat rawPath uniformly as an array. If it's not already an array,
- // store it as the only entry in one. (But let's say Undefined means an
- // empty array.)
- LLSD path;
- if (rawPath.isArray() || rawPath.isUndefined())
- {
- path = rawPath;
- }
- else
- {
- path.append(rawPath);
- }
-
- // Need to indicate a current destination -- but that current destination
- // must change as we step through the path array. Where normally we'd use
- // an LLSD& to capture a subscripted LLSD lvalue, this time we must
- // instead use a pointer -- since it must be reassigned.
- // Start by pointing to the input blob exactly as is.
- LLSD* located{&blob};
-
- // Extract the element of interest by walking path. Use an explicit index
- // so that, in case of a bogus type in path, we can identify the specific
- // path entry that's bad.
- for (LLSD::Integer i = 0; i < path.size(); ++i)
- {
- LL_PROFILE_ZONE_NUM( i )
-
- const LLSD& key{path[i]};
- if (key.isString())
- {
- // a string path element is a map key
- located = &((*located)[key.asString()]);
- }
- else if (key.isInteger())
- {
- // an integer path element is an array index
- located = &((*located)[key.asInteger()]);
- }
- else
- {
- // What do we do with Real or Array or Map or ...?
- // As it's a coder error -- not a user error -- rub the coder's
- // face in it so it gets fixed.
- LL_ERRS("llsdutil") << "drill(" << blob << ", " << rawPath
- << "): path[" << i << "] bad type "
- << sTypes.lookup(key.type()) << LL_ENDL;
- }
- }
-
- // dereference the pointer to return a reference to the element we found
- return *located;
-}
-
-LLSD drill(const LLSD& blob, const LLSD& path)
-{
- LL_PROFILE_ZONE_SCOPED
-
- // drill_ref() does exactly what we want. Temporarily cast away
- // const-ness and use that.
- return drill_ref(const_cast<LLSD&>(blob), path);
-}
-
-} // namespace llsd
-
-// Construct a deep partial clone of of an LLSD object. primitive types share
-// references, however maps, arrays and binary objects are duplicated. An optional
-// filter may be include to exclude/include keys in a map.
-LLSD llsd_clone(LLSD value, LLSD filter)
-{
- LL_PROFILE_ZONE_SCOPED
-
- LLSD clone;
- bool has_filter(filter.isMap());
-
- switch (value.type())
- {
- case LLSD::TypeMap:
- clone = LLSD::emptyMap();
- for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm)
- {
- if (has_filter)
- {
- if (filter.has((*itm).first))
- {
- if (!filter[(*itm).first].asBoolean())
- continue;
- }
- else if (filter.has("*"))
- {
- if (!filter["*"].asBoolean())
- continue;
- }
- else
- {
- continue;
- }
- }
- clone[(*itm).first] = llsd_clone((*itm).second, filter);
- }
- break;
- case LLSD::TypeArray:
- clone = LLSD::emptyArray();
- for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita)
- {
- clone.append(llsd_clone(*ita, filter));
- }
- break;
-
- case LLSD::TypeBinary:
- {
- LLSD::Binary bin(value.asBinary().begin(), value.asBinary().end());
- clone = LLSD::Binary(bin);
- break;
- }
- default:
- clone = value;
- }
-
- return clone;
-}
-
-LLSD llsd_shallow(LLSD value, LLSD filter)
-{
- LLSD shallow;
- bool has_filter(filter.isMap());
-
- if (value.isMap())
- {
- shallow = LLSD::emptyMap();
- for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm)
- {
- if (has_filter)
- {
- if (filter.has((*itm).first))
- {
- if (!filter[(*itm).first].asBoolean())
- continue;
- }
- else if (filter.has("*"))
- {
- if (!filter["*"].asBoolean())
- continue;
- }
- else
- {
- continue;
- }
- }
- shallow[(*itm).first] = (*itm).second;
- }
- }
- else if (value.isArray())
- {
- shallow = LLSD::emptyArray();
- for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita)
- {
- shallow.append(*ita);
- }
- }
- else
- {
- return value;
- }
-
- return shallow;
-}
-
-LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args)
-{
- // LLSD supports a number of types, two of which are aggregates: Map and
- // Array. We don't try to support Map: supporting Map would seem to
- // promise that we could somehow match the string key to 'func's parameter
- // names. Uh sorry, maybe in some future version of C++ with reflection.
- if (args.isMap())
- {
- LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported"));
- }
- // We expect an LLSD array, but what the heck, treat isUndefined() as a
- // zero-length array for calling a nullary 'func'.
- if (args.isUndefined() || args.isArray())
- {
- // this works because LLSD().size() == 0
- if (args.size() != arity)
- {
- LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ",
- args.size(), "-entry LLSD array)")));
- }
- return args;
- }
-
- // args is one of the scalar types
- // scalar_LLSD.size() == 0, so don't test that here.
- // You can pass a scalar LLSD only to a unary 'func'.
- if (arity != 1)
- {
- LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), "
- "LLSD ", LLSD::typeString(args.type()), ")")));
- }
- // make an array of it
- return llsd::array(args);
-}
+/** + * @file llsdutil.cpp + * @author Phoenix + * @date 2006-05-24 + * @brief Implementation of classes, functions, etc, for using structured data. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llsdutil.h" +#include <sstream> + +#if LL_WINDOWS +# define WIN32_LEAN_AND_MEAN +# include <winsock2.h> // for htonl +#elif LL_LINUX +# include <netinet/in.h> +#elif LL_DARWIN +# include <arpa/inet.h> +#endif + +#include "llsdserialize.h" +#include "stringize.h" +#include "is_approx_equal_fraction.h" + +#include <map> +#include <set> +#include <boost/range.hpp> + +// U32 +LLSD ll_sd_from_U32(const U32 val) +{ + std::vector<U8> v; + U32 net_order = htonl(val); + + v.resize(4); + memcpy(&(v[0]), &net_order, 4); /* Flawfinder: ignore */ + + return LLSD(v); +} + +U32 ll_U32_from_sd(const LLSD& sd) +{ + U32 ret; + std::vector<U8> v = sd.asBinary(); + if (v.size() < 4) + { + return 0; + } + memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */ + ret = ntohl(ret); + return ret; +} + +//U64 +LLSD ll_sd_from_U64(const U64 val) +{ + std::vector<U8> v; + U32 high, low; + + high = (U32)(val >> 32); + low = (U32)val; + high = htonl(high); + low = htonl(low); + + v.resize(8); + memcpy(&(v[0]), &high, 4); /* Flawfinder: ignore */ + memcpy(&(v[4]), &low, 4); /* Flawfinder: ignore */ + + return LLSD(v); +} + +U64 ll_U64_from_sd(const LLSD& sd) +{ + U32 high, low; + std::vector<U8> v = sd.asBinary(); + + if (v.size() < 8) + { + return 0; + } + + memcpy(&high, &(v[0]), 4); /* Flawfinder: ignore */ + memcpy(&low, &(v[4]), 4); /* Flawfinder: ignore */ + high = ntohl(high); + low = ntohl(low); + + return ((U64)high) << 32 | low; +} + +// IP Address (stored in net order in a U32, so don't need swizzling) +LLSD ll_sd_from_ipaddr(const U32 val) +{ + std::vector<U8> v; + + v.resize(4); + memcpy(&(v[0]), &val, 4); /* Flawfinder: ignore */ + + return LLSD(v); +} + +U32 ll_ipaddr_from_sd(const LLSD& sd) +{ + U32 ret; + std::vector<U8> v = sd.asBinary(); + if (v.size() < 4) + { + return 0; + } + memcpy(&ret, &(v[0]), 4); /* Flawfinder: ignore */ + return ret; +} + +// Converts an LLSD binary to an LLSD string +LLSD ll_string_from_binary(const LLSD& sd) +{ + std::vector<U8> value = sd.asBinary(); + std::string str; + str.resize(value.size()); + memcpy(&str[0], &value[0], value.size()); + return str; +} + +// Converts an LLSD string to an LLSD binary +LLSD ll_binary_from_string(const LLSD& sd) +{ + std::vector<U8> binary_value; + + std::string string_value = sd.asString(); + for (const U8 c : string_value) + { + binary_value.push_back(c); + } + + binary_value.push_back('\0'); + + return binary_value; +} + +char* ll_print_sd(const LLSD& sd) +{ + const U32 bufferSize = 10 * 1024; + static char buffer[bufferSize]; + std::ostringstream stream; + //stream.rdbuf()->pubsetbuf(buffer, bufferSize); + stream << LLSDOStreamer<LLSDXMLFormatter>(sd); + stream << std::ends; + strncpy(buffer, stream.str().c_str(), bufferSize); + buffer[bufferSize - 1] = '\0'; + return buffer; +} + +char* ll_pretty_print_sd_ptr(const LLSD* sd) +{ + if (sd) + { + return ll_pretty_print_sd(*sd); + } + return NULL; +} + +char* ll_pretty_print_sd(const LLSD& sd) +{ + const U32 bufferSize = 100 * 1024; + static char buffer[bufferSize]; + std::ostringstream stream; + //stream.rdbuf()->pubsetbuf(buffer, bufferSize); + stream << LLSDOStreamer<LLSDXMLFormatter>(sd, LLSDFormatter::OPTIONS_PRETTY); + stream << std::ends; + strncpy(buffer, stream.str().c_str(), bufferSize); + buffer[bufferSize - 1] = '\0'; + return buffer; +} + +std::string ll_stream_notation_sd(const LLSD& sd) +{ + std::ostringstream stream; + stream << LLSDOStreamer<LLSDNotationFormatter>(sd); + return stream.str(); +} + + +//compares the structure of an LLSD to a template LLSD and stores the +//"valid" values in a 3rd LLSD. Default values pulled from the template +//if the tested LLSD does not contain the key/value pair. +//Excess values in the test LLSD are ignored in the resultant_llsd. +//If the llsd to test has a specific key to a map and the values +//are not of the same type, false is returned or if the LLSDs are not +//of the same value. Ordering of arrays matters +//Otherwise, returns true +bool compare_llsd_with_template( + const LLSD& llsd_to_test, + const LLSD& template_llsd, + LLSD& resultant_llsd) +{ + LL_PROFILE_ZONE_SCOPED + + if ( + llsd_to_test.isUndefined() && + template_llsd.isDefined() ) + { + resultant_llsd = template_llsd; + return true; + } + else if ( llsd_to_test.type() != template_llsd.type() ) + { + resultant_llsd = LLSD(); + return false; + } + + if ( llsd_to_test.isArray() ) + { + //they are both arrays + //we loop over all the items in the template + //verifying that the to_test has a subset (in the same order) + //any shortcoming in the testing_llsd are just taken + //to be the rest of the template + LLSD data; + LLSD::array_const_iterator test_iter; + LLSD::array_const_iterator template_iter; + + resultant_llsd = LLSD::emptyArray(); + test_iter = llsd_to_test.beginArray(); + + for ( + template_iter = template_llsd.beginArray(); + (template_iter != template_llsd.endArray() && + test_iter != llsd_to_test.endArray()); + ++template_iter) + { + if ( !compare_llsd_with_template( + *test_iter, + *template_iter, + data) ) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd.append(data); + } + + ++test_iter; + } + + //so either the test or the template ended + //we do another loop now to the end of the template + //grabbing the default values + for (; + template_iter != template_llsd.endArray(); + ++template_iter) + { + resultant_llsd.append(*template_iter); + } + } + else if ( llsd_to_test.isMap() ) + { + //now we loop over the keys of the two maps + //any excess is taken from the template + //excess is ignored in the test + LLSD value; + LLSD::map_const_iterator template_iter; + + resultant_llsd = LLSD::emptyMap(); + for ( + template_iter = template_llsd.beginMap(); + template_iter != template_llsd.endMap(); + ++template_iter) + { + if ( llsd_to_test.has(template_iter->first) ) + { + //the test LLSD has the same key + if ( !compare_llsd_with_template( + llsd_to_test[template_iter->first], + template_iter->second, + value) ) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd[template_iter->first] = value; + } + } + else + { + //test llsd doesn't have it...take the + //template as default value + resultant_llsd[template_iter->first] = + template_iter->second; + } + } + } + else + { + //of same type...take the test llsd's value + resultant_llsd = llsd_to_test; + } + + + return true; +} + +// filter_llsd_with_template() is a direct clone (copy-n-paste) of +// compare_llsd_with_template with the following differences: +// (1) bool vs BOOL return types +// (2) A map with the key value "*" is a special value and maps any key in the +// test llsd that doesn't have an explicitly matching key in the template. +// (3) The element of an array with exactly one element is taken as a template +// for *all* the elements of the test array. If the template array is of +// different size, compare_llsd_with_template() semantics apply. +bool filter_llsd_with_template( + const LLSD & llsd_to_test, + const LLSD & template_llsd, + LLSD & resultant_llsd) +{ + LL_PROFILE_ZONE_SCOPED + + if (llsd_to_test.isUndefined() && template_llsd.isDefined()) + { + resultant_llsd = template_llsd; + return true; + } + else if (llsd_to_test.type() != template_llsd.type()) + { + resultant_llsd = LLSD(); + return false; + } + + if (llsd_to_test.isArray()) + { + //they are both arrays + //we loop over all the items in the template + //verifying that the to_test has a subset (in the same order) + //any shortcoming in the testing_llsd are just taken + //to be the rest of the template + LLSD data; + LLSD::array_const_iterator test_iter; + LLSD::array_const_iterator template_iter; + + resultant_llsd = LLSD::emptyArray(); + test_iter = llsd_to_test.beginArray(); + + if (1 == template_llsd.size()) + { + // If the template has a single item, treat it as + // the template for *all* items in the test LLSD. + template_iter = template_llsd.beginArray(); + + for (; test_iter != llsd_to_test.endArray(); ++test_iter) + { + if (! filter_llsd_with_template(*test_iter, *template_iter, data)) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd.append(data); + } + } + } + else + { + // Traditional compare_llsd_with_template matching + + for (template_iter = template_llsd.beginArray(); + template_iter != template_llsd.endArray() && + test_iter != llsd_to_test.endArray(); + ++template_iter, ++test_iter) + { + if (! filter_llsd_with_template(*test_iter, *template_iter, data)) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd.append(data); + } + } + + //so either the test or the template ended + //we do another loop now to the end of the template + //grabbing the default values + for (; + template_iter != template_llsd.endArray(); + ++template_iter) + { + resultant_llsd.append(*template_iter); + } + } + } + else if (llsd_to_test.isMap()) + { + resultant_llsd = LLSD::emptyMap(); + + //now we loop over the keys of the two maps + //any excess is taken from the template + //excess is ignored in the test + + // Special tag for wildcarded LLSD map key templates + const LLSD::String wildcard_tag("*"); + + const bool template_has_wildcard = template_llsd.has(wildcard_tag); + LLSD wildcard_value; + LLSD value; + + const LLSD::map_const_iterator template_iter_end(template_llsd.endMap()); + for (LLSD::map_const_iterator template_iter(template_llsd.beginMap()); + template_iter_end != template_iter; + ++template_iter) + { + if (wildcard_tag == template_iter->first) + { + wildcard_value = template_iter->second; + } + else if (llsd_to_test.has(template_iter->first)) + { + //the test LLSD has the same key + if (! filter_llsd_with_template(llsd_to_test[template_iter->first], + template_iter->second, + value)) + { + resultant_llsd = LLSD(); + return false; + } + else + { + resultant_llsd[template_iter->first] = value; + } + } + else if (! template_has_wildcard) + { + // test llsd doesn't have it...take the + // template as default value + resultant_llsd[template_iter->first] = template_iter->second; + } + } + if (template_has_wildcard) + { + LLSD sub_value; + LLSD::map_const_iterator test_iter; + + for (test_iter = llsd_to_test.beginMap(); + test_iter != llsd_to_test.endMap(); + ++test_iter) + { + if (resultant_llsd.has(test_iter->first)) + { + // Final value has test key, assume more specific + // template matched and we shouldn't modify it again. + continue; + } + else if (! filter_llsd_with_template(test_iter->second, + wildcard_value, + sub_value)) + { + // Test value doesn't match wildcarded template + resultant_llsd = LLSD(); + return false; + } + else + { + // Test value matches template, add the actuals. + resultant_llsd[test_iter->first] = sub_value; + } + } + } + } + else + { + //of same type...take the test llsd's value + resultant_llsd = llsd_to_test; + } + + return true; +} + +/***************************************************************************** +* Helpers for llsd_matches() +*****************************************************************************/ +// raw data used for LLSD::Type lookup +struct Data +{ + LLSD::Type type; + const char* name; +} typedata[] = +{ +#define def(type) { LLSD::type, &#type[4] } + def(TypeUndefined), + def(TypeBoolean), + def(TypeInteger), + def(TypeReal), + def(TypeString), + def(TypeUUID), + def(TypeDate), + def(TypeURI), + def(TypeBinary), + def(TypeMap), + def(TypeArray) +#undef def +}; + +// LLSD::Type lookup class into which we load the above static data +class TypeLookup +{ + typedef std::map<LLSD::Type, std::string> MapType; + +public: + TypeLookup() + { + LL_PROFILE_ZONE_SCOPED + + for (const Data *di(boost::begin(typedata)), *dend(boost::end(typedata)); di != dend; ++di) + { + mMap[di->type] = di->name; + } + } + + std::string lookup(LLSD::Type type) const + { + LL_PROFILE_ZONE_SCOPED + + MapType::const_iterator found = mMap.find(type); + if (found != mMap.end()) + { + return found->second; + } + return STRINGIZE("<unknown LLSD type " << type << ">"); + } + +private: + MapType mMap; +}; + +// static instance of the lookup class +static const TypeLookup sTypes; + +// describe a mismatch; phrasing may want tweaking +const std::string op(" required instead of "); + +// llsd_matches() wants to identify specifically where in a complex prototype +// structure the mismatch occurred. This entails passing a prefix string, +// empty for the top-level call. If the prototype contains an array of maps, +// and the mismatch occurs in the second map in a key 'foo', we want to +// decorate the returned string with: "[1]['foo']: etc." On the other hand, we +// want to omit the entire prefix -- including colon -- if the mismatch is at +// top level. This helper accepts the (possibly empty) recursively-accumulated +// prefix string, returning either empty or the original string with colon +// appended. +static std::string colon(const std::string& pfx) +{ + if (pfx.empty()) + return pfx; + return pfx + ": "; +} + +// param type for match_types +typedef std::vector<LLSD::Type> TypeVector; + +// The scalar cases in llsd_matches() use this helper. In most cases, we can +// accept not only the exact type specified in the prototype, but also other +// types convertible to the expected type. That implies looping over an array +// of such types. If the actual type doesn't match any of them, we want to +// provide a list of acceptable conversions as well as the exact type, e.g.: +// "Integer (or Boolean, Real, String) required instead of UUID". Both the +// implementation and the calling logic are simplified by separating out the +// expected type from the convertible types. +static std::string match_types(LLSD::Type expect, // prototype.type() + const TypeVector& accept, // types convertible to that type + LLSD::Type actual, // type we're checking + const std::string& pfx) // as for llsd_matches +{ + LL_PROFILE_ZONE_SCOPED + + // Trivial case: if the actual type is exactly what we expect, we're good. + if (actual == expect) + return ""; + + // For the rest of the logic, build up a suitable error string as we go so + // we only have to make a single pass over the list of acceptable types. + // If we detect success along the way, we'll simply discard the partial + // error string. + std::ostringstream out; + out << colon(pfx) << sTypes.lookup(expect); + + // If there are any convertible types, append that list. + if (! accept.empty()) + { + out << " ("; + const char* sep = "or "; + for (TypeVector::const_iterator ai(accept.begin()), aend(accept.end()); + ai != aend; ++ai, sep = ", ") + { + // Don't forget to return success if we match any of those types... + if (actual == *ai) + return ""; + out << sep << sTypes.lookup(*ai); + } + out << ')'; + } + // If we got this far, it's because 'actual' was not one of the acceptable + // types, so we must return an error. 'out' already contains colon(pfx) + // and the formatted list of acceptable types, so just append the mismatch + // phrase and the actual type. + out << op << sTypes.lookup(actual); + return out.str(); +} + +// see docstring in .h file +std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx) +{ + LL_PROFILE_ZONE_SCOPED + + // An undefined prototype means that any data is valid. + // An undefined slot in an array or map prototype means that any data + // may fill that slot. + if (prototype.isUndefined()) + return ""; + // A prototype array must match a data array with at least as many + // entries. Moreover, every prototype entry must match the + // corresponding data entry. + if (prototype.isArray()) + { + if (! data.isArray()) + { + return STRINGIZE(colon(pfx) << "Array" << op << sTypes.lookup(data.type())); + } + if (data.size() < prototype.size()) + { + return STRINGIZE(colon(pfx) << "Array size " << prototype.size() << op + << "Array size " << data.size()); + } + for (LLSD::Integer i = 0; i < prototype.size(); ++i) + { + std::string match(llsd_matches(prototype[i], data[i], STRINGIZE('[' << i << ']'))); + if (! match.empty()) + { + return match; + } + } + return ""; + } + // A prototype map must match a data map. Every key in the prototype + // must have a corresponding key in the data map; every value in the + // prototype must match the corresponding key's value in the data. + if (prototype.isMap()) + { + if (! data.isMap()) + { + return STRINGIZE(colon(pfx) << "Map" << op << sTypes.lookup(data.type())); + } + // If there are a number of keys missing from the data, it would be + // frustrating to a coder to discover them one at a time, with a big + // build each time. Enumerate all missing keys. + std::ostringstream out; + out << colon(pfx); + const char* init = "Map missing keys: "; + const char* sep = init; + for (LLSD::map_const_iterator mi = prototype.beginMap(); mi != prototype.endMap(); ++mi) + { + if (! data.has(mi->first)) + { + out << sep << mi->first; + sep = ", "; + } + } + // So... are we missing any keys? + if (sep != init) + { + return out.str(); + } + // Good, the data block contains all the keys required by the + // prototype. Now match the prototype entries. + for (LLSD::map_const_iterator mi2 = prototype.beginMap(); mi2 != prototype.endMap(); ++mi2) + { + std::string match(llsd_matches(mi2->second, data[mi2->first], + STRINGIZE("['" << mi2->first << "']"))); + if (! match.empty()) + { + return match; + } + } + return ""; + } + // A String prototype can match String, Boolean, Integer, Real, UUID, + // Date and URI, because any of these can be converted to String. + if (prototype.isString()) + { + static LLSD::Type accept[] = + { + LLSD::TypeBoolean, + LLSD::TypeInteger, + LLSD::TypeReal, + LLSD::TypeUUID, + LLSD::TypeDate, + LLSD::TypeURI + }; + return match_types(prototype.type(), + TypeVector(boost::begin(accept), boost::end(accept)), + data.type(), + pfx); + } + // Boolean, Integer, Real match each other or String. TBD: ensure that + // a String value is numeric. + if (prototype.isBoolean() || prototype.isInteger() || prototype.isReal()) + { + static LLSD::Type all[] = + { + LLSD::TypeBoolean, + LLSD::TypeInteger, + LLSD::TypeReal, + LLSD::TypeString + }; + // Funny business: shuffle the set of acceptable types to include all + // but the prototype's type. Get the acceptable types in a set. + std::set<LLSD::Type> rest(boost::begin(all), boost::end(all)); + // Remove the prototype's type because we pass that separately. + rest.erase(prototype.type()); + return match_types(prototype.type(), + TypeVector(rest.begin(), rest.end()), + data.type(), + pfx); + } + // UUID, Date and URI match themselves or String. + if (prototype.isUUID() || prototype.isDate() || prototype.isURI()) + { + static LLSD::Type accept[] = + { + LLSD::TypeString + }; + return match_types(prototype.type(), + TypeVector(boost::begin(accept), boost::end(accept)), + data.type(), + pfx); + } + // We don't yet know the conversion semantics associated with any new LLSD + // data type that might be added, so until we've been extended to handle + // them, assume it's strict: the new type matches only itself. (This is + // true of Binary, which is why we don't handle that case separately.) Too + // bad LLSD doesn't define isConvertible(Type to, Type from). + return match_types(prototype.type(), TypeVector(), data.type(), pfx); +} + +bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits) +{ + LL_PROFILE_ZONE_SCOPED + + // We're comparing strict equality of LLSD representation rather than + // performing any conversions. So if the types aren't equal, the LLSD + // values aren't equal. + if (lhs.type() != rhs.type()) + { + return false; + } + + // Here we know both types are equal. Now compare values. + switch (lhs.type()) + { + case LLSD::TypeUndefined: + // Both are TypeUndefined. There's nothing more to know. + return true; + + case LLSD::TypeReal: + // This is where the 'bits' argument comes in handy. If passed + // explicitly, it means to use is_approx_equal_fraction() to compare. + if (bits >= 0) + { + return is_approx_equal_fraction(lhs.asReal(), rhs.asReal(), bits); + } + // Otherwise we compare bit representations, and the usual caveats + // about comparing floating-point numbers apply. Omitting 'bits' when + // comparing Real values is only useful when we expect identical bit + // representation for a given Real value, e.g. for integer-valued + // Reals. + return (lhs.asReal() == rhs.asReal()); + +#define COMPARE_SCALAR(type) \ + case LLSD::Type##type: \ + /* LLSD::URI has operator!=() but not operator==() */ \ + /* rely on the optimizer for all others */ \ + return (! (lhs.as##type() != rhs.as##type())) + + COMPARE_SCALAR(Boolean); + COMPARE_SCALAR(Integer); + COMPARE_SCALAR(String); + COMPARE_SCALAR(UUID); + COMPARE_SCALAR(Date); + COMPARE_SCALAR(URI); + COMPARE_SCALAR(Binary); + +#undef COMPARE_SCALAR + + case LLSD::TypeArray: + { + LLSD::array_const_iterator + lai(lhs.beginArray()), laend(lhs.endArray()), + rai(rhs.beginArray()), raend(rhs.endArray()); + // Compare array elements, walking the two arrays in parallel. + for ( ; lai != laend && rai != raend; ++lai, ++rai) + { + // If any one array element is unequal, the arrays are unequal. + if (! llsd_equals(*lai, *rai, bits)) + return false; + } + // Here we've reached the end of one or the other array. They're equal + // only if they're BOTH at end: that is, if they have equal length too. + return (lai == laend && rai == raend); + } + + case LLSD::TypeMap: + { + // Build a set of all rhs keys. + std::set<LLSD::String> rhskeys; + for (LLSD::map_const_iterator rmi(rhs.beginMap()), rmend(rhs.endMap()); + rmi != rmend; ++rmi) + { + rhskeys.insert(rmi->first); + } + // Now walk all the lhs keys. + for (LLSD::map_const_iterator lmi(lhs.beginMap()), lmend(lhs.endMap()); + lmi != lmend; ++lmi) + { + // Try to erase this lhs key from the set of rhs keys. If rhs has + // no such key, the maps are unequal. erase(key) returns count of + // items erased. + if (rhskeys.erase(lmi->first) != 1) + return false; + // Both maps have the current key. Compare values. + if (! llsd_equals(lmi->second, rhs[lmi->first], bits)) + return false; + } + // We've now established that all the lhs keys have equal values in + // both maps. The maps are equal unless rhs contains a superset of + // those keys. + return rhskeys.empty(); + } + + default: + // We expect that every possible type() value is specifically handled + // above. Failing to extend this switch to support a new LLSD type is + // an error that must be brought to the coder's attention. + LL_ERRS("llsd_equals") << "llsd_equals(" << lhs << ", " << rhs << ", " << bits << "): " + "unknown type " << lhs.type() << LL_ENDL; + return false; // pacify the compiler + } +} + +/***************************************************************************** +* llsd::drill() +*****************************************************************************/ +namespace llsd +{ + +LLSD& drill_ref(LLSD& blob, const LLSD& rawPath) +{ + LL_PROFILE_ZONE_SCOPED + + // Treat rawPath uniformly as an array. If it's not already an array, + // store it as the only entry in one. (But let's say Undefined means an + // empty array.) + LLSD path; + if (rawPath.isArray() || rawPath.isUndefined()) + { + path = rawPath; + } + else + { + path.append(rawPath); + } + + // Need to indicate a current destination -- but that current destination + // must change as we step through the path array. Where normally we'd use + // an LLSD& to capture a subscripted LLSD lvalue, this time we must + // instead use a pointer -- since it must be reassigned. + // Start by pointing to the input blob exactly as is. + LLSD* located{&blob}; + + // Extract the element of interest by walking path. Use an explicit index + // so that, in case of a bogus type in path, we can identify the specific + // path entry that's bad. + for (LLSD::Integer i = 0; i < path.size(); ++i) + { + LL_PROFILE_ZONE_NUM( i ) + + const LLSD& key{path[i]}; + if (key.isString()) + { + // a string path element is a map key + located = &((*located)[key.asString()]); + } + else if (key.isInteger()) + { + // an integer path element is an array index + located = &((*located)[key.asInteger()]); + } + else + { + // What do we do with Real or Array or Map or ...? + // As it's a coder error -- not a user error -- rub the coder's + // face in it so it gets fixed. + LL_ERRS("llsdutil") << "drill(" << blob << ", " << rawPath + << "): path[" << i << "] bad type " + << sTypes.lookup(key.type()) << LL_ENDL; + } + } + + // dereference the pointer to return a reference to the element we found + return *located; +} + +LLSD drill(const LLSD& blob, const LLSD& path) +{ + LL_PROFILE_ZONE_SCOPED + + // drill_ref() does exactly what we want. Temporarily cast away + // const-ness and use that. + return drill_ref(const_cast<LLSD&>(blob), path); +} + +} // namespace llsd + +// Construct a deep partial clone of of an LLSD object. primitive types share +// references, however maps, arrays and binary objects are duplicated. An optional +// filter may be include to exclude/include keys in a map. +LLSD llsd_clone(LLSD value, LLSD filter) +{ + LL_PROFILE_ZONE_SCOPED + + LLSD clone; + bool has_filter(filter.isMap()); + + switch (value.type()) + { + case LLSD::TypeMap: + clone = LLSD::emptyMap(); + for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm) + { + if (has_filter) + { + if (filter.has((*itm).first)) + { + if (!filter[(*itm).first].asBoolean()) + continue; + } + else if (filter.has("*")) + { + if (!filter["*"].asBoolean()) + continue; + } + else + { + continue; + } + } + clone[(*itm).first] = llsd_clone((*itm).second, filter); + } + break; + case LLSD::TypeArray: + clone = LLSD::emptyArray(); + for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita) + { + clone.append(llsd_clone(*ita, filter)); + } + break; + + case LLSD::TypeBinary: + { + LLSD::Binary bin(value.asBinary().begin(), value.asBinary().end()); + clone = LLSD::Binary(bin); + break; + } + default: + clone = value; + } + + return clone; +} + +LLSD llsd_shallow(LLSD value, LLSD filter) +{ + LLSD shallow; + bool has_filter(filter.isMap()); + + if (value.isMap()) + { + shallow = LLSD::emptyMap(); + for (LLSD::map_const_iterator itm = value.beginMap(); itm != value.endMap(); ++itm) + { + if (has_filter) + { + if (filter.has((*itm).first)) + { + if (!filter[(*itm).first].asBoolean()) + continue; + } + else if (filter.has("*")) + { + if (!filter["*"].asBoolean()) + continue; + } + else + { + continue; + } + } + shallow[(*itm).first] = (*itm).second; + } + } + else if (value.isArray()) + { + shallow = LLSD::emptyArray(); + for (LLSD::array_const_iterator ita = value.beginArray(); ita != value.endArray(); ++ita) + { + shallow.append(*ita); + } + } + else + { + return value; + } + + return shallow; +} + +LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args) +{ + // LLSD supports a number of types, two of which are aggregates: Map and + // Array. We don't try to support Map: supporting Map would seem to + // promise that we could somehow match the string key to 'func's parameter + // names. Uh sorry, maybe in some future version of C++ with reflection. + if (args.isMap()) + { + LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported")); + } + // We expect an LLSD array, but what the heck, treat isUndefined() as a + // zero-length array for calling a nullary 'func'. + if (args.isUndefined() || args.isArray()) + { + // this works because LLSD().size() == 0 + if (args.size() != arity) + { + LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ", + args.size(), "-entry LLSD array)"))); + } + return args; + } + + // args is one of the scalar types + // scalar_LLSD.size() == 0, so don't test that here. + // You can pass a scalar LLSD only to a unary 'func'. + if (arity != 1) + { + LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), " + "LLSD ", LLSD::typeString(args.type()), ")"))); + } + // make an array of it + return llsd::array(args); +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 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 <boost/functional/hash.hpp>
-#include <cassert>
-#include <memory> // std::shared_ptr
-#include <type_traits>
-#include <vector>
-
-// U32
-LL_COMMON_API LLSD ll_sd_from_U32(const U32);
-LL_COMMON_API U32 ll_U32_from_sd(const LLSD& sd);
-
-// U64
-LL_COMMON_API LLSD ll_sd_from_U64(const U64);
-LL_COMMON_API U64 ll_U64_from_sd(const LLSD& sd);
-
-// IP Address
-LL_COMMON_API LLSD ll_sd_from_ipaddr(const U32);
-LL_COMMON_API U32 ll_ipaddr_from_sd(const LLSD& sd);
-
-// Binary to string
-LL_COMMON_API LLSD ll_string_from_binary(const LLSD& sd);
-
-//String to binary
-LL_COMMON_API LLSD ll_binary_from_string(const LLSD& sd);
-
-// Serializes sd to static buffer and returns pointer, useful for gdb debugging.
-LL_COMMON_API char* ll_print_sd(const LLSD& sd);
-
-// Serializes sd to static buffer and returns pointer, using "pretty printing" mode.
-LL_COMMON_API char* ll_pretty_print_sd_ptr(const LLSD* sd);
-LL_COMMON_API char* ll_pretty_print_sd(const LLSD& sd);
-
-LL_COMMON_API std::string ll_stream_notation_sd(const LLSD& sd);
-
-//compares the structure of an LLSD to a template LLSD and stores the
-//"valid" values in a 3rd LLSD. Default values
-//are pulled from the template. Extra keys/values in the test
-//are ignored in the resultant LLSD. Ordering of arrays matters
-//Returns false if the test is of same type but values differ in type
-//Otherwise, returns true
-
-LL_COMMON_API bool compare_llsd_with_template(
- const LLSD& llsd_to_test,
- const LLSD& template_llsd,
- LLSD& resultant_llsd);
-
-// filter_llsd_with_template() is a direct clone (copy-n-paste) of
-// compare_llsd_with_template with the following differences:
-// (1) bool vs BOOL return types
-// (2) A map with the key value "*" is a special value and maps any key in the
-// test llsd that doesn't have an explicitly matching key in the template.
-// (3) The element of an array with exactly one element is taken as a template
-// for *all* the elements of the test array. If the template array is of
-// different size, compare_llsd_with_template() semantics apply.
-bool filter_llsd_with_template(
- const LLSD & llsd_to_test,
- const LLSD & template_llsd,
- LLSD & resultant_llsd);
-
-/**
- * Recursively determine whether a given LLSD data block "matches" another
- * LLSD prototype. The returned string is empty() on success, non-empty() on
- * mismatch.
- *
- * This function tests structure (types) rather than data values. It is
- * intended for when a consumer expects an LLSD block with a particular
- * structure, and must succinctly detect whether the arriving block is
- * well-formed. For instance, a test of the form:
- * @code
- * if (! (data.has("request") && data.has("target") && data.has("modifier") ...))
- * @endcode
- * could instead be expressed by initializing a prototype LLSD map with the
- * required keys and writing:
- * @code
- * if (! llsd_matches(prototype, data).empty())
- * @endcode
- *
- * A non-empty return value is an error-message fragment intended to indicate
- * to (English-speaking) developers where in the prototype structure the
- * mismatch occurred.
- *
- * * If a slot in the prototype isUndefined(), then anything is valid at that
- * place in the real object. (Passing prototype == LLSD() matches anything
- * at all.)
- * * An array in the prototype must match a data array at least that large.
- * (Additional entries in the data array are ignored.) Every isDefined()
- * entry in the prototype array must match the corresponding entry in the
- * data array.
- * * A map in the prototype must match a map in the data. Every key in the
- * prototype map must match a corresponding key in the data map. (Additional
- * keys in the data map are ignored.) Every isDefined() value in the
- * prototype map must match the corresponding key's value in the data map.
- * * Scalar values in the prototype are tested for @em type rather than value.
- * For instance, a String in the prototype matches any String at all. In
- * effect, storing an Integer at a particular place in the prototype asserts
- * that the caller intends to apply asInteger() to the corresponding slot in
- * the data.
- * * A String in the prototype matches String, Boolean, Integer, Real, UUID,
- * Date and URI, because asString() applied to any of these produces a
- * meaningful result.
- * * Similarly, a Boolean, Integer or Real in the prototype can match any of
- * Boolean, Integer or Real in the data -- or even String.
- * * UUID matches UUID or String.
- * * Date matches Date or String.
- * * URI matches URI or String.
- * * Binary in the prototype matches only Binary in the data.
- *
- * @TODO: when a Boolean, Integer or Real in the prototype matches a String in
- * the data, we should examine the String @em value to ensure it can be
- * meaningfully converted to the requested type. The same goes for UUID, Date
- * and URI.
- */
-LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx="");
-
-/// Deep equality. If you want to compare LLSD::Real values for approximate
-/// equality rather than bitwise equality, pass @a bits as for
-/// is_approx_equal_fraction().
-LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits=-1);
-/// If you don't care about LLSD::Real equality
-inline bool operator==(const LLSD& lhs, const LLSD& rhs)
-{
- return llsd_equals(lhs, rhs);
-}
-inline bool operator!=(const LLSD& lhs, const LLSD& rhs)
-{
- // operator!=() should always be the negation of operator==()
- return ! (lhs == rhs);
-}
-
-// Simple function to copy data out of input & output iterators if
-// there is no need for casting.
-template<typename Input> LLSD llsd_copy_array(Input iter, Input end)
-{
- LLSD dest;
- for (; iter != end; ++iter)
- {
- dest.append(*iter);
- }
- return dest;
-}
-
-namespace llsd
-{
-
-/**
- * Drill down to locate an element in 'blob' according to 'path', where 'path'
- * is one of the following:
- *
- * - LLSD::String: 'blob' is an LLSD::Map. Find the entry with key 'path'.
- * - LLSD::Integer: 'blob' is an LLSD::Array. Find the entry with index 'path'.
- * - Any other 'path' type will be interpreted as LLSD::Array, and 'blob' is a
- * nested structure. For each element of 'path':
- * - If it's an LLSD::Integer, select the entry with that index from an
- * LLSD::Array at that level.
- * - If it's an LLSD::String, select the entry with that key from an
- * LLSD::Map at that level.
- * - Anything else is an error.
- *
- * By implication, if path.isUndefined() or otherwise equivalent to an empty
- * LLSD::Array, drill[_ref]() returns 'blob' as is.
- */
-LLSD drill(const LLSD& blob, const LLSD& path);
-LLSD& drill_ref( LLSD& blob, const LLSD& path);
-
-}
-
-namespace llsd
-{
-
-/**
- * Construct an LLSD::Array inline, using modern C++ variadic arguments.
- */
-
-// recursion tail
-inline
-void array_(LLSD&) {}
-
-// recursive call
-template <typename T0, typename... Ts>
-void array_(LLSD& data, T0&& v0, Ts&&... vs)
-{
- data.append(std::forward<T0>(v0));
- array_(data, std::forward<Ts>(vs)...);
-}
-
-// public interface
-template <typename... Ts>
-LLSD array(Ts&&... vs)
-{
- LLSD data;
- array_(data, std::forward<Ts>(vs)...);
- return data;
-}
-
-} // namespace llsd
-
-/*****************************************************************************
-* LLSDMap
-*****************************************************************************/
-/**
- * Construct an LLSD::Map inline, with implicit conversion to LLSD. Usage:
- *
- * @code
- * void somefunc(const LLSD&);
- * ...
- * somefunc(LLSDMap("alpha", "abc")("number", 17)("pi", 3.14));
- * @endcode
- *
- * For completeness, LLSDMap() with no args constructs an empty map, so
- * <tt>LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14)</tt> produces a map
- * equivalent to the above. But for most purposes, LLSD() is already
- * equivalent to an empty map, and if you explicitly want an empty isMap(),
- * there's LLSD::emptyMap(). However, supporting a no-args LLSDMap()
- * constructor follows the principle of least astonishment.
- */
-class LLSDMap
-{
-public:
- LLSDMap():
- _data(LLSD::emptyMap())
- {}
- LLSDMap(const LLSD::String& key, const LLSD& value):
- _data(LLSD::emptyMap())
- {
- _data[key] = value;
- }
-
- LLSDMap& operator()(const LLSD::String& key, const LLSD& value)
- {
- _data[key] = value;
- return *this;
- }
-
- operator LLSD() const { return _data; }
- LLSD get() const { return _data; }
-
-private:
- LLSD _data;
-};
-
-namespace llsd
-{
-
-/**
- * Construct an LLSD::Map inline, using modern C++ variadic arguments.
- */
-
-// recursion tail
-inline
-void map_(LLSD&) {}
-
-// recursive call
-template <typename T0, typename... Ts>
-void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs)
-{
- data[k0] = v0;
- map_(data, std::forward<Ts>(vs)...);
-}
-
-// public interface
-template <typename... Ts>
-LLSD map(Ts&&... vs)
-{
- LLSD data;
- map_(data, std::forward<Ts>(vs)...);
- return data;
-}
-
-} // namespace llsd
-
-/*****************************************************************************
-* LLSDParam
-*****************************************************************************/
-struct LLSDParamBase
-{
- virtual ~LLSDParamBase() {}
-};
-
-/**
- * LLSDParam is a customization point for passing LLSD values to function
- * parameters of more or less arbitrary type. LLSD provides a small set of
- * native conversions; but if a generic algorithm explicitly constructs an
- * LLSDParam object in the function's argument list, a consumer can provide
- * LLSDParam specializations to support more different parameter types than
- * LLSD's native conversions.
- *
- * Usage:
- *
- * @code
- * void somefunc(const paramtype&);
- * ...
- * somefunc(..., LLSDParam<paramtype>(someLLSD), ...);
- * @endcode
- */
-template <typename T>
-class LLSDParam: public LLSDParamBase
-{
-public:
- /**
- * Default implementation converts to T on construction, saves converted
- * value for later retrieval
- */
- LLSDParam(const LLSD& value):
- value_(value)
- {}
-
- operator T() const { return value_; }
-
-private:
- T value_;
-};
-
-/**
- * LLSDParam<LLSD> is for when you don't already have the target parameter
- * type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the
- * templated conversion operator will try to select a more specific LLSDParam
- * specialization.
- */
-template <>
-class LLSDParam<LLSD>: public LLSDParamBase
-{
-private:
- LLSD value_;
- // LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on
- // demand. Returning that engages LLSDParam<T>::operator T(), producing
- // the desired result. But LLSDParam<const char*> owns a std::string whose
- // c_str() is returned by its operator const char*(). If we return a temp
- // LLSDParam<const char*>, the compiler can destroy it right away, as soon
- // as we've called operator const char*(). That's a problem! That
- // invalidates the const char* we've just passed to the subject function.
- // This LLSDParam<LLSD> is presumably guaranteed to survive until the
- // subject function has returned, so we must ensure that any constructed
- // LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting
- // each LLSDParam<T> on the heap and capturing a smart pointer in a vector
- // works. We would have liked to use std::unique_ptr, but vector entries
- // must be copyable.
- // (Alternatively we could assume that every instance of LLSDParam<LLSD>
- // will be asked for at most ONE conversion. We could store a scalar
- // std::unique_ptr and, when constructing an new LLSDParam<T>, assert that
- // the unique_ptr is empty. But some future change in usage patterns, and
- // consequent failure of that assertion, would be very mysterious. Instead
- // of explaining how to fix it, just fix it now.)
- mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_;
-
-public:
- LLSDParam(const LLSD& value): value_(value) {}
-
- /// if we're literally being asked for an LLSD parameter, avoid infinite
- /// recursion
- operator LLSD() const { return value_; }
-
- /// otherwise, instantiate a more specific LLSDParam<T> to convert; that
- /// preserves the existing customization mechanism
- template <typename T>
- operator T() const
- {
- // capture 'ptr' with the specific subclass type because converters_
- // only stores LLSDParamBase pointers
- auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) };
- // keep the new converter alive until we ourselves are destroyed
- converters_.push_back(ptr);
- return *ptr;
- }
-};
-
-/**
- * Turns out that several target types could accept an LLSD param using any of
- * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or
- * std::string. Therefore, the compiler can't decide which LLSD conversion
- * operator to choose, even though to us it seems obvious. But that's okay, we
- * can specialize LLSDParam for such target types, explicitly specifying the
- * desired conversion -- that's part of what LLSDParam is all about. Turns out
- * we have to do that enough to make it worthwhile generalizing. Use a macro
- * because I need to specify one of the asReal, etc., explicit conversion
- * methods as well as a type. If I'm overlooking a clever way to implement
- * that using a template instead, feel free to reimplement.
- */
-#define LLSDParam_for(T, AS) \
-template <> \
-class LLSDParam<T>: public LLSDParamBase \
-{ \
-public: \
- LLSDParam(const LLSD& value): \
- value_((T)value.AS()) \
- {} \
- \
- operator T() const { return value_; } \
- \
-private: \
- T value_; \
-}
-
-LLSDParam_for(float, asReal);
-LLSDParam_for(LLUUID, asUUID);
-LLSDParam_for(LLDate, asDate);
-LLSDParam_for(LLURI, asURI);
-LLSDParam_for(LLSD::Binary, asBinary);
-
-/**
- * LLSDParam<const char*> is an example of the kind of conversion you can
- * support with LLSDParam beyond native LLSD conversions. Normally you can't
- * pass an LLSD object to a function accepting const char* -- but you can
- * safely pass an LLSDParam<const char*>(yourLLSD).
- */
-template <>
-class LLSDParam<const char*>: public LLSDParamBase
-{
-private:
- // The difference here is that we store a std::string rather than a const
- // char*. It's important that the LLSDParam object own the std::string.
- std::string value_;
- // We don't bother storing the incoming LLSD object, but we do have to
- // distinguish whether value_ is an empty string because the LLSD object
- // contains an empty string or because it's isUndefined().
- bool undefined_;
-
-public:
- LLSDParam(const LLSD& value):
- value_(value),
- undefined_(value.isUndefined())
- {}
-
- // The const char* we retrieve is for storage owned by our value_ member.
- // That's how we guarantee that the const char* is valid for the lifetime
- // of this LLSDParam object. Constructing your LLSDParam in the argument
- // list should ensure that the LLSDParam object will persist for the
- // duration of the function call.
- operator const char*() const
- {
- if (undefined_)
- {
- // By default, an isUndefined() LLSD object's asString() method
- // will produce an empty string. But for a function accepting
- // const char*, it's often important to be able to pass NULL, and
- // isUndefined() seems like the best way. If you want to pass an
- // empty string, you can still pass LLSD(""). Without this special
- // case, though, no LLSD value could pass NULL.
- return NULL;
- }
- return value_.c_str();
- }
-};
-
-namespace llsd
-{
-
-/*****************************************************************************
-* range-based for-loop helpers for LLSD
-*****************************************************************************/
-/// Usage: for (LLSD item : inArray(someLLSDarray)) { ... }
-class inArray
-{
-public:
- inArray(const LLSD& array):
- _array(array)
- {}
-
- typedef LLSD::array_const_iterator const_iterator;
- typedef LLSD::array_iterator iterator;
-
- iterator begin() { return _array.beginArray(); }
- iterator end() { return _array.endArray(); }
- const_iterator begin() const { return _array.beginArray(); }
- const_iterator end() const { return _array.endArray(); }
-
-private:
- LLSD _array;
-};
-
-/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator.
-typedef std::map<LLSD::String, LLSD>::value_type MapEntry;
-
-/// Usage: for([const] MapEntry& e : inMap(someLLSDmap)) { ... }
-class inMap
-{
-public:
- inMap(const LLSD& map):
- _map(map)
- {}
-
- typedef LLSD::map_const_iterator const_iterator;
- typedef LLSD::map_iterator iterator;
-
- iterator begin() { return _map.beginMap(); }
- iterator end() { return _map.endMap(); }
- const_iterator begin() const { return _map.beginMap(); }
- const_iterator end() const { return _map.endMap(); }
-
-private:
- LLSD _map;
-};
-
-} // namespace llsd
-
-
-// Creates a deep clone of an LLSD object. Maps, Arrays and binary objects
-// are duplicated, atomic primitives (Boolean, Integer, Real, etc) simply
-// use a shared reference.
-// Optionally a filter may be specified to control what is duplicated. The
-// map takes the form "keyname/boolean".
-// If the value is true the value will be duplicated otherwise it will be skipped
-// when encountered in a map. A key name of "*" can be specified as a wild card
-// and will specify the default behavior. If no wild card is given and the clone
-// encounters a name not in the filter, that value will be skipped.
-LLSD llsd_clone(LLSD value, LLSD filter = LLSD());
-
-// Creates a shallow copy of a map or array. If passed any other type of LLSD
-// object it simply returns that value. See llsd_clone for a description of
-// the filter parameter.
-LLSD llsd_shallow(LLSD value, LLSD filter = LLSD());
-
-namespace llsd
-{
-
-// llsd namespace aliases
-inline
-LLSD clone (LLSD value, LLSD filter=LLSD()) { return llsd_clone (value, filter); }
-inline
-LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter); }
-
-} // namespace llsd
-
-// Specialization for generating a hash value from an LLSD block.
-namespace boost
-{
-template <>
-struct hash<LLSD>
-{
- typedef LLSD argument_type;
- typedef std::size_t result_type;
- result_type operator()(argument_type const& s) const
- {
- result_type seed(0);
-
- LLSD::Type stype = s.type();
- boost::hash_combine(seed, (S32)stype);
-
- switch (stype)
- {
- case LLSD::TypeBoolean:
- boost::hash_combine(seed, s.asBoolean());
- break;
- case LLSD::TypeInteger:
- boost::hash_combine(seed, s.asInteger());
- break;
- case LLSD::TypeReal:
- boost::hash_combine(seed, s.asReal());
- break;
- case LLSD::TypeURI:
- case LLSD::TypeString:
- boost::hash_combine(seed, s.asString());
- break;
- case LLSD::TypeUUID:
- boost::hash_combine(seed, s.asUUID());
- break;
- case LLSD::TypeDate:
- boost::hash_combine(seed, s.asDate().secondsSinceEpoch());
- break;
- case LLSD::TypeBinary:
- {
- const LLSD::Binary &b(s.asBinary());
- boost::hash_range(seed, b.begin(), b.end());
- break;
- }
- case LLSD::TypeMap:
- {
- for (LLSD::map_const_iterator itm = s.beginMap(); itm != s.endMap(); ++itm)
- {
- boost::hash_combine(seed, (*itm).first);
- boost::hash_combine(seed, (*itm).second);
- }
- break;
- }
- case LLSD::TypeArray:
- for (LLSD::array_const_iterator ita = s.beginArray(); ita != s.endArray(); ++ita)
- {
- boost::hash_combine(seed, (*ita));
- }
- break;
- case LLSD::TypeUndefined:
- default:
- break;
- }
-
- return seed;
- }
-};
-}
-
-namespace LL
-{
-
-/*****************************************************************************
-* apply(function, LLSD array)
-*****************************************************************************/
-// validate incoming LLSD blob, and return an LLSD array suitable to pass to
-// the function of interest
-LLSD apply_llsd_fix(size_t arity, const LLSD& args);
-
-// Derived from https://stackoverflow.com/a/20441189
-// and https://en.cppreference.com/w/cpp/utility/apply .
-// We can't simply make a tuple from the LLSD array and then apply() that
-// tuple to the function -- how would make_tuple() deduce the correct
-// parameter type for each entry? We must go directly to the target function.
-template <typename CALLABLE, std::size_t... I>
-auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>)
-{
- // call func(unpacked args), using generic LLSDParam<LLSD> to convert each
- // entry in 'array' to the target parameter type
- return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...);
-}
-
-// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic
-// function with (that many) items from the passed LLSD array
-template <size_t ARITY, typename CALLABLE>
-auto apply_n(CALLABLE&& func, const LLSD& args)
-{
- return apply_impl(std::forward<CALLABLE>(func),
- apply_llsd_fix(ARITY, args),
- std::make_index_sequence<ARITY>());
-}
-
-/**
- * apply(function, LLSD) goes beyond C++17 std::apply(). For this case
- * @a function @emph cannot be variadic: the compiler must know at compile
- * time how many arguments to pass. This isn't Python. (But see apply_n() to
- * pass a specific number of args to a variadic function.)
- */
-template <typename CALLABLE>
-auto apply(CALLABLE&& func, const LLSD& args)
-{
- // infer arity from the definition of func
- constexpr auto arity = function_arity<
- typename std::remove_reference<CALLABLE>::type>::value;
- // now that we have a compile-time arity, apply_n() works
- return apply_n<arity>(std::forward<CALLABLE>(func), args);
-}
-
-} // namespace LL
-
-#endif // LL_LLSDUTIL_H
+/** + * @file llsdutil.h + * @author Phoenix + * @date 2006-05-24 + * @brief Utility classes, functions, etc, for using structured data. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSDUTIL_H +#define LL_LLSDUTIL_H + +#include "apply.h" // LL::invoke() +#include "function_types.h" // LL::function_arity +#include "llsd.h" +#include <boost/functional/hash.hpp> +#include <cassert> +#include <memory> // std::shared_ptr +#include <type_traits> +#include <vector> + +// U32 +LL_COMMON_API LLSD ll_sd_from_U32(const U32); +LL_COMMON_API U32 ll_U32_from_sd(const LLSD& sd); + +// U64 +LL_COMMON_API LLSD ll_sd_from_U64(const U64); +LL_COMMON_API U64 ll_U64_from_sd(const LLSD& sd); + +// IP Address +LL_COMMON_API LLSD ll_sd_from_ipaddr(const U32); +LL_COMMON_API U32 ll_ipaddr_from_sd(const LLSD& sd); + +// Binary to string +LL_COMMON_API LLSD ll_string_from_binary(const LLSD& sd); + +//String to binary +LL_COMMON_API LLSD ll_binary_from_string(const LLSD& sd); + +// Serializes sd to static buffer and returns pointer, useful for gdb debugging. +LL_COMMON_API char* ll_print_sd(const LLSD& sd); + +// Serializes sd to static buffer and returns pointer, using "pretty printing" mode. +LL_COMMON_API char* ll_pretty_print_sd_ptr(const LLSD* sd); +LL_COMMON_API char* ll_pretty_print_sd(const LLSD& sd); + +LL_COMMON_API std::string ll_stream_notation_sd(const LLSD& sd); + +//compares the structure of an LLSD to a template LLSD and stores the +//"valid" values in a 3rd LLSD. Default values +//are pulled from the template. Extra keys/values in the test +//are ignored in the resultant LLSD. Ordering of arrays matters +//Returns false if the test is of same type but values differ in type +//Otherwise, returns true + +LL_COMMON_API bool compare_llsd_with_template( + const LLSD& llsd_to_test, + const LLSD& template_llsd, + LLSD& resultant_llsd); + +// filter_llsd_with_template() is a direct clone (copy-n-paste) of +// compare_llsd_with_template with the following differences: +// (1) bool vs BOOL return types +// (2) A map with the key value "*" is a special value and maps any key in the +// test llsd that doesn't have an explicitly matching key in the template. +// (3) The element of an array with exactly one element is taken as a template +// for *all* the elements of the test array. If the template array is of +// different size, compare_llsd_with_template() semantics apply. +bool filter_llsd_with_template( + const LLSD & llsd_to_test, + const LLSD & template_llsd, + LLSD & resultant_llsd); + +/** + * Recursively determine whether a given LLSD data block "matches" another + * LLSD prototype. The returned string is empty() on success, non-empty() on + * mismatch. + * + * This function tests structure (types) rather than data values. It is + * intended for when a consumer expects an LLSD block with a particular + * structure, and must succinctly detect whether the arriving block is + * well-formed. For instance, a test of the form: + * @code + * if (! (data.has("request") && data.has("target") && data.has("modifier") ...)) + * @endcode + * could instead be expressed by initializing a prototype LLSD map with the + * required keys and writing: + * @code + * if (! llsd_matches(prototype, data).empty()) + * @endcode + * + * A non-empty return value is an error-message fragment intended to indicate + * to (English-speaking) developers where in the prototype structure the + * mismatch occurred. + * + * * If a slot in the prototype isUndefined(), then anything is valid at that + * place in the real object. (Passing prototype == LLSD() matches anything + * at all.) + * * An array in the prototype must match a data array at least that large. + * (Additional entries in the data array are ignored.) Every isDefined() + * entry in the prototype array must match the corresponding entry in the + * data array. + * * A map in the prototype must match a map in the data. Every key in the + * prototype map must match a corresponding key in the data map. (Additional + * keys in the data map are ignored.) Every isDefined() value in the + * prototype map must match the corresponding key's value in the data map. + * * Scalar values in the prototype are tested for @em type rather than value. + * For instance, a String in the prototype matches any String at all. In + * effect, storing an Integer at a particular place in the prototype asserts + * that the caller intends to apply asInteger() to the corresponding slot in + * the data. + * * A String in the prototype matches String, Boolean, Integer, Real, UUID, + * Date and URI, because asString() applied to any of these produces a + * meaningful result. + * * Similarly, a Boolean, Integer or Real in the prototype can match any of + * Boolean, Integer or Real in the data -- or even String. + * * UUID matches UUID or String. + * * Date matches Date or String. + * * URI matches URI or String. + * * Binary in the prototype matches only Binary in the data. + * + * @TODO: when a Boolean, Integer or Real in the prototype matches a String in + * the data, we should examine the String @em value to ensure it can be + * meaningfully converted to the requested type. The same goes for UUID, Date + * and URI. + */ +LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data, const std::string& pfx=""); + +/// Deep equality. If you want to compare LLSD::Real values for approximate +/// equality rather than bitwise equality, pass @a bits as for +/// is_approx_equal_fraction(). +LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits=-1); +/// If you don't care about LLSD::Real equality +inline bool operator==(const LLSD& lhs, const LLSD& rhs) +{ + return llsd_equals(lhs, rhs); +} +inline bool operator!=(const LLSD& lhs, const LLSD& rhs) +{ + // operator!=() should always be the negation of operator==() + return ! (lhs == rhs); +} + +// Simple function to copy data out of input & output iterators if +// there is no need for casting. +template<typename Input> LLSD llsd_copy_array(Input iter, Input end) +{ + LLSD dest; + for (; iter != end; ++iter) + { + dest.append(*iter); + } + return dest; +} + +namespace llsd +{ + +/** + * Drill down to locate an element in 'blob' according to 'path', where 'path' + * is one of the following: + * + * - LLSD::String: 'blob' is an LLSD::Map. Find the entry with key 'path'. + * - LLSD::Integer: 'blob' is an LLSD::Array. Find the entry with index 'path'. + * - Any other 'path' type will be interpreted as LLSD::Array, and 'blob' is a + * nested structure. For each element of 'path': + * - If it's an LLSD::Integer, select the entry with that index from an + * LLSD::Array at that level. + * - If it's an LLSD::String, select the entry with that key from an + * LLSD::Map at that level. + * - Anything else is an error. + * + * By implication, if path.isUndefined() or otherwise equivalent to an empty + * LLSD::Array, drill[_ref]() returns 'blob' as is. + */ +LLSD drill(const LLSD& blob, const LLSD& path); +LLSD& drill_ref( LLSD& blob, const LLSD& path); + +} + +namespace llsd +{ + +/** + * Construct an LLSD::Array inline, using modern C++ variadic arguments. + */ + +// recursion tail +inline +void array_(LLSD&) {} + +// recursive call +template <typename T0, typename... Ts> +void array_(LLSD& data, T0&& v0, Ts&&... vs) +{ + data.append(std::forward<T0>(v0)); + array_(data, std::forward<Ts>(vs)...); +} + +// public interface +template <typename... Ts> +LLSD array(Ts&&... vs) +{ + LLSD data; + array_(data, std::forward<Ts>(vs)...); + return data; +} + +} // namespace llsd + +/***************************************************************************** +* LLSDMap +*****************************************************************************/ +/** + * Construct an LLSD::Map inline, with implicit conversion to LLSD. Usage: + * + * @code + * void somefunc(const LLSD&); + * ... + * somefunc(LLSDMap("alpha", "abc")("number", 17)("pi", 3.14)); + * @endcode + * + * For completeness, LLSDMap() with no args constructs an empty map, so + * <tt>LLSDMap()("alpha", "abc")("number", 17)("pi", 3.14)</tt> produces a map + * equivalent to the above. But for most purposes, LLSD() is already + * equivalent to an empty map, and if you explicitly want an empty isMap(), + * there's LLSD::emptyMap(). However, supporting a no-args LLSDMap() + * constructor follows the principle of least astonishment. + */ +class LLSDMap +{ +public: + LLSDMap(): + _data(LLSD::emptyMap()) + {} + LLSDMap(const LLSD::String& key, const LLSD& value): + _data(LLSD::emptyMap()) + { + _data[key] = value; + } + + LLSDMap& operator()(const LLSD::String& key, const LLSD& value) + { + _data[key] = value; + return *this; + } + + operator LLSD() const { return _data; } + LLSD get() const { return _data; } + +private: + LLSD _data; +}; + +namespace llsd +{ + +/** + * Construct an LLSD::Map inline, using modern C++ variadic arguments. + */ + +// recursion tail +inline +void map_(LLSD&) {} + +// recursive call +template <typename T0, typename... Ts> +void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs) +{ + data[k0] = v0; + map_(data, std::forward<Ts>(vs)...); +} + +// public interface +template <typename... Ts> +LLSD map(Ts&&... vs) +{ + LLSD data; + map_(data, std::forward<Ts>(vs)...); + return data; +} + +} // namespace llsd + +/***************************************************************************** +* LLSDParam +*****************************************************************************/ +struct LLSDParamBase +{ + virtual ~LLSDParamBase() {} +}; + +/** + * LLSDParam is a customization point for passing LLSD values to function + * parameters of more or less arbitrary type. LLSD provides a small set of + * native conversions; but if a generic algorithm explicitly constructs an + * LLSDParam object in the function's argument list, a consumer can provide + * LLSDParam specializations to support more different parameter types than + * LLSD's native conversions. + * + * Usage: + * + * @code + * void somefunc(const paramtype&); + * ... + * somefunc(..., LLSDParam<paramtype>(someLLSD), ...); + * @endcode + */ +template <typename T> +class LLSDParam: public LLSDParamBase +{ +public: + /** + * Default implementation converts to T on construction, saves converted + * value for later retrieval + */ + LLSDParam(const LLSD& value): + value_(value) + {} + + operator T() const { return value_; } + +private: + T value_; +}; + +/** + * LLSDParam<LLSD> is for when you don't already have the target parameter + * type in hand. Instantiate LLSDParam<LLSD>(your LLSD object), and the + * templated conversion operator will try to select a more specific LLSDParam + * specialization. + */ +template <> +class LLSDParam<LLSD>: public LLSDParamBase +{ +private: + LLSD value_; + // LLSDParam<LLSD>::operator T() works by instantiating an LLSDParam<T> on + // demand. Returning that engages LLSDParam<T>::operator T(), producing + // the desired result. But LLSDParam<const char*> owns a std::string whose + // c_str() is returned by its operator const char*(). If we return a temp + // LLSDParam<const char*>, the compiler can destroy it right away, as soon + // as we've called operator const char*(). That's a problem! That + // invalidates the const char* we've just passed to the subject function. + // This LLSDParam<LLSD> is presumably guaranteed to survive until the + // subject function has returned, so we must ensure that any constructed + // LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting + // each LLSDParam<T> on the heap and capturing a smart pointer in a vector + // works. We would have liked to use std::unique_ptr, but vector entries + // must be copyable. + // (Alternatively we could assume that every instance of LLSDParam<LLSD> + // will be asked for at most ONE conversion. We could store a scalar + // std::unique_ptr and, when constructing an new LLSDParam<T>, assert that + // the unique_ptr is empty. But some future change in usage patterns, and + // consequent failure of that assertion, would be very mysterious. Instead + // of explaining how to fix it, just fix it now.) + mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_; + +public: + LLSDParam(const LLSD& value): value_(value) {} + + /// if we're literally being asked for an LLSD parameter, avoid infinite + /// recursion + operator LLSD() const { return value_; } + + /// otherwise, instantiate a more specific LLSDParam<T> to convert; that + /// preserves the existing customization mechanism + template <typename T> + operator T() const + { + // capture 'ptr' with the specific subclass type because converters_ + // only stores LLSDParamBase pointers + auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) }; + // keep the new converter alive until we ourselves are destroyed + converters_.push_back(ptr); + return *ptr; + } +}; + +/** + * Turns out that several target types could accept an LLSD param using any of + * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or + * std::string. Therefore, the compiler can't decide which LLSD conversion + * operator to choose, even though to us it seems obvious. But that's okay, we + * can specialize LLSDParam for such target types, explicitly specifying the + * desired conversion -- that's part of what LLSDParam is all about. Turns out + * we have to do that enough to make it worthwhile generalizing. Use a macro + * because I need to specify one of the asReal, etc., explicit conversion + * methods as well as a type. If I'm overlooking a clever way to implement + * that using a template instead, feel free to reimplement. + */ +#define LLSDParam_for(T, AS) \ +template <> \ +class LLSDParam<T>: public LLSDParamBase \ +{ \ +public: \ + LLSDParam(const LLSD& value): \ + value_((T)value.AS()) \ + {} \ + \ + operator T() const { return value_; } \ + \ +private: \ + T value_; \ +} + +LLSDParam_for(float, asReal); +LLSDParam_for(LLUUID, asUUID); +LLSDParam_for(LLDate, asDate); +LLSDParam_for(LLURI, asURI); +LLSDParam_for(LLSD::Binary, asBinary); + +/** + * LLSDParam<const char*> is an example of the kind of conversion you can + * support with LLSDParam beyond native LLSD conversions. Normally you can't + * pass an LLSD object to a function accepting const char* -- but you can + * safely pass an LLSDParam<const char*>(yourLLSD). + */ +template <> +class LLSDParam<const char*>: public LLSDParamBase +{ +private: + // The difference here is that we store a std::string rather than a const + // char*. It's important that the LLSDParam object own the std::string. + std::string value_; + // We don't bother storing the incoming LLSD object, but we do have to + // distinguish whether value_ is an empty string because the LLSD object + // contains an empty string or because it's isUndefined(). + bool undefined_; + +public: + LLSDParam(const LLSD& value): + value_(value), + undefined_(value.isUndefined()) + {} + + // The const char* we retrieve is for storage owned by our value_ member. + // That's how we guarantee that the const char* is valid for the lifetime + // of this LLSDParam object. Constructing your LLSDParam in the argument + // list should ensure that the LLSDParam object will persist for the + // duration of the function call. + operator const char*() const + { + if (undefined_) + { + // By default, an isUndefined() LLSD object's asString() method + // will produce an empty string. But for a function accepting + // const char*, it's often important to be able to pass NULL, and + // isUndefined() seems like the best way. If you want to pass an + // empty string, you can still pass LLSD(""). Without this special + // case, though, no LLSD value could pass NULL. + return NULL; + } + return value_.c_str(); + } +}; + +namespace llsd +{ + +/***************************************************************************** +* range-based for-loop helpers for LLSD +*****************************************************************************/ +/// Usage: for (LLSD item : inArray(someLLSDarray)) { ... } +class inArray +{ +public: + inArray(const LLSD& array): + _array(array) + {} + + typedef LLSD::array_const_iterator const_iterator; + typedef LLSD::array_iterator iterator; + + iterator begin() { return _array.beginArray(); } + iterator end() { return _array.endArray(); } + const_iterator begin() const { return _array.beginArray(); } + const_iterator end() const { return _array.endArray(); } + +private: + LLSD _array; +}; + +/// MapEntry is what you get from dereferencing an LLSD::map_[const_]iterator. +typedef std::map<LLSD::String, LLSD>::value_type MapEntry; + +/// Usage: for([const] MapEntry& e : inMap(someLLSDmap)) { ... } +class inMap +{ +public: + inMap(const LLSD& map): + _map(map) + {} + + typedef LLSD::map_const_iterator const_iterator; + typedef LLSD::map_iterator iterator; + + iterator begin() { return _map.beginMap(); } + iterator end() { return _map.endMap(); } + const_iterator begin() const { return _map.beginMap(); } + const_iterator end() const { return _map.endMap(); } + +private: + LLSD _map; +}; + +} // namespace llsd + + +// Creates a deep clone of an LLSD object. Maps, Arrays and binary objects +// are duplicated, atomic primitives (Boolean, Integer, Real, etc) simply +// use a shared reference. +// Optionally a filter may be specified to control what is duplicated. The +// map takes the form "keyname/boolean". +// If the value is true the value will be duplicated otherwise it will be skipped +// when encountered in a map. A key name of "*" can be specified as a wild card +// and will specify the default behavior. If no wild card is given and the clone +// encounters a name not in the filter, that value will be skipped. +LLSD llsd_clone(LLSD value, LLSD filter = LLSD()); + +// Creates a shallow copy of a map or array. If passed any other type of LLSD +// object it simply returns that value. See llsd_clone for a description of +// the filter parameter. +LLSD llsd_shallow(LLSD value, LLSD filter = LLSD()); + +namespace llsd +{ + +// llsd namespace aliases +inline +LLSD clone (LLSD value, LLSD filter=LLSD()) { return llsd_clone (value, filter); } +inline +LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter); } + +} // namespace llsd + +// Specialization for generating a hash value from an LLSD block. +namespace boost +{ +template <> +struct hash<LLSD> +{ + typedef LLSD argument_type; + typedef std::size_t result_type; + result_type operator()(argument_type const& s) const + { + result_type seed(0); + + LLSD::Type stype = s.type(); + boost::hash_combine(seed, (S32)stype); + + switch (stype) + { + case LLSD::TypeBoolean: + boost::hash_combine(seed, s.asBoolean()); + break; + case LLSD::TypeInteger: + boost::hash_combine(seed, s.asInteger()); + break; + case LLSD::TypeReal: + boost::hash_combine(seed, s.asReal()); + break; + case LLSD::TypeURI: + case LLSD::TypeString: + boost::hash_combine(seed, s.asString()); + break; + case LLSD::TypeUUID: + boost::hash_combine(seed, s.asUUID()); + break; + case LLSD::TypeDate: + boost::hash_combine(seed, s.asDate().secondsSinceEpoch()); + break; + case LLSD::TypeBinary: + { + const LLSD::Binary &b(s.asBinary()); + boost::hash_range(seed, b.begin(), b.end()); + break; + } + case LLSD::TypeMap: + { + for (LLSD::map_const_iterator itm = s.beginMap(); itm != s.endMap(); ++itm) + { + boost::hash_combine(seed, (*itm).first); + boost::hash_combine(seed, (*itm).second); + } + break; + } + case LLSD::TypeArray: + for (LLSD::array_const_iterator ita = s.beginArray(); ita != s.endArray(); ++ita) + { + boost::hash_combine(seed, (*ita)); + } + break; + case LLSD::TypeUndefined: + default: + break; + } + + return seed; + } +}; +} + +namespace LL +{ + +/***************************************************************************** +* apply(function, LLSD array) +*****************************************************************************/ +// validate incoming LLSD blob, and return an LLSD array suitable to pass to +// the function of interest +LLSD apply_llsd_fix(size_t arity, const LLSD& args); + +// Derived from https://stackoverflow.com/a/20441189 +// and https://en.cppreference.com/w/cpp/utility/apply . +// We can't simply make a tuple from the LLSD array and then apply() that +// tuple to the function -- how would make_tuple() deduce the correct +// parameter type for each entry? We must go directly to the target function. +template <typename CALLABLE, std::size_t... I> +auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence<I...>) +{ + // call func(unpacked args), using generic LLSDParam<LLSD> to convert each + // entry in 'array' to the target parameter type + return std::forward<CALLABLE>(func)(LLSDParam<LLSD>(array[I])...); +} + +// use apply_n<ARITY>(function, LLSD) to call a specific arity of a variadic +// function with (that many) items from the passed LLSD array +template <size_t ARITY, typename CALLABLE> +auto apply_n(CALLABLE&& func, const LLSD& args) +{ + return apply_impl(std::forward<CALLABLE>(func), + apply_llsd_fix(ARITY, args), + std::make_index_sequence<ARITY>()); +} + +/** + * apply(function, LLSD) goes beyond C++17 std::apply(). For this case + * @a function @emph cannot be variadic: the compiler must know at compile + * time how many arguments to pass. This isn't Python. (But see apply_n() to + * pass a specific number of args to a variadic function.) + */ +template <typename CALLABLE> +auto apply(CALLABLE&& func, const LLSD& args) +{ + // infer arity from the definition of func + constexpr auto arity = function_arity< + typename std::remove_reference<CALLABLE>::type>::value; + // now that we have a compile-time arity, apply_n() works + return apply_n<arity>(std::forward<CALLABLE>(func), args); +} + +} // namespace LL + +#endif // LL_LLSDUTIL_H diff --git a/indra/llcommon/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 <iostream>
-#include <sstream>
-
-#include "llwin32headerslean.h"
-#pragma warning (push)
-#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
-#include <dbghelp.h>
-#pragma warning (pop)
-
-typedef USHORT NTAPI RtlCaptureStackBackTrace_Function(
- IN ULONG frames_to_skip,
- IN ULONG frames_to_capture,
- OUT PVOID *backtrace,
- OUT PULONG backtrace_hash);
-
-static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn =
- (RtlCaptureStackBackTrace_Function*)
- GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCaptureStackBackTrace");
-
-bool ll_get_stack_trace(std::vector<std::string>& lines)
-{
- const S32 MAX_STACK_DEPTH = 32;
- const S32 STRING_NAME_LENGTH = 200;
- const S32 FRAME_SKIP = 2;
- static bool symbolsLoaded = false;
- static bool firstCall = true;
-
- HANDLE hProc = GetCurrentProcess();
-
- // load the symbols if they're not loaded
- if(!symbolsLoaded && firstCall)
- {
- symbolsLoaded = SymInitialize(hProc, NULL, true);
- firstCall = false;
- }
-
- // if loaded, get the call stack
- if(symbolsLoaded)
- {
- // create the frames to hold the addresses
- void* frames[MAX_STACK_DEPTH];
- memset(frames, 0, sizeof(void*)*MAX_STACK_DEPTH);
- S32 depth = 0;
-
- // get the addresses
- depth = RtlCaptureStackBackTrace_fn(FRAME_SKIP, MAX_STACK_DEPTH, frames, NULL);
-
- IMAGEHLP_LINE64 line;
- memset(&line, 0, sizeof(IMAGEHLP_LINE64));
- line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
-
- // create something to hold address info
- PIMAGEHLP_SYMBOL64 pSym;
- pSym = (PIMAGEHLP_SYMBOL64)malloc(sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH);
- memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH);
- pSym->MaxNameLength = STRING_NAME_LENGTH;
- pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
-
- // get address info for each address frame
- // and store
- for(S32 i=0; i < depth; i++)
- {
- std::stringstream stack_line;
- bool ret;
-
- DWORD64 addr = (DWORD64)frames[i];
- ret = SymGetSymFromAddr64(hProc, addr, 0, pSym);
- if(ret)
- {
- stack_line << pSym->Name << " ";
- }
-
- DWORD dummy;
- ret = SymGetLineFromAddr64(hProc, addr, &dummy, &line);
- if(ret)
- {
- std::string file_name = line.FileName;
- std::string::size_type index = file_name.rfind("\\");
- stack_line << file_name.substr(index + 1, file_name.size()) << ":" << line.LineNumber;
- }
-
- lines.push_back(stack_line.str());
- }
-
- free(pSym);
-
- // TODO: figure out a way to cleanup symbol loading
- // Not hugely necessary, however.
- //SymCleanup(hProc);
- return true;
- }
- else
- {
- lines.push_back("Stack Trace Failed. PDB symbol info not loaded");
- }
-
- return false;
-}
-
-void ll_get_stack_trace_internal(std::vector<std::string>& lines)
-{
- const S32 MAX_STACK_DEPTH = 100;
- const S32 STRING_NAME_LENGTH = 256;
-
- HANDLE process = GetCurrentProcess();
- SymInitialize( process, NULL, true );
-
- void *stack[MAX_STACK_DEPTH];
-
- unsigned short frames = RtlCaptureStackBackTrace_fn( 0, MAX_STACK_DEPTH, stack, NULL );
- SYMBOL_INFO *symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + STRING_NAME_LENGTH * sizeof(char), 1);
- symbol->MaxNameLen = STRING_NAME_LENGTH-1;
- symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
-
- for(unsigned int i = 0; i < frames; i++)
- {
- SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
- lines.push_back(symbol->Name);
- }
-
- free( symbol );
-}
-
-#else
-
-bool ll_get_stack_trace(std::vector<std::string>& lines)
-{
- return false;
-}
-
-void ll_get_stack_trace_internal(std::vector<std::string>& lines)
-{
-
-}
-
-#endif
-
+/** + * @file llstacktrace.cpp + * @brief stack tracing functionality + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llstacktrace.h" + +#ifdef LL_WINDOWS + +#include <iostream> +#include <sstream> + +#include "llwin32headerslean.h" +#pragma warning (push) +#pragma warning (disable:4091) // a microsoft header has warnings. Very nice. +#include <dbghelp.h> +#pragma warning (pop) + +typedef USHORT NTAPI RtlCaptureStackBackTrace_Function( + IN ULONG frames_to_skip, + IN ULONG frames_to_capture, + OUT PVOID *backtrace, + OUT PULONG backtrace_hash); + +static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn = + (RtlCaptureStackBackTrace_Function*) + GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlCaptureStackBackTrace"); + +bool ll_get_stack_trace(std::vector<std::string>& lines) +{ + const S32 MAX_STACK_DEPTH = 32; + const S32 STRING_NAME_LENGTH = 200; + const S32 FRAME_SKIP = 2; + static bool symbolsLoaded = false; + static bool firstCall = true; + + HANDLE hProc = GetCurrentProcess(); + + // load the symbols if they're not loaded + if(!symbolsLoaded && firstCall) + { + symbolsLoaded = SymInitialize(hProc, NULL, true); + firstCall = false; + } + + // if loaded, get the call stack + if(symbolsLoaded) + { + // create the frames to hold the addresses + void* frames[MAX_STACK_DEPTH]; + memset(frames, 0, sizeof(void*)*MAX_STACK_DEPTH); + S32 depth = 0; + + // get the addresses + depth = RtlCaptureStackBackTrace_fn(FRAME_SKIP, MAX_STACK_DEPTH, frames, NULL); + + IMAGEHLP_LINE64 line; + memset(&line, 0, sizeof(IMAGEHLP_LINE64)); + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + // create something to hold address info + PIMAGEHLP_SYMBOL64 pSym; + pSym = (PIMAGEHLP_SYMBOL64)malloc(sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH); + memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STRING_NAME_LENGTH); + pSym->MaxNameLength = STRING_NAME_LENGTH; + pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + + // get address info for each address frame + // and store + for(S32 i=0; i < depth; i++) + { + std::stringstream stack_line; + bool ret; + + DWORD64 addr = (DWORD64)frames[i]; + ret = SymGetSymFromAddr64(hProc, addr, 0, pSym); + if(ret) + { + stack_line << pSym->Name << " "; + } + + DWORD dummy; + ret = SymGetLineFromAddr64(hProc, addr, &dummy, &line); + if(ret) + { + std::string file_name = line.FileName; + std::string::size_type index = file_name.rfind("\\"); + stack_line << file_name.substr(index + 1, file_name.size()) << ":" << line.LineNumber; + } + + lines.push_back(stack_line.str()); + } + + free(pSym); + + // TODO: figure out a way to cleanup symbol loading + // Not hugely necessary, however. + //SymCleanup(hProc); + return true; + } + else + { + lines.push_back("Stack Trace Failed. PDB symbol info not loaded"); + } + + return false; +} + +void ll_get_stack_trace_internal(std::vector<std::string>& lines) +{ + const S32 MAX_STACK_DEPTH = 100; + const S32 STRING_NAME_LENGTH = 256; + + HANDLE process = GetCurrentProcess(); + SymInitialize( process, NULL, true ); + + void *stack[MAX_STACK_DEPTH]; + + unsigned short frames = RtlCaptureStackBackTrace_fn( 0, MAX_STACK_DEPTH, stack, NULL ); + SYMBOL_INFO *symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + STRING_NAME_LENGTH * sizeof(char), 1); + symbol->MaxNameLen = STRING_NAME_LENGTH-1; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + + for(unsigned int i = 0; i < frames; i++) + { + SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol); + lines.push_back(symbol->Name); + } + + free( symbol ); +} + +#else + +bool ll_get_stack_trace(std::vector<std::string>& lines) +{ + return false; +} + +void ll_get_stack_trace_internal(std::vector<std::string>& lines) +{ + +} + +#endif + diff --git a/indra/llcommon/llstl.h b/indra/llcommon/llstl.h index 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 <functional>
-#include <algorithm>
-#include <map>
-#include <vector>
-#include <list>
-#include <set>
-#include <typeinfo>
-
-#ifdef LL_LINUX
-// <ND> For strcmp
-#include <string.h>
-#endif
-// Use to compare the first element only of a pair
-// e.g. typedef std::set<std::pair<int, Data*>, compare_pair<int, Data*> > some_pair_set_t;
-template <typename T1, typename T2>
-struct compare_pair_first
-{
- bool operator()(const std::pair<T1, T2>& a, const std::pair<T1, T2>& b) const
- {
- return a.first < b.first;
- }
-};
-
-template <typename T1, typename T2>
-struct compare_pair_greater
-{
- bool operator()(const std::pair<T1, T2>& a, const std::pair<T1, T2>& b) const
- {
- if (!(a.first < b.first))
- return true;
- else if (!(b.first < a.first))
- return false;
- else
- return !(a.second < b.second);
- }
-};
-
-// Use to compare the contents of two pointers (e.g. std::string*)
-template <typename T>
-struct compare_pointer_contents
-{
- typedef const T* Tptr;
- bool operator()(const Tptr& a, const Tptr& b) const
- {
- return *a < *b;
- }
-};
-
-// DeletePointer is a simple helper for deleting all pointers in a container.
-// The general form is:
-//
-// std::for_each(cont.begin(), cont.end(), DeletePointer());
-// somemap.clear();
-//
-// Don't forget to clear()!
-
-struct DeletePointer
-{
- template<typename T> void operator()(T* ptr) const
- {
- delete ptr;
- }
-};
-struct DeletePointerArray
-{
- template<typename T> void operator()(T* ptr) const
- {
- delete[] ptr;
- }
-};
-
-// DeletePairedPointer is a simple helper for deleting all pointers in a map.
-// The general form is:
-//
-// std::for_each(somemap.begin(), somemap.end(), DeletePairedPointer());
-// somemap.clear(); // Don't leave dangling pointers around
-
-struct DeletePairedPointer
-{
- template<typename T> void operator()(T &ptr) const
- {
- delete ptr.second;
- ptr.second = NULL;
- }
-};
-struct DeletePairedPointerArray
-{
- template<typename T> void operator()(T &ptr) const
- {
- delete[] ptr.second;
- ptr.second = NULL;
- }
-};
-
-
-// Alternate version of the above so that has a more cumbersome
-// syntax, but it can be used with compositional functors.
-// NOTE: The functor retuns a bool because msdev bombs during the
-// composition if you return void. Once we upgrade to a newer
-// compiler, the second unary_function template parameter can be set
-// to void.
-//
-// Here's a snippet showing how you use this object:
-//
-// typedef std::map<int, widget*> map_type;
-// map_type widget_map;
-// ... // add elements
-// // delete them all
-// for_each(widget_map.begin(),
-// widget_map.end(),
-// llcompose1(DeletePointerFunctor<widget>(),
-// llselect2nd<map_type::value_type>()));
-
-template<typename T>
-struct DeletePointerFunctor
-{
- bool operator()(T* ptr) const
- {
- delete ptr;
- return true;
- }
-};
-
-// See notes about DeleteArray for why you should consider avoiding this.
-template<typename T>
-struct DeleteArrayFunctor
-{
- bool operator()(T* ptr) const
- {
- delete[] ptr;
- return true;
- }
-};
-
-// CopyNewPointer is a simple helper which accepts a pointer, and
-// returns a new pointer built with the copy constructor. Example:
-//
-// transform(in.begin(), in.end(), out.end(), CopyNewPointer());
-
-struct CopyNewPointer
-{
- template<typename T> T* operator()(const T* ptr) const
- {
- return new T(*ptr);
- }
-};
-
-template<typename T, typename ALLOC>
-void delete_and_clear(std::list<T*, ALLOC>& list)
-{
- std::for_each(list.begin(), list.end(), DeletePointer());
- list.clear();
-}
-
-template<typename T, typename ALLOC>
-void delete_and_clear(std::vector<T*, ALLOC>& vector)
-{
- std::for_each(vector.begin(), vector.end(), DeletePointer());
- vector.clear();
-}
-
-template<typename T, typename COMPARE, typename ALLOC>
-void delete_and_clear(std::set<T*, COMPARE, ALLOC>& set)
-{
- std::for_each(set.begin(), set.end(), DeletePointer());
- set.clear();
-}
-
-template<typename K, typename V, typename COMPARE, typename ALLOC>
-void delete_and_clear(std::map<K, V*, COMPARE, ALLOC>& map)
-{
- std::for_each(map.begin(), map.end(), DeletePairedPointer());
- map.clear();
-}
-
-template<typename T>
-void delete_and_clear(T*& ptr)
-{
- delete ptr;
- ptr = NULL;
-}
-
-
-template<typename T>
-void delete_and_clear_array(T*& ptr)
-{
- delete[] ptr;
- ptr = NULL;
-}
-
-// Simple function to help with finding pointers in maps.
-// For example:
-// typedef map_t;
-// std::map<int, const char*> foo;
-// foo[18] = "there";
-// foo[2] = "hello";
-// const char* bar = get_ptr_in_map(foo, 2); // bar -> "hello"
-// const char* baz = get_ptr_in_map(foo, 3); // baz == NULL
-template <typename K, typename T>
-inline T* get_ptr_in_map(const std::map<K,T*>& inmap, const K& key)
-{
- // Typedef here avoids warnings because of new c++ naming rules.
- typedef typename std::map<K,T*>::const_iterator map_iter;
- map_iter iter = inmap.find(key);
- if(iter == inmap.end())
- {
- return NULL;
- }
- else
- {
- return iter->second;
- }
-};
-
-// helper function which returns true if key is in inmap.
-template <typename K, typename T>
-inline bool is_in_map(const std::map<K,T>& inmap, const K& key)
-{
- if(inmap.find(key) == inmap.end())
- {
- return false;
- }
- else
- {
- return true;
- }
-}
-
-// Similar to get_ptr_in_map, but for any type with a valid T(0) constructor.
-// To replace LLSkipMap getIfThere, use:
-// get_if_there(map, key, 0)
-// WARNING: Make sure default_value (generally 0) is not a valid map entry!
-template <typename K, typename T>
-inline T get_if_there(const std::map<K,T>& inmap, const K& key, T default_value)
-{
- // Typedef here avoids warnings because of new c++ naming rules.
- typedef typename std::map<K,T>::const_iterator map_iter;
- map_iter iter = inmap.find(key);
- if(iter == inmap.end())
- {
- return default_value;
- }
- else
- {
- return iter->second;
- }
-};
-
-// Useful for replacing the removeObj() functionality of LLDynamicArray
-// Example:
-// for (std::vector<T>::iterator iter = mList.begin(); iter != mList.end(); )
-// {
-// if ((*iter)->isMarkedForRemoval())
-// iter = vector_replace_with_last(mList, iter);
-// else
-// ++iter;
-// }
-template <typename T>
-inline typename std::vector<T>::iterator vector_replace_with_last(std::vector<T>& invec, typename std::vector<T>::iterator iter)
-{
- typename std::vector<T>::iterator last = invec.end(); --last;
- if (iter == invec.end())
- {
- return iter;
- }
- else if (iter == last)
- {
- invec.pop_back();
- return invec.end();
- }
- else
- {
- *iter = *last;
- invec.pop_back();
- return iter;
- }
-};
-
-// Example:
-// vector_replace_with_last(mList, x);
-template <typename T>
-inline bool vector_replace_with_last(std::vector<T>& invec, const T& val)
-{
- typename std::vector<T>::iterator iter = std::find(invec.begin(), invec.end(), val);
- if (iter != invec.end())
- {
- typename std::vector<T>::iterator last = invec.end(); --last;
- *iter = *last;
- invec.pop_back();
- return true;
- }
- return false;
-}
-
-// Append N elements to the vector and return a pointer to the first new element.
-template <typename T>
-inline T* vector_append(std::vector<T>& invec, S32 N)
-{
- U32 sz = invec.size();
- invec.resize(sz+N);
- return &(invec[sz]);
-}
-
-// call function f to n members starting at first. similar to std::for_each
-template <class InputIter, class Size, class Function>
-Function ll_for_n(InputIter first, Size n, Function f)
-{
- for ( ; n > 0; --n, ++first)
- f(*first);
- return f;
-}
-
-// copy first to result n times, incrementing each as we go
-template <class InputIter, class Size, class OutputIter>
-OutputIter ll_copy_n(InputIter first, Size n, OutputIter result)
-{
- for ( ; n > 0; --n, ++result, ++first)
- *result = *first;
- return result;
-}
-
-// set *result = op(*f) for n elements of f
-template <class InputIter, class OutputIter, class Size, class UnaryOp>
-OutputIter ll_transform_n(
- InputIter first,
- Size n,
- OutputIter result,
- UnaryOp op)
-{
- for ( ; n > 0; --n, ++result, ++first)
- *result = op(*first);
- return result;
-}
-
-
-
-/*
- *
- * Copyright (c) 1994
- * Hewlett-Packard Company
- *
- * Permission to use, copy, modify, distribute and sell this software
- * and its documentation for any purpose is hereby granted without fee,
- * provided that the above copyright notice appear in all copies and
- * that both that copyright notice and this permission notice appear
- * in supporting documentation. Hewlett-Packard Company makes no
- * representations about the suitability of this software for any
- * purpose. It is provided "as is" without express or implied warranty.
- *
- *
- * Copyright (c) 1996-1998
- * Silicon Graphics Computer Systems, Inc.
- *
- * Permission to use, copy, modify, distribute and sell this software
- * and its documentation for any purpose is hereby granted without fee,
- * provided that the above copyright notice appear in all copies and
- * that both that copyright notice and this permission notice appear
- * in supporting documentation. Silicon Graphics makes no
- * representations about the suitability of this software for any
- * purpose. It is provided "as is" without express or implied warranty.
- */
-
-
-// helper to deal with the fact that MSDev does not package
-// select... with the stl. Look up usage on the sgi website.
-
-template <class _Pair>
-struct _LLSelect1st
-{
- const auto& operator()(const _Pair& __x) const {
- return __x.first;
- }
-};
-
-template <class _Pair>
-struct _LLSelect2nd
-{
- const auto& operator()(const _Pair& __x) const {
- return __x.second;
- }
-};
-
-template <class _Pair> struct llselect1st : public _LLSelect1st<_Pair> {};
-template <class _Pair> struct llselect2nd : public _LLSelect2nd<_Pair> {};
-
-// helper to deal with the fact that MSDev does not package
-// compose... with the stl. Look up usage on the sgi website.
-
-template <class _Operation1, class _Operation2>
-class ll_unary_compose
-{
-protected:
- _Operation1 __op1;
- _Operation2 __op2;
-public:
- ll_unary_compose(const _Operation1& __x, const _Operation2& __y)
- : __op1(__x), __op2(__y) {}
- template <typename _Op2Arg>
- auto
- operator()(const _Op2Arg& __x) const {
- return __op1(__op2(__x));
- }
-};
-
-template <class _Operation1, class _Operation2>
-inline ll_unary_compose<_Operation1,_Operation2>
-llcompose1(const _Operation1& __op1, const _Operation2& __op2)
-{
- return ll_unary_compose<_Operation1,_Operation2>(__op1, __op2);
-}
-
-template <class _Operation1, class _Operation2, class _Operation3>
-class ll_binary_compose
-{
-protected:
- _Operation1 _M_op1;
- _Operation2 _M_op2;
- _Operation3 _M_op3;
-public:
- ll_binary_compose(const _Operation1& __x, const _Operation2& __y,
- const _Operation3& __z)
- : _M_op1(__x), _M_op2(__y), _M_op3(__z) { }
- template<typename OP2ARG>
- auto
- operator()(const OP2ARG& __x) const {
- return _M_op1(_M_op2(__x), _M_op3(__x));
- }
-};
-
-template <class _Operation1, class _Operation2, class _Operation3>
-inline ll_binary_compose<_Operation1, _Operation2, _Operation3>
-llcompose2(const _Operation1& __op1, const _Operation2& __op2,
- const _Operation3& __op3)
-{
- return ll_binary_compose<_Operation1,_Operation2,_Operation3>
- (__op1, __op2, __op3);
-}
-
-// helpers to deal with the fact that MSDev does not package
-// bind... with the stl. Again, this is from sgi.
-template <class _Operation, typename _Arg1>
-class llbinder1st
-{
-protected:
- _Operation op;
- _Arg1 value;
-public:
- llbinder1st(const _Operation& __x, const _Arg1& __y)
- : op(__x), value(__y) {}
- template <typename _Arg2>
- auto
- operator()(const _Arg2& __x) const {
- return op(value, __x);
- }
-};
-
-template <class _Operation, class _Tp>
-inline auto
-llbind1st(const _Operation& __oper, const _Tp& __x)
-{
- return llbinder1st<_Operation, _Tp>(__oper, __x);
-}
-
-template <class _Operation, typename _Arg2>
-class llbinder2nd
-{
-protected:
- _Operation op;
- _Arg2 value;
-public:
- llbinder2nd(const _Operation& __x,
- const _Arg2& __y)
- : op(__x), value(__y) {}
- template <typename _Arg1>
- auto
- operator()(const _Arg1& __x) const {
- return op(__x, value);
- }
-};
-
-template <class _Operation, class _Tp>
-inline auto
-llbind2nd(const _Operation& __oper, const _Tp& __x)
-{
- return llbinder2nd<_Operation, _Tp>(__oper, __x);
-}
-
-/**
- * Compare std::type_info* pointers a la std::less. We break this out as a
- * separate function for use in two different std::less specializations.
- */
-inline
-bool before(const std::type_info* lhs, const std::type_info* rhs)
-{
-#if LL_LINUX && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 4))
- // If we're building on Linux with gcc, and it's either gcc 3.x or
- // 4.{0,1,2,3}, then we have to use a workaround. Note that we use gcc on
- // Mac too, and some people build with gcc on Windows (cygwin or mingw).
- // On Linux, different load modules may produce different type_info*
- // pointers for the same type. Have to compare name strings to get good
- // results.
- return strcmp(lhs->name(), rhs->name()) < 0;
-#else // not Linux, or gcc 4.4+
- // Just use before(), as we normally would
- return lhs->before(*rhs);
-#endif
-}
-
-/**
- * Specialize std::less<std::type_info*> to use std::type_info::before().
- * See MAINT-1175. It is NEVER a good idea to directly compare std::type_info*
- * because, on Linux, you might get different std::type_info* pointers for the
- * same type (from different load modules)!
- */
-namespace std
-{
- template <>
- struct less<const std::type_info*>
- {
- bool operator()(const std::type_info* lhs, const std::type_info* rhs) const
- {
- return before(lhs, rhs);
- }
- };
-
- template <>
- struct less<std::type_info*>
- {
- bool operator()(std::type_info* lhs, std::type_info* rhs) const
- {
- return before(lhs, rhs);
- }
- };
-} // std
-
-
-/**
- * Implementation for ll_template_cast() (q.v.).
- *
- * Default implementation: trying to cast two completely unrelated types
- * returns 0. Typically you'd specify T and U as pointer types, but in fact T
- * can be any type that can be initialized with 0.
- */
-template <typename T, typename U>
-struct ll_template_cast_impl
-{
- T operator()(U)
- {
- return 0;
- }
-};
-
-/**
- * ll_template_cast<T>(some_value) is for use in a template function when
- * some_value might be of arbitrary type, but you want to recognize type T
- * specially.
- *
- * It's designed for use with pointer types. Example:
- * @code
- * struct SpecialClass
- * {
- * void someMethod(const std::string&) const;
- * };
- *
- * template <class REALCLASS>
- * void somefunc(const REALCLASS& instance)
- * {
- * const SpecialClass* ptr = ll_template_cast<const SpecialClass*>(&instance);
- * if (ptr)
- * {
- * ptr->someMethod("Call method only available on SpecialClass");
- * }
- * }
- * @endcode
- *
- * Why is this better than dynamic_cast<>? Because unless OtherClass is
- * polymorphic, the following won't even compile (gcc 4.0.1):
- * @code
- * OtherClass other;
- * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&other);
- * @endcode
- * to say nothing of this:
- * @code
- * void function(int);
- * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&function);
- * @endcode
- * ll_template_cast handles these kinds of cases by returning 0.
- */
-template <typename T, typename U>
-T ll_template_cast(U value)
-{
- return ll_template_cast_impl<T, U>()(value);
-}
-
-/**
- * Implementation for ll_template_cast() (q.v.).
- *
- * Implementation for identical types: return same value.
- */
-template <typename T>
-struct ll_template_cast_impl<T, T>
-{
- T operator()(T value)
- {
- return value;
- }
-};
-
-/**
- * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of
- * type @c source, <tt>ll_template_cast<dest>(s)</tt> will return @c s --
- * presuming that @c source can be converted to @c dest by the normal rules of
- * C++.
- *
- * By default, <tt>ll_template_cast<dest>(s)</tt> will return 0 unless @c s's
- * type is literally identical to @c dest. (This is because of the
- * straightforward application of template specialization rules.) That can
- * lead to surprising results, e.g.:
- *
- * @code
- * Foo myFoo;
- * const Foo* fooptr = ll_template_cast<const Foo*>(&myFoo);
- * @endcode
- *
- * Here @c fooptr will be 0 because <tt>&myFoo</tt> is of type <tt>Foo*</tt>
- * -- @em not <tt>const Foo*</tt>. (Declaring <tt>const Foo myFoo;</tt> would
- * force the compiler to do the right thing.)
- *
- * More disappointingly:
- * @code
- * struct Base {};
- * struct Subclass: public Base {};
- * Subclass object;
- * Base* ptr = ll_template_cast<Base*>(&object);
- * @endcode
- *
- * Here @c ptr will be 0 because <tt>&object</tt> is of type
- * <tt>Subclass*</tt> rather than <tt>Base*</tt>. We @em want this cast to
- * succeed, but without our help ll_template_cast can't recognize it.
- *
- * The following would suffice:
- * @code
- * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*);
- * ...
- * Base* ptr = ll_template_cast<Base*>(&object);
- * @endcode
- *
- * However, as noted earlier, this is easily fooled:
- * @code
- * const Base* ptr = ll_template_cast<const Base*>(&object);
- * @endcode
- * would still produce 0 because we haven't yet seen:
- * @code
- * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*);
- * @endcode
- *
- * @TODO
- * This macro should use Boost type_traits facilities for stripping and
- * re-adding @c const and @c volatile qualifiers so that invoking
- * LL_TEMPLATE_CONVERTIBLE(dest, source) will automatically generate all
- * permitted permutations. It's really not fair to the coder to require
- * separate:
- * @code
- * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*);
- * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*);
- * LL_TEMPLATE_CONVERTIBLE(const Base*, const Subclass*);
- * @endcode
- *
- * (Naturally we omit <tt>LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*)</tt>
- * because that's not permitted by normal C++ assignment anyway.)
- */
-#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \
-template <> \
-struct ll_template_cast_impl<DEST, SOURCE> \
-{ \
- DEST operator()(SOURCE wrapper) \
- { \
- return wrapper; \
- } \
-}
-
-
-#endif // LL_LLSTL_H
+/** + * @file llstl.h + * @brief helper object & functions for use with the stl. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSTL_H +#define LL_LLSTL_H + +#include "stdtypes.h" +#include <functional> +#include <algorithm> +#include <map> +#include <vector> +#include <list> +#include <set> +#include <typeinfo> + +#ifdef LL_LINUX +// <ND> For strcmp +#include <string.h> +#endif +// Use to compare the first element only of a pair +// e.g. typedef std::set<std::pair<int, Data*>, compare_pair<int, Data*> > some_pair_set_t; +template <typename T1, typename T2> +struct compare_pair_first +{ + bool operator()(const std::pair<T1, T2>& a, const std::pair<T1, T2>& b) const + { + return a.first < b.first; + } +}; + +template <typename T1, typename T2> +struct compare_pair_greater +{ + bool operator()(const std::pair<T1, T2>& a, const std::pair<T1, T2>& b) const + { + if (!(a.first < b.first)) + return true; + else if (!(b.first < a.first)) + return false; + else + return !(a.second < b.second); + } +}; + +// Use to compare the contents of two pointers (e.g. std::string*) +template <typename T> +struct compare_pointer_contents +{ + typedef const T* Tptr; + bool operator()(const Tptr& a, const Tptr& b) const + { + return *a < *b; + } +}; + +// DeletePointer is a simple helper for deleting all pointers in a container. +// The general form is: +// +// std::for_each(cont.begin(), cont.end(), DeletePointer()); +// somemap.clear(); +// +// Don't forget to clear()! + +struct DeletePointer +{ + template<typename T> void operator()(T* ptr) const + { + delete ptr; + } +}; +struct DeletePointerArray +{ + template<typename T> void operator()(T* ptr) const + { + delete[] ptr; + } +}; + +// DeletePairedPointer is a simple helper for deleting all pointers in a map. +// The general form is: +// +// std::for_each(somemap.begin(), somemap.end(), DeletePairedPointer()); +// somemap.clear(); // Don't leave dangling pointers around + +struct DeletePairedPointer +{ + template<typename T> void operator()(T &ptr) const + { + delete ptr.second; + ptr.second = NULL; + } +}; +struct DeletePairedPointerArray +{ + template<typename T> void operator()(T &ptr) const + { + delete[] ptr.second; + ptr.second = NULL; + } +}; + + +// Alternate version of the above so that has a more cumbersome +// syntax, but it can be used with compositional functors. +// NOTE: The functor retuns a bool because msdev bombs during the +// composition if you return void. Once we upgrade to a newer +// compiler, the second unary_function template parameter can be set +// to void. +// +// Here's a snippet showing how you use this object: +// +// typedef std::map<int, widget*> map_type; +// map_type widget_map; +// ... // add elements +// // delete them all +// for_each(widget_map.begin(), +// widget_map.end(), +// llcompose1(DeletePointerFunctor<widget>(), +// llselect2nd<map_type::value_type>())); + +template<typename T> +struct DeletePointerFunctor +{ + bool operator()(T* ptr) const + { + delete ptr; + return true; + } +}; + +// See notes about DeleteArray for why you should consider avoiding this. +template<typename T> +struct DeleteArrayFunctor +{ + bool operator()(T* ptr) const + { + delete[] ptr; + return true; + } +}; + +// CopyNewPointer is a simple helper which accepts a pointer, and +// returns a new pointer built with the copy constructor. Example: +// +// transform(in.begin(), in.end(), out.end(), CopyNewPointer()); + +struct CopyNewPointer +{ + template<typename T> T* operator()(const T* ptr) const + { + return new T(*ptr); + } +}; + +template<typename T, typename ALLOC> +void delete_and_clear(std::list<T*, ALLOC>& list) +{ + std::for_each(list.begin(), list.end(), DeletePointer()); + list.clear(); +} + +template<typename T, typename ALLOC> +void delete_and_clear(std::vector<T*, ALLOC>& vector) +{ + std::for_each(vector.begin(), vector.end(), DeletePointer()); + vector.clear(); +} + +template<typename T, typename COMPARE, typename ALLOC> +void delete_and_clear(std::set<T*, COMPARE, ALLOC>& set) +{ + std::for_each(set.begin(), set.end(), DeletePointer()); + set.clear(); +} + +template<typename K, typename V, typename COMPARE, typename ALLOC> +void delete_and_clear(std::map<K, V*, COMPARE, ALLOC>& map) +{ + std::for_each(map.begin(), map.end(), DeletePairedPointer()); + map.clear(); +} + +template<typename T> +void delete_and_clear(T*& ptr) +{ + delete ptr; + ptr = NULL; +} + + +template<typename T> +void delete_and_clear_array(T*& ptr) +{ + delete[] ptr; + ptr = NULL; +} + +// Simple function to help with finding pointers in maps. +// For example: +// typedef map_t; +// std::map<int, const char*> foo; +// foo[18] = "there"; +// foo[2] = "hello"; +// const char* bar = get_ptr_in_map(foo, 2); // bar -> "hello" +// const char* baz = get_ptr_in_map(foo, 3); // baz == NULL +template <typename K, typename T> +inline T* get_ptr_in_map(const std::map<K,T*>& inmap, const K& key) +{ + // Typedef here avoids warnings because of new c++ naming rules. + typedef typename std::map<K,T*>::const_iterator map_iter; + map_iter iter = inmap.find(key); + if(iter == inmap.end()) + { + return NULL; + } + else + { + return iter->second; + } +}; + +// helper function which returns true if key is in inmap. +template <typename K, typename T> +inline bool is_in_map(const std::map<K,T>& inmap, const K& key) +{ + if(inmap.find(key) == inmap.end()) + { + return false; + } + else + { + return true; + } +} + +// Similar to get_ptr_in_map, but for any type with a valid T(0) constructor. +// To replace LLSkipMap getIfThere, use: +// get_if_there(map, key, 0) +// WARNING: Make sure default_value (generally 0) is not a valid map entry! +template <typename K, typename T> +inline T get_if_there(const std::map<K,T>& inmap, const K& key, T default_value) +{ + // Typedef here avoids warnings because of new c++ naming rules. + typedef typename std::map<K,T>::const_iterator map_iter; + map_iter iter = inmap.find(key); + if(iter == inmap.end()) + { + return default_value; + } + else + { + return iter->second; + } +}; + +// Useful for replacing the removeObj() functionality of LLDynamicArray +// Example: +// for (std::vector<T>::iterator iter = mList.begin(); iter != mList.end(); ) +// { +// if ((*iter)->isMarkedForRemoval()) +// iter = vector_replace_with_last(mList, iter); +// else +// ++iter; +// } +template <typename T> +inline typename std::vector<T>::iterator vector_replace_with_last(std::vector<T>& invec, typename std::vector<T>::iterator iter) +{ + typename std::vector<T>::iterator last = invec.end(); --last; + if (iter == invec.end()) + { + return iter; + } + else if (iter == last) + { + invec.pop_back(); + return invec.end(); + } + else + { + *iter = *last; + invec.pop_back(); + return iter; + } +}; + +// Example: +// vector_replace_with_last(mList, x); +template <typename T> +inline bool vector_replace_with_last(std::vector<T>& invec, const T& val) +{ + typename std::vector<T>::iterator iter = std::find(invec.begin(), invec.end(), val); + if (iter != invec.end()) + { + typename std::vector<T>::iterator last = invec.end(); --last; + *iter = *last; + invec.pop_back(); + return true; + } + return false; +} + +// Append N elements to the vector and return a pointer to the first new element. +template <typename T> +inline T* vector_append(std::vector<T>& invec, S32 N) +{ + U32 sz = invec.size(); + invec.resize(sz+N); + return &(invec[sz]); +} + +// call function f to n members starting at first. similar to std::for_each +template <class InputIter, class Size, class Function> +Function ll_for_n(InputIter first, Size n, Function f) +{ + for ( ; n > 0; --n, ++first) + f(*first); + return f; +} + +// copy first to result n times, incrementing each as we go +template <class InputIter, class Size, class OutputIter> +OutputIter ll_copy_n(InputIter first, Size n, OutputIter result) +{ + for ( ; n > 0; --n, ++result, ++first) + *result = *first; + return result; +} + +// set *result = op(*f) for n elements of f +template <class InputIter, class OutputIter, class Size, class UnaryOp> +OutputIter ll_transform_n( + InputIter first, + Size n, + OutputIter result, + UnaryOp op) +{ + for ( ; n > 0; --n, ++result, ++first) + *result = op(*first); + return result; +} + + + +/* + * + * Copyright (c) 1994 + * Hewlett-Packard Company + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Hewlett-Packard Company makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * + * Copyright (c) 1996-1998 + * Silicon Graphics Computer Systems, Inc. + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Silicon Graphics makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + */ + + +// helper to deal with the fact that MSDev does not package +// select... with the stl. Look up usage on the sgi website. + +template <class _Pair> +struct _LLSelect1st +{ + const auto& operator()(const _Pair& __x) const { + return __x.first; + } +}; + +template <class _Pair> +struct _LLSelect2nd +{ + const auto& operator()(const _Pair& __x) const { + return __x.second; + } +}; + +template <class _Pair> struct llselect1st : public _LLSelect1st<_Pair> {}; +template <class _Pair> struct llselect2nd : public _LLSelect2nd<_Pair> {}; + +// helper to deal with the fact that MSDev does not package +// compose... with the stl. Look up usage on the sgi website. + +template <class _Operation1, class _Operation2> +class ll_unary_compose +{ +protected: + _Operation1 __op1; + _Operation2 __op2; +public: + ll_unary_compose(const _Operation1& __x, const _Operation2& __y) + : __op1(__x), __op2(__y) {} + template <typename _Op2Arg> + auto + operator()(const _Op2Arg& __x) const { + return __op1(__op2(__x)); + } +}; + +template <class _Operation1, class _Operation2> +inline ll_unary_compose<_Operation1,_Operation2> +llcompose1(const _Operation1& __op1, const _Operation2& __op2) +{ + return ll_unary_compose<_Operation1,_Operation2>(__op1, __op2); +} + +template <class _Operation1, class _Operation2, class _Operation3> +class ll_binary_compose +{ +protected: + _Operation1 _M_op1; + _Operation2 _M_op2; + _Operation3 _M_op3; +public: + ll_binary_compose(const _Operation1& __x, const _Operation2& __y, + const _Operation3& __z) + : _M_op1(__x), _M_op2(__y), _M_op3(__z) { } + template<typename OP2ARG> + auto + operator()(const OP2ARG& __x) const { + return _M_op1(_M_op2(__x), _M_op3(__x)); + } +}; + +template <class _Operation1, class _Operation2, class _Operation3> +inline ll_binary_compose<_Operation1, _Operation2, _Operation3> +llcompose2(const _Operation1& __op1, const _Operation2& __op2, + const _Operation3& __op3) +{ + return ll_binary_compose<_Operation1,_Operation2,_Operation3> + (__op1, __op2, __op3); +} + +// helpers to deal with the fact that MSDev does not package +// bind... with the stl. Again, this is from sgi. +template <class _Operation, typename _Arg1> +class llbinder1st +{ +protected: + _Operation op; + _Arg1 value; +public: + llbinder1st(const _Operation& __x, const _Arg1& __y) + : op(__x), value(__y) {} + template <typename _Arg2> + auto + operator()(const _Arg2& __x) const { + return op(value, __x); + } +}; + +template <class _Operation, class _Tp> +inline auto +llbind1st(const _Operation& __oper, const _Tp& __x) +{ + return llbinder1st<_Operation, _Tp>(__oper, __x); +} + +template <class _Operation, typename _Arg2> +class llbinder2nd +{ +protected: + _Operation op; + _Arg2 value; +public: + llbinder2nd(const _Operation& __x, + const _Arg2& __y) + : op(__x), value(__y) {} + template <typename _Arg1> + auto + operator()(const _Arg1& __x) const { + return op(__x, value); + } +}; + +template <class _Operation, class _Tp> +inline auto +llbind2nd(const _Operation& __oper, const _Tp& __x) +{ + return llbinder2nd<_Operation, _Tp>(__oper, __x); +} + +/** + * Compare std::type_info* pointers a la std::less. We break this out as a + * separate function for use in two different std::less specializations. + */ +inline +bool before(const std::type_info* lhs, const std::type_info* rhs) +{ +#if LL_LINUX && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 4)) + // If we're building on Linux with gcc, and it's either gcc 3.x or + // 4.{0,1,2,3}, then we have to use a workaround. Note that we use gcc on + // Mac too, and some people build with gcc on Windows (cygwin or mingw). + // On Linux, different load modules may produce different type_info* + // pointers for the same type. Have to compare name strings to get good + // results. + return strcmp(lhs->name(), rhs->name()) < 0; +#else // not Linux, or gcc 4.4+ + // Just use before(), as we normally would + return lhs->before(*rhs); +#endif +} + +/** + * Specialize std::less<std::type_info*> to use std::type_info::before(). + * See MAINT-1175. It is NEVER a good idea to directly compare std::type_info* + * because, on Linux, you might get different std::type_info* pointers for the + * same type (from different load modules)! + */ +namespace std +{ + template <> + struct less<const std::type_info*> + { + bool operator()(const std::type_info* lhs, const std::type_info* rhs) const + { + return before(lhs, rhs); + } + }; + + template <> + struct less<std::type_info*> + { + bool operator()(std::type_info* lhs, std::type_info* rhs) const + { + return before(lhs, rhs); + } + }; +} // std + + +/** + * Implementation for ll_template_cast() (q.v.). + * + * Default implementation: trying to cast two completely unrelated types + * returns 0. Typically you'd specify T and U as pointer types, but in fact T + * can be any type that can be initialized with 0. + */ +template <typename T, typename U> +struct ll_template_cast_impl +{ + T operator()(U) + { + return 0; + } +}; + +/** + * ll_template_cast<T>(some_value) is for use in a template function when + * some_value might be of arbitrary type, but you want to recognize type T + * specially. + * + * It's designed for use with pointer types. Example: + * @code + * struct SpecialClass + * { + * void someMethod(const std::string&) const; + * }; + * + * template <class REALCLASS> + * void somefunc(const REALCLASS& instance) + * { + * const SpecialClass* ptr = ll_template_cast<const SpecialClass*>(&instance); + * if (ptr) + * { + * ptr->someMethod("Call method only available on SpecialClass"); + * } + * } + * @endcode + * + * Why is this better than dynamic_cast<>? Because unless OtherClass is + * polymorphic, the following won't even compile (gcc 4.0.1): + * @code + * OtherClass other; + * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&other); + * @endcode + * to say nothing of this: + * @code + * void function(int); + * SpecialClass* ptr = dynamic_cast<SpecialClass*>(&function); + * @endcode + * ll_template_cast handles these kinds of cases by returning 0. + */ +template <typename T, typename U> +T ll_template_cast(U value) +{ + return ll_template_cast_impl<T, U>()(value); +} + +/** + * Implementation for ll_template_cast() (q.v.). + * + * Implementation for identical types: return same value. + */ +template <typename T> +struct ll_template_cast_impl<T, T> +{ + T operator()(T value) + { + return value; + } +}; + +/** + * LL_TEMPLATE_CONVERTIBLE(dest, source) asserts that, for a value @c s of + * type @c source, <tt>ll_template_cast<dest>(s)</tt> will return @c s -- + * presuming that @c source can be converted to @c dest by the normal rules of + * C++. + * + * By default, <tt>ll_template_cast<dest>(s)</tt> will return 0 unless @c s's + * type is literally identical to @c dest. (This is because of the + * straightforward application of template specialization rules.) That can + * lead to surprising results, e.g.: + * + * @code + * Foo myFoo; + * const Foo* fooptr = ll_template_cast<const Foo*>(&myFoo); + * @endcode + * + * Here @c fooptr will be 0 because <tt>&myFoo</tt> is of type <tt>Foo*</tt> + * -- @em not <tt>const Foo*</tt>. (Declaring <tt>const Foo myFoo;</tt> would + * force the compiler to do the right thing.) + * + * More disappointingly: + * @code + * struct Base {}; + * struct Subclass: public Base {}; + * Subclass object; + * Base* ptr = ll_template_cast<Base*>(&object); + * @endcode + * + * Here @c ptr will be 0 because <tt>&object</tt> is of type + * <tt>Subclass*</tt> rather than <tt>Base*</tt>. We @em want this cast to + * succeed, but without our help ll_template_cast can't recognize it. + * + * The following would suffice: + * @code + * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); + * ... + * Base* ptr = ll_template_cast<Base*>(&object); + * @endcode + * + * However, as noted earlier, this is easily fooled: + * @code + * const Base* ptr = ll_template_cast<const Base*>(&object); + * @endcode + * would still produce 0 because we haven't yet seen: + * @code + * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); + * @endcode + * + * @TODO + * This macro should use Boost type_traits facilities for stripping and + * re-adding @c const and @c volatile qualifiers so that invoking + * LL_TEMPLATE_CONVERTIBLE(dest, source) will automatically generate all + * permitted permutations. It's really not fair to the coder to require + * separate: + * @code + * LL_TEMPLATE_CONVERTIBLE(Base*, Subclass*); + * LL_TEMPLATE_CONVERTIBLE(const Base*, Subclass*); + * LL_TEMPLATE_CONVERTIBLE(const Base*, const Subclass*); + * @endcode + * + * (Naturally we omit <tt>LL_TEMPLATE_CONVERTIBLE(Base*, const Subclass*)</tt> + * because that's not permitted by normal C++ assignment anyway.) + */ +#define LL_TEMPLATE_CONVERTIBLE(DEST, SOURCE) \ +template <> \ +struct ll_template_cast_impl<DEST, SOURCE> \ +{ \ + DEST operator()(SOURCE wrapper) \ + { \ + return wrapper; \ + } \ +} + + +#endif // LL_LLSTL_H diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 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 <vector>
-
-#if LL_WINDOWS
-#include "llwin32headerslean.h"
-#include <winnls.h> // for WideCharToMultiByte
-#endif
-
-std::string ll_safe_string(const char* in)
-{
- if(in) return std::string(in);
- return std::string();
-}
-
-std::string ll_safe_string(const char* in, S32 maxlen)
-{
- if(in && maxlen > 0 ) return std::string(in, maxlen);
-
- return std::string();
-}
-
-bool is_char_hex(char hex)
-{
- if((hex >= '0') && (hex <= '9'))
- {
- return true;
- }
- else if((hex >= 'a') && (hex <='f'))
- {
- return true;
- }
- else if((hex >= 'A') && (hex <='F'))
- {
- return true;
- }
- return false; // uh - oh, not hex any more...
-}
-
-U8 hex_as_nybble(char hex)
-{
- if((hex >= '0') && (hex <= '9'))
- {
- return (U8)(hex - '0');
- }
- else if((hex >= 'a') && (hex <='f'))
- {
- return (U8)(10 + hex - 'a');
- }
- else if((hex >= 'A') && (hex <='F'))
- {
- return (U8)(10 + hex - 'A');
- }
- return 0; // uh - oh, not hex any more...
-}
-
-bool iswindividual(llwchar elem)
-{
- U32 cur_char = (U32)elem;
- bool result = false;
- if (0x2E80<= cur_char && cur_char <= 0x9FFF)
- {
- result = true;
- }
- else if (0xAC00<= cur_char && cur_char <= 0xD7A0 )
- {
- result = true;
- }
- else if (0xF900<= cur_char && cur_char <= 0xFA60 )
- {
- result = true;
- }
- return result;
-}
-
-bool _read_file_into_string(std::string& str, const std::string& filename)
-{
- llifstream ifs(filename.c_str(), llifstream::binary);
- if (!ifs.is_open())
- {
- LL_INFOS() << "Unable to open file " << filename << LL_ENDL;
- return false;
- }
-
- std::ostringstream oss;
-
- oss << ifs.rdbuf();
- str = oss.str();
- ifs.close();
- return true;
-}
-
-
-
-
-// See http://www.unicode.org/Public/BETA/CVTUTF-1-2/ConvertUTF.c
-// for the Unicode implementation - this doesn't match because it was written before finding
-// it.
-
-
-std::ostream& operator<<(std::ostream &s, const LLWString &wstr)
-{
- std::string utf8_str = wstring_to_utf8str(wstr);
- s << utf8_str;
- return s;
-}
-
-std::string rawstr_to_utf8(const std::string& raw)
-{
- LLWString wstr(utf8str_to_wstring(raw));
- return wstring_to_utf8str(wstr);
-}
-
-std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars)
-{
- U32 cur_char = (U32)in_char;
- char* base = outchars;
- if (cur_char < 0x80)
- {
- *outchars++ = (U8)cur_char;
- }
- else if (cur_char < 0x800)
- {
- *outchars++ = 0xC0 | (cur_char >> 6);
- *outchars++ = 0x80 | (cur_char & 0x3F);
- }
- else if (cur_char < 0x10000)
- {
- *outchars++ = 0xE0 | (cur_char >> 12);
- *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
- *outchars++ = 0x80 | (cur_char & 0x3F);
- }
- else if (cur_char < 0x200000)
- {
- *outchars++ = 0xF0 | (cur_char >> 18);
- *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F);
- *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
- *outchars++ = 0x80 | (cur_char & 0x3F);
- }
- else if (cur_char < 0x4000000)
- {
- *outchars++ = 0xF8 | (cur_char >> 24);
- *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F);
- *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F);
- *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
- *outchars++ = 0x80 | (cur_char & 0x3F);
- }
- else if (cur_char < 0x80000000)
- {
- *outchars++ = 0xFC | (cur_char >> 30);
- *outchars++ = 0x80 | ((cur_char >> 24) & 0x3F);
- *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F);
- *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F);
- *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
- *outchars++ = 0x80 | (cur_char & 0x3F);
- }
- else
- {
- LL_WARNS() << "Invalid Unicode character " << cur_char << "!" << LL_ENDL;
- *outchars++ = LL_UNKNOWN_CHAR;
- }
- return outchars - base;
-}
-
-auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar)
-{
- const U16* base = inchars;
- U16 cur_char = *inchars++;
- llwchar char32 = cur_char;
- if ((cur_char >= 0xD800) && (cur_char <= 0xDFFF))
- {
- // Surrogates
- char32 = ((llwchar)(cur_char - 0xD800)) << 10;
- cur_char = *inchars++;
- char32 += (llwchar)(cur_char - 0xDC00) + 0x0010000UL;
- }
- else
- {
- char32 = (llwchar)cur_char;
- }
- *outchar = char32;
- return inchars - base;
-}
-
-llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len)
-{
- llutf16string out;
-
- S32 i = 0;
- while (i < len)
- {
- U32 cur_char = utf32str[i];
- if (cur_char > 0xFFFF)
- {
- out += (0xD7C0 + (cur_char >> 10));
- out += (0xDC00 | (cur_char & 0x3FF));
- }
- else
- {
- out += cur_char;
- }
- i++;
- }
- return out;
-}
-
-llutf16string utf8str_to_utf16str( const char* utf8str, size_t len )
-{
- LLWString wstr = utf8str_to_wstring ( utf8str, len );
- return wstring_to_utf16str ( wstr );
-}
-
-LLWString utf16str_to_wstring(const U16* utf16str, size_t len)
-{
- LLWString wout;
- if (len == 0) return wout;
-
- S32 i = 0;
- const U16* chars16 = utf16str;
- while (i < len)
- {
- llwchar cur_char;
- i += utf16chars_to_wchar(chars16+i, &cur_char);
- wout += cur_char;
- }
- return wout;
-}
-
-// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string.
-S32 utf16str_wstring_length(const llutf16string &utf16str, const S32 utf16_len)
-{
- S32 surrogate_pairs = 0;
- // ... craziness to make gcc happy (llutf16string.c_str() is tweaked on linux):
- const U16 *const utf16_chars = &(*(utf16str.begin()));
- S32 i = 0;
- while (i < utf16_len)
- {
- const U16 c = utf16_chars[i++];
- if (c >= 0xD800 && c <= 0xDBFF) // See http://en.wikipedia.org/wiki/UTF-16
- { // Have first byte of a surrogate pair
- if (i >= utf16_len)
- {
- break;
- }
- const U16 d = utf16_chars[i];
- if (d >= 0xDC00 && d <= 0xDFFF)
- { // Have valid second byte of a surrogate pair
- surrogate_pairs++;
- i++;
- }
- }
- }
- return utf16_len - surrogate_pairs;
-}
-
-// Length in utf16string (UTF-16) of wlen wchars beginning at woffset.
-S32 wstring_utf16_length(const LLWString &wstr, const S32 woffset, const S32 wlen)
-{
- const S32 end = llmin((S32)wstr.length(), woffset + wlen);
- if (end < woffset)
- {
- return 0;
- }
- else
- {
- S32 length = end - woffset;
- for (S32 i = woffset; i < end; i++)
- {
- if (wstr[i] >= 0x10000)
- {
- length++;
- }
- }
- return length;
- }
-}
-
-// Given a wstring and an offset in it, returns the length as wstring (i.e.,
-// number of llwchars) of the longest substring that starts at the offset
-// and whose equivalent utf-16 string does not exceeds the given utf16_length.
-S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, bool *unaligned)
-{
- const auto end = wstr.length();
- bool u{ false };
- S32 n = woffset + utf16_length;
- S32 i = woffset;
- while (i < end)
- {
- if (wstr[i] >= 0x10000)
- {
- --n;
- }
- if (i >= n)
- {
- u = (i > n);
- break;
- }
- i++;
- }
- if (unaligned)
- {
- *unaligned = u;
- }
- return i - woffset;
-}
-
-S32 wchar_utf8_length(const llwchar wc)
-{
- if (wc < 0x80)
- {
- return 1;
- }
- else if (wc < 0x800)
- {
- return 2;
- }
- else if (wc < 0x10000)
- {
- return 3;
- }
- else if (wc < 0x200000)
- {
- return 4;
- }
- else if (wc < 0x4000000)
- {
- return 5;
- }
- else
- {
- return 6;
- }
-}
-
-std::string wchar_utf8_preview(const llwchar wc)
-{
- std::ostringstream oss;
- oss << std::hex << std::uppercase << (U32)wc;
-
- U8 out_bytes[8];
- U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes);
-
- if (size > 1)
- {
- oss << " [";
- for (U32 i = 0; i < size; ++i)
- {
- if (i)
- {
- oss << ", ";
- }
- oss << (int)out_bytes[i];
- }
- oss << "]";
- }
-
- return oss.str();
-}
-
-S32 wstring_utf8_length(const LLWString& wstr)
-{
- S32 len = 0;
- for (S32 i = 0; i < (S32)wstr.length(); i++)
- {
- len += wchar_utf8_length(wstr[i]);
- }
- return len;
-}
-
-LLWString utf8str_to_wstring(const char* utf8str, size_t len)
-{
- LLWString wout;
-
- S32 i = 0;
- while (i < len)
- {
- llwchar unichar;
- U8 cur_char = utf8str[i];
-
- if (cur_char < 0x80)
- {
- // Ascii character, just add it
- unichar = cur_char;
- }
- else
- {
- S32 cont_bytes = 0;
- if ((cur_char >> 5) == 0x6) // Two byte UTF8 -> 1 UTF32
- {
- unichar = (0x1F&cur_char);
- cont_bytes = 1;
- }
- else if ((cur_char >> 4) == 0xe) // Three byte UTF8 -> 1 UTF32
- {
- unichar = (0x0F&cur_char);
- cont_bytes = 2;
- }
- else if ((cur_char >> 3) == 0x1e) // Four byte UTF8 -> 1 UTF32
- {
- unichar = (0x07&cur_char);
- cont_bytes = 3;
- }
- else if ((cur_char >> 2) == 0x3e) // Five byte UTF8 -> 1 UTF32
- {
- unichar = (0x03&cur_char);
- cont_bytes = 4;
- }
- else if ((cur_char >> 1) == 0x7e) // Six byte UTF8 -> 1 UTF32
- {
- unichar = (0x01&cur_char);
- cont_bytes = 5;
- }
- else
- {
- wout += LL_UNKNOWN_CHAR;
- ++i;
- continue;
- }
-
- // Check that this character doesn't go past the end of the string
- auto end = (len < (i + cont_bytes)) ? len : (i + cont_bytes);
- do
- {
- ++i;
-
- cur_char = utf8str[i];
- if ( (cur_char >> 6) == 0x2 )
- {
- unichar <<= 6;
- unichar += (0x3F&cur_char);
- }
- else
- {
- // Malformed sequence - roll back to look at this as a new char
- unichar = LL_UNKNOWN_CHAR;
- --i;
- break;
- }
- } while(i < end);
-
- // Handle overlong characters and NULL characters
- if ( ((cont_bytes == 1) && (unichar < 0x80))
- || ((cont_bytes == 2) && (unichar < 0x800))
- || ((cont_bytes == 3) && (unichar < 0x10000))
- || ((cont_bytes == 4) && (unichar < 0x200000))
- || ((cont_bytes == 5) && (unichar < 0x4000000)) )
- {
- unichar = LL_UNKNOWN_CHAR;
- }
- }
-
- wout += unichar;
- ++i;
- }
- return wout;
-}
-
-std::string wstring_to_utf8str(const llwchar* utf32str, size_t len)
-{
- std::string out;
-
- S32 i = 0;
- while (i < len)
- {
- char tchars[8]; /* Flawfinder: ignore */
- auto n = wchar_to_utf8chars(utf32str[i], tchars);
- tchars[n] = 0;
- out += tchars;
- i++;
- }
- return out;
-}
-
-std::string utf16str_to_utf8str(const U16* utf16str, size_t len)
-{
- return wstring_to_utf8str(utf16str_to_wstring(utf16str, len));
-}
-
-std::string utf8str_trim(const std::string& utf8str)
-{
- LLWString wstr = utf8str_to_wstring(utf8str);
- LLWStringUtil::trim(wstr);
- return wstring_to_utf8str(wstr);
-}
-
-
-std::string utf8str_tolower(const std::string& utf8str)
-{
- LLWString out_str = utf8str_to_wstring(utf8str);
- LLWStringUtil::toLower(out_str);
- return wstring_to_utf8str(out_str);
-}
-
-
-S32 utf8str_compare_insensitive(const std::string& lhs, const std::string& rhs)
-{
- LLWString wlhs = utf8str_to_wstring(lhs);
- LLWString wrhs = utf8str_to_wstring(rhs);
- return LLWStringUtil::compareInsensitive(wlhs, wrhs);
-}
-
-std::string utf8str_truncate(const std::string& utf8str, const S32 max_len)
-{
- if (0 == max_len)
- {
- return std::string();
- }
- if ((S32)utf8str.length() <= max_len)
- {
- return utf8str;
- }
- else
- {
- S32 cur_char = max_len;
-
- // If we're ASCII, we don't need to do anything
- if ((U8)utf8str[cur_char] > 0x7f)
- {
- // If first two bits are (10), it's the tail end of a multibyte char. We need to shift back
- // to the first character
- while (0x80 == (0xc0 & utf8str[cur_char]))
- {
- cur_char--;
- // Keep moving forward until we hit the first char;
- if (cur_char == 0)
- {
- // Make sure we don't trash memory if we've got a bogus string.
- break;
- }
- }
- }
- // The byte index we're on is one we want to get rid of, so we only want to copy up to (cur_char-1) chars
- return utf8str.substr(0, cur_char);
- }
-}
-
-std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len)
-{
- if (0 == symbol_len)
- {
- return std::string();
- }
- if ((S32)utf8str.length() <= symbol_len)
- {
- return utf8str;
- }
- else
- {
- int len = 0, byteIndex = 0;
- const char* aStr = utf8str.c_str();
- size_t origSize = utf8str.size();
-
- for (byteIndex = 0; len < symbol_len && byteIndex < origSize; byteIndex++)
- {
- if ((aStr[byteIndex] & 0xc0) != 0x80)
- {
- len += 1;
- }
- }
- return utf8str.substr(0, byteIndex);
- }
-}
-
-std::string utf8str_substChar(
- const std::string& utf8str,
- const llwchar target_char,
- const llwchar replace_char)
-{
- LLWString wstr = utf8str_to_wstring(utf8str);
- LLWStringUtil::replaceChar(wstr, target_char, replace_char);
- //wstr = wstring_substChar(wstr, target_char, replace_char);
- return wstring_to_utf8str(wstr);
-}
-
-std::string utf8str_makeASCII(const std::string& utf8str)
-{
- LLWString wstr = utf8str_to_wstring(utf8str);
- LLWStringUtil::_makeASCII(wstr);
- return wstring_to_utf8str(wstr);
-}
-
-std::string mbcsstring_makeASCII(const std::string& wstr)
-{
- // Replace non-ASCII chars with replace_char
- std::string out_str = wstr;
- for (S32 i = 0; i < (S32)out_str.length(); i++)
- {
- if ((U8)out_str[i] > 0x7f)
- {
- out_str[i] = LL_UNKNOWN_CHAR;
- }
- }
- return out_str;
-}
-
-std::string utf8str_removeCRLF(const std::string& utf8str)
-{
- if (0 == utf8str.length())
- {
- return std::string();
- }
- const char CR = 13;
-
- std::string out;
- out.reserve(utf8str.length());
- const S32 len = (S32)utf8str.length();
- for( S32 i = 0; i < len; i++ )
- {
- if( utf8str[i] != CR )
- {
- out.push_back(utf8str[i]);
- }
- }
- return out;
-}
-
-llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length)
-{
- switch (length)
- {
- case 2:
- return ((utf8str[offset] & 0x1F) << 6) +
- (utf8str[offset + 1] & 0x3F);
- case 3:
- return ((utf8str[offset] & 0x0F) << 12) +
- ((utf8str[offset + 1] & 0x3F) << 6) +
- (utf8str[offset + 2] & 0x3F);
- case 4:
- return ((utf8str[offset] & 0x07) << 18) +
- ((utf8str[offset + 1] & 0x3F) << 12) +
- ((utf8str[offset + 2] & 0x3F) << 6) +
- (utf8str[offset + 3] & 0x3F);
- case 5:
- return ((utf8str[offset] & 0x03) << 24) +
- ((utf8str[offset + 1] & 0x3F) << 18) +
- ((utf8str[offset + 2] & 0x3F) << 12) +
- ((utf8str[offset + 3] & 0x3F) << 6) +
- (utf8str[offset + 4] & 0x3F);
- case 6:
- return ((utf8str[offset] & 0x01) << 30) +
- ((utf8str[offset + 1] & 0x3F) << 24) +
- ((utf8str[offset + 2] & 0x3F) << 18) +
- ((utf8str[offset + 3] & 0x3F) << 12) +
- ((utf8str[offset + 4] & 0x3F) << 6) +
- (utf8str[offset + 5] & 0x3F);
- case 7:
- return ((utf8str[offset + 1] & 0x03) << 30) +
- ((utf8str[offset + 2] & 0x3F) << 24) +
- ((utf8str[offset + 3] & 0x3F) << 18) +
- ((utf8str[offset + 4] & 0x3F) << 12) +
- ((utf8str[offset + 5] & 0x3F) << 6) +
- (utf8str[offset + 6] & 0x3F);
- }
- return LL_UNKNOWN_CHAR;
-}
-
-std::string utf8str_showBytesUTF8(const std::string& utf8str)
-{
- std::string result;
-
- bool in_sequence = false;
- size_t sequence_size = 0;
- size_t byte_index = 0;
- size_t source_length = utf8str.size();
-
- auto open_sequence = [&]()
- {
- if (!result.empty() && result.back() != '\n')
- result += '\n'; // Use LF as a separator before new UTF-8 sequence
- result += '[';
- in_sequence = true;
- };
-
- auto close_sequence = [&]()
- {
- llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size);
- if (unicode != LL_UNKNOWN_CHAR)
- {
- result += llformat("+%04X", unicode);
- }
- result += ']';
- in_sequence = false;
- sequence_size = 0;
- };
-
- while (byte_index < source_length)
- {
- U8 byte = utf8str[byte_index];
- if (byte >= 0x80) // Part of an UTF-8 sequence
- {
- if (!in_sequence) // Start new UTF-8 sequence
- {
- open_sequence();
- }
- else if (byte >= 0xC0) // Start another UTF-8 sequence
- {
- close_sequence();
- open_sequence();
- }
- else // Continue the same UTF-8 sequence
- {
- result += '.';
- }
- result += llformat("%02X", byte); // The byte is represented in hexadecimal form
- ++sequence_size;
- }
- else // ASCII symbol is represented as a character
- {
- if (in_sequence) // End of UTF-8 sequence
- {
- close_sequence();
- if (byte != '\n')
- {
- result += '\n'; // Use LF as a separator between UTF-8 and ASCII
- }
- }
- result += byte;
- }
- ++byte_index;
- }
-
- if (in_sequence) // End of UTF-8 sequence
- {
- close_sequence();
- }
-
- return result;
-}
-
-// Search for any emoji symbol, return true if found
-bool wstring_has_emoji(const LLWString& wstr)
-{
- for (const llwchar& wch : wstr)
- {
- if (LLStringOps::isEmoji(wch))
- return true;
- }
-
- return false;
-}
-
-// Cut emoji symbols if exist
-bool wstring_remove_emojis(LLWString& wstr)
-{
- bool found = false;
- for (size_t i = 0; i < wstr.size(); ++i)
- {
- if (LLStringOps::isEmoji(wstr[i]))
- {
- wstr.erase(i--, 1);
- found = true;
- }
- }
- return found;
-}
-
-// Cut emoji symbols if exist
-bool utf8str_remove_emojis(std::string& utf8str)
-{
- LLWString wstr = utf8str_to_wstring(utf8str);
- if (!wstring_remove_emojis(wstr))
- return false;
- utf8str = wstring_to_utf8str(wstr);
- return true;
-}
-
-#if LL_WINDOWS
-unsigned int ll_wstring_default_code_page()
-{
- return CP_UTF8;
-}
-
-std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned int code_page)
-{
- std::string out;
- if(in)
- {
- int len_out = WideCharToMultiByte(
- code_page,
- 0,
- in,
- len_in,
- NULL,
- 0,
- 0,
- 0);
- // We will need two more bytes for the double NULL ending
- // created in WideCharToMultiByte().
- char* pout = new char [len_out + 2];
- memset(pout, 0, len_out + 2);
- if(pout)
- {
- WideCharToMultiByte(
- code_page,
- 0,
- in,
- len_in,
- pout,
- len_out,
- 0,
- 0);
- out.assign(pout);
- delete[] pout;
- }
- }
- return out;
-}
-
-std::wstring ll_convert_string_to_wide(const char* in, size_t len, unsigned int code_page)
-{
- // From review:
- // We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input,
- // plus one for a null terminator, and be guaranteed to not overflow.
-
- // Normally, I'd call that sort of thing premature optimization,
- // but we *are* seeing string operations taking a bunch of time, especially when constructing widgets.
-// int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0);
-
- // reserve an output buffer that will be destroyed on exit, with a place
- // to put NULL terminator
- std::vector<wchar_t> w_out(len + 1);
-
- memset(&w_out[0], 0, w_out.size());
- int real_output_str_len = MultiByteToWideChar(code_page, 0, in, len,
- &w_out[0], w_out.size() - 1);
-
- //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858.
- w_out[real_output_str_len] = 0;
-
- // construct string<wchar_t> from our temporary output buffer
- return {&w_out[0]};
-}
-
-LLWString ll_convert_wide_to_wstring(const wchar_t* in, size_t len)
-{
- // Whether or not std::wstring and llutf16string are distinct types, they
- // both hold UTF-16LE characters. (See header file comments.) Pretend this
- // wchar_t* sequence is really a U16* sequence and use the conversion we
- // define above.
- return utf16str_to_wstring(reinterpret_cast<const U16*>(in), len);
-}
-
-std::wstring ll_convert_wstring_to_wide(const llwchar* in, size_t len)
-{
- // first, convert to llutf16string, for which we have a real implementation
- auto utf16str{ wstring_to_utf16str(in, len) };
- // then, because each U16 char must be UTF-16LE encoded, pretend the U16*
- // string pointer is a wchar_t* and instantiate a std::wstring of the same
- // length.
- return { reinterpret_cast<const wchar_t*>(utf16str.c_str()), utf16str.length() };
-}
-
-std::string ll_convert_string_to_utf8_string(const std::string& in)
-{
- // If you pass code_page, you must also pass length, otherwise the code
- // page parameter will be mistaken for length.
- auto w_mesg = ll_convert_string_to_wide(in, in.length(), CP_ACP);
- // CP_UTF8 is default -- see ll_wstring_default_code_page() above.
- return ll_convert_wide_to_string(w_mesg);
-}
-
-namespace
-{
-
-void HeapFree_deleter(void* ptr)
-{
- // instead of LocalFree(), per https://stackoverflow.com/a/31541205
- HeapFree(GetProcessHeap(), NULL, ptr);
-}
-
-} // anonymous namespace
-
-template<>
-std::wstring windows_message<std::wstring>(DWORD error)
-{
- // derived from https://stackoverflow.com/a/455533
- wchar_t* rawptr = nullptr;
- auto okay = FormatMessageW(
- // use system message tables for GetLastError() codes
- FORMAT_MESSAGE_FROM_SYSTEM |
- // internally allocate buffer and return its pointer
- FORMAT_MESSAGE_ALLOCATE_BUFFER |
- // you cannot pass insertion parameters (thanks Gandalf)
- FORMAT_MESSAGE_IGNORE_INSERTS |
- // ignore line breaks in message definition text
- FORMAT_MESSAGE_MAX_WIDTH_MASK,
- NULL, // lpSource, unused with FORMAT_MESSAGE_FROM_SYSTEM
- error, // dwMessageId
- MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId
- (LPWSTR)&rawptr, // lpBuffer: force-cast wchar_t** to wchar_t*
- 0, // nSize, unused with FORMAT_MESSAGE_ALLOCATE_BUFFER
- NULL); // Arguments, unused
-
- // make a unique_ptr from rawptr so it gets cleaned up properly
- std::unique_ptr<wchar_t, void(*)(void*)> bufferptr(rawptr, HeapFree_deleter);
-
- if (okay && bufferptr)
- {
- // got the message, return it ('okay' is length in characters)
- return { bufferptr.get(), okay };
- }
-
- // did not get the message, synthesize one
- auto format_message_error = GetLastError();
- std::wostringstream out;
- out << L"GetLastError() " << error << L" (FormatMessageW() failed with "
- << format_message_error << L")";
- return out.str();
-}
-
-std::optional<std::wstring> llstring_getoptenv(const std::string& key)
-{
- auto wkey = ll_convert_string_to_wide(key);
- // Take a wild guess as to how big the buffer should be.
- std::vector<wchar_t> buffer(1024);
- auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size());
- // If our initial guess was too short, n will indicate the size (in
- // wchar_t's) that buffer should have been, including the terminating nul.
- if (n > (buffer.size() - 1))
- {
- // make it big enough
- buffer.resize(n);
- // and try again
- n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size());
- }
- // did that (ultimately) succeed?
- if (n)
- {
- // great, return populated std::optional
- return std::make_optional<std::wstring>(&buffer[0]);
- }
-
- // not successful
- auto last_error = GetLastError();
- // Don't bother warning for NOT_FOUND; that's an expected case
- if (last_error != ERROR_ENVVAR_NOT_FOUND)
- {
- LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: "
- << windows_message<std::string>(last_error) << LL_ENDL;
- }
- // return empty std::optional
- return {};
-}
-
-#else // ! LL_WINDOWS
-
-std::optional<std::string> llstring_getoptenv(const std::string& key)
-{
- auto found = getenv(key.c_str());
- if (found)
- {
- // return populated std::optional
- return std::make_optional<std::string>(found);
- }
- else
- {
- // return empty std::optional
- return {};
- }
-}
-
-#endif // ! LL_WINDOWS
-
-long LLStringOps::sPacificTimeOffset = 0;
-long LLStringOps::sLocalTimeOffset = 0;
-bool LLStringOps::sPacificDaylightTime = 0;
-std::map<std::string, std::string> LLStringOps::datetimeToCodes;
-
-std::vector<std::string> LLStringOps::sWeekDayList;
-std::vector<std::string> LLStringOps::sWeekDayShortList;
-std::vector<std::string> LLStringOps::sMonthList;
-std::vector<std::string> LLStringOps::sMonthShortList;
-
-
-std::string LLStringOps::sDayFormat;
-std::string LLStringOps::sAM;
-std::string LLStringOps::sPM;
-
-// static
-bool LLStringOps::isEmoji(llwchar a)
-{
-#if 0 // Do not consider special characters that might have a corresponding
- // glyph in the monochorme fallback fonts as a "genuine" emoji. HB
- return a == 0xa9 || a == 0xae || (a >= 0x2000 && a < 0x3300) ||
- (a >= 0x1f000 && a < 0x20000);
-#else
- // These are indeed "genuine" emojis, we *do want* rendered as such. HB
- return a >= 0x1f000 && a < 0x20000;
-#endif
- }
-
-S32 LLStringOps::collate(const llwchar* a, const llwchar* b)
-{
- #if LL_WINDOWS
- // in Windows, wide string functions operator on 16-bit strings,
- // not the proper 32 bit wide string
- return strcmp(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str());
- #else
- return wcscoll(a, b);
- #endif
-}
-
-void LLStringOps::setupDatetimeInfo (bool daylight)
-{
- time_t nowT, localT, gmtT;
- struct tm * tmpT;
-
- nowT = time (NULL);
-
- tmpT = gmtime (&nowT);
- gmtT = mktime (tmpT);
-
- tmpT = localtime (&nowT);
- localT = mktime (tmpT);
-
- sLocalTimeOffset = (long) (gmtT - localT);
- if (tmpT->tm_isdst)
- {
- sLocalTimeOffset -= 60 * 60; // 1 hour
- }
-
- sPacificDaylightTime = daylight;
- sPacificTimeOffset = (sPacificDaylightTime? 7 : 8 ) * 60 * 60;
-
- datetimeToCodes["wkday"] = "%a"; // Thu
- datetimeToCodes["weekday"] = "%A"; // Thursday
- datetimeToCodes["year4"] = "%Y"; // 2009
- datetimeToCodes["year"] = "%Y"; // 2009
- datetimeToCodes["year2"] = "%y"; // 09
- datetimeToCodes["mth"] = "%b"; // Aug
- datetimeToCodes["month"] = "%B"; // August
- datetimeToCodes["mthnum"] = "%m"; // 08
- datetimeToCodes["day"] = "%d"; // 31
- datetimeToCodes["sday"] = "%-d"; // 9
- datetimeToCodes["hour24"] = "%H"; // 14
- datetimeToCodes["hour"] = "%H"; // 14
- datetimeToCodes["hour12"] = "%I"; // 02
- datetimeToCodes["min"] = "%M"; // 59
- datetimeToCodes["ampm"] = "%p"; // AM
- datetimeToCodes["second"] = "%S"; // 59
- datetimeToCodes["timezone"] = "%Z"; // PST
-}
-
-void tokenizeStringToArray(const std::string& data, std::vector<std::string>& output)
-{
- output.clear();
- size_t length = data.size();
-
- // tokenize it and put it in the array
- std::string cur_word;
- for(size_t i = 0; i < length; ++i)
- {
- if(data[i] == ':')
- {
- output.push_back(cur_word);
- cur_word.clear();
- }
- else
- {
- cur_word.append(1, data[i]);
- }
- }
- output.push_back(cur_word);
-}
-
-void LLStringOps::setupWeekDaysNames(const std::string& data)
-{
- tokenizeStringToArray(data,sWeekDayList);
-}
-void LLStringOps::setupWeekDaysShortNames(const std::string& data)
-{
- tokenizeStringToArray(data,sWeekDayShortList);
-}
-void LLStringOps::setupMonthNames(const std::string& data)
-{
- tokenizeStringToArray(data,sMonthList);
-}
-void LLStringOps::setupMonthShortNames(const std::string& data)
-{
- tokenizeStringToArray(data,sMonthShortList);
-}
-void LLStringOps::setupDayFormat(const std::string& data)
-{
- sDayFormat = data;
-}
-
-
-std::string LLStringOps::getDatetimeCode (std::string key)
-{
- std::map<std::string, std::string>::iterator iter;
-
- iter = datetimeToCodes.find (key);
- if (iter != datetimeToCodes.end())
- {
- return iter->second;
- }
- else
- {
- return std::string("");
- }
-}
-
-std::string LLStringOps::getReadableNumber(F64 num)
-{
- if (fabs(num)>=1e9)
- {
- return llformat("%.2lfB", num / 1e9);
- }
- else if (fabs(num)>=1e6)
- {
- return llformat("%.2lfM", num / 1e6);
- }
- else if (fabs(num)>=1e3)
- {
- return llformat("%.2lfK", num / 1e3);
- }
- else
- {
- return llformat("%.2lf", num);
- }
-}
-
-namespace LLStringFn
-{
- // NOTE - this restricts output to ascii
- void replace_nonprintable_in_ascii(std::basic_string<char>& string, char replacement)
- {
- const char MIN = 0x20;
- std::basic_string<char>::size_type len = string.size();
- for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii)
- {
- if(string[ii] < MIN)
- {
- string[ii] = replacement;
- }
- }
- }
-
-
- // NOTE - this restricts output to ascii
- void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str,
- char replacement)
- {
- const char MIN = 0x20;
- const char PIPE = 0x7c;
- std::basic_string<char>::size_type len = str.size();
- for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii)
- {
- if( (str[ii] < MIN) || (str[ii] == PIPE) )
- {
- str[ii] = replacement;
- }
- }
- }
-
- // https://wiki.lindenlab.com/wiki/Unicode_Guidelines has details on
- // allowable code points for XML. Specifically, they are:
- // 0x09, 0x0a, 0x0d, and 0x20 on up. JC
- std::string strip_invalid_xml(const std::string& instr)
- {
- std::string output;
- output.reserve( instr.size() );
- std::string::const_iterator it = instr.begin();
- while (it != instr.end())
- {
- // Must compare as unsigned for >=
- // Test most likely match first
- const unsigned char c = (unsigned char)*it;
- if ( c >= (unsigned char)0x20 // SPACE
- || c == (unsigned char)0x09 // TAB
- || c == (unsigned char)0x0a // LINE_FEED
- || c == (unsigned char)0x0d ) // CARRIAGE_RETURN
- {
- output.push_back(c);
- }
- ++it;
- }
- return output;
- }
-
- /**
- * @brief Replace all control characters (c < 0x20) with replacement in
- * string.
- */
- void replace_ascii_controlchars(std::basic_string<char>& string, char replacement)
- {
- const unsigned char MIN = 0x20;
- std::basic_string<char>::size_type len = string.size();
- for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii)
- {
- const unsigned char c = (unsigned char) string[ii];
- if(c < MIN)
- {
- string[ii] = replacement;
- }
- }
- }
-}
-
-////////////////////////////////////////////////////////////
-
-// Forward specialization of LLStringUtil::format before use in LLStringUtil::formatDatetime.
-template<>
-S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions);
-
-//static
-template<>
-void LLStringUtil::getTokens(const std::string& instr, std::vector<std::string >& tokens, const std::string& delims)
-{
- // Starting at offset 0, scan forward for the next non-delimiter. We're
- // done when the only characters left in 'instr' are delimiters.
- for (std::string::size_type begIdx, endIdx = 0;
- (begIdx = instr.find_first_not_of (delims, endIdx)) != std::string::npos; )
- {
- // Found a non-delimiter. After that, find the next delimiter.
- endIdx = instr.find_first_of (delims, begIdx);
- if (endIdx == std::string::npos)
- {
- // No more delimiters: this token extends to the end of the string.
- endIdx = instr.length();
- }
-
- // extract the token between begIdx and endIdx; substr() needs length
- std::string currToken(instr.substr(begIdx, endIdx - begIdx));
- LLStringUtil::trim (currToken);
- tokens.push_back(currToken);
- // next scan past delimiters starts at endIdx
- }
-}
-
-template<>
-LLStringUtil::size_type LLStringUtil::getSubstitution(const std::string& instr, size_type& start, std::vector<std::string>& tokens)
-{
- const std::string delims (",");
-
- // Find the first [
- size_type pos1 = instr.find('[', start);
- if (pos1 == std::string::npos)
- return std::string::npos;
-
- //Find the first ] after the initial [
- size_type pos2 = instr.find(']', pos1);
- if (pos2 == std::string::npos)
- return std::string::npos;
-
- // Find the last [ before ] in case of nested [[]]
- pos1 = instr.find_last_of('[', pos2-1);
- if (pos1 == std::string::npos || pos1 < start)
- return std::string::npos;
-
- getTokens(std::string(instr,pos1+1,pos2-pos1-1), tokens, delims);
- start = pos2+1;
-
- return pos1;
-}
-
-// static
-template<>
-bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const format_map_t& substitutions)
-{
- // see if we have a replacement for the bracketed string (without the brackets)
- // test first using has() because if we just look up with operator[] we get back an
- // empty string even if the value is missing. We want to distinguish between
- // missing replacements and deliberately empty replacement strings.
- format_map_t::const_iterator iter = substitutions.find(token);
- if (iter != substitutions.end())
- {
- replacement = iter->second;
- return true;
- }
- // if not, see if there's one WITH brackets
- iter = substitutions.find(std::string("[" + token + "]"));
- if (iter != substitutions.end())
- {
- replacement = iter->second;
- return true;
- }
-
- return false;
-}
-
-// static
-template<>
-bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const LLSD& substitutions)
-{
- // see if we have a replacement for the bracketed string (without the brackets)
- // test first using has() because if we just look up with operator[] we get back an
- // empty string even if the value is missing. We want to distinguish between
- // missing replacements and deliberately empty replacement strings.
- if (substitutions.has(token))
- {
- replacement = substitutions[token].asString();
- return true;
- }
- // if not, see if there's one WITH brackets
- else if (substitutions.has(std::string("[" + token + "]")))
- {
- replacement = substitutions[std::string("[" + token + "]")].asString();
- return true;
- }
-
- return false;
-}
-
-//static
-template<>
-void LLStringUtil::setLocale(std::string inLocale)
-{
- sLocale = inLocale;
-};
-
-//static
-template<>
-std::string LLStringUtil::getLocale(void)
-{
- return sLocale;
-};
-
-// static
-template<>
-void LLStringUtil::formatNumber(std::string& numStr, std::string decimals)
-{
- std::stringstream strStream;
- S32 intDecimals = 0;
-
- convertToS32 (decimals, intDecimals);
- if (!sLocale.empty())
- {
- // std::locale() throws if the locale is unknown! (EXT-7926)
- try
- {
- strStream.imbue(std::locale(sLocale.c_str()));
- } catch (const std::exception &)
- {
- LL_WARNS_ONCE("Locale") << "Cannot set locale to " << sLocale << LL_ENDL;
- }
- }
-
- if (!intDecimals)
- {
- S32 intStr;
-
- if (convertToS32(numStr, intStr))
- {
- strStream << intStr;
- numStr = strStream.str();
- }
- }
- else
- {
- F32 floatStr;
-
- if (convertToF32(numStr, floatStr))
- {
- strStream << std::fixed << std::showpoint << std::setprecision(intDecimals) << floatStr;
- numStr = strStream.str();
- }
- }
-}
-
-// static
-template<>
-bool LLStringUtil::formatDatetime(std::string& replacement, std::string token,
- std::string param, S32 secFromEpoch)
-{
- if (param == "local") // local
- {
- secFromEpoch -= LLStringOps::getLocalTimeOffset();
- }
- else if (param != "utc") // slt
- {
- secFromEpoch -= LLStringOps::getPacificTimeOffset();
- }
-
- // if never fell into those two ifs above, param must be utc
- if (secFromEpoch < 0) secFromEpoch = 0;
-
- LLDate datetime((F64)secFromEpoch);
- std::string code = LLStringOps::getDatetimeCode (token);
-
- // special case to handle timezone
- if (code == "%Z") {
- if (param == "utc")
- {
- replacement = "GMT";
- }
- else if (param == "local")
- {
- replacement = ""; // user knows their own timezone
- }
- else
- {
-#if 0
- // EXT-1565 : Zai Lynch, James Linden : 15/Oct/09
- // [BSI] Feedback: Viewer clock mentions SLT, but would prefer it to show PST/PDT
- // "slt" = Second Life Time, which is deprecated.
- // If not utc or user local time, fallback to Pacific time
- replacement = LLStringOps::getPacificDaylightTime() ? "PDT" : "PST";
-#else
- // SL-20370 : Steeltoe Linden : 29/Sep/23
- // Change "PDT" to "SLT" on menu bar
- replacement = "SLT";
-#endif
- }
- return true;
- }
-
- //EXT-7013
- //few codes are not suppotred by strtime function (example - weekdays for Japanise)
- //so use predefined ones
-
- //if sWeekDayList is not empty than current locale doesn't support
- //weekday name.
- time_t loc_seconds = (time_t) secFromEpoch;
- if(LLStringOps::sWeekDayList.size() == 7 && code == "%A")
- {
- struct tm * gmt = gmtime (&loc_seconds);
- replacement = LLStringOps::sWeekDayList[gmt->tm_wday];
- }
- else if(LLStringOps::sWeekDayShortList.size() == 7 && code == "%a")
- {
- struct tm * gmt = gmtime (&loc_seconds);
- replacement = LLStringOps::sWeekDayShortList[gmt->tm_wday];
- }
- else if(LLStringOps::sMonthList.size() == 12 && code == "%B")
- {
- struct tm * gmt = gmtime (&loc_seconds);
- replacement = LLStringOps::sMonthList[gmt->tm_mon];
- }
- else if( !LLStringOps::sDayFormat.empty() && code == "%d" )
- {
- struct tm * gmt = gmtime (&loc_seconds);
- LLStringUtil::format_map_t args;
- args["[MDAY]"] = llformat ("%d", gmt->tm_mday);
- replacement = LLStringOps::sDayFormat;
- LLStringUtil::format(replacement, args);
- }
- else if (code == "%-d")
- {
- struct tm * gmt = gmtime (&loc_seconds);
- replacement = llformat ("%d", gmt->tm_mday); // day of the month without leading zero
- }
- else if( !LLStringOps::sAM.empty() && !LLStringOps::sPM.empty() && code == "%p" )
- {
- struct tm * gmt = gmtime (&loc_seconds);
- if(gmt->tm_hour<12)
- {
- replacement = LLStringOps::sAM;
- }
- else
- {
- replacement = LLStringOps::sPM;
- }
- }
- else
- {
- replacement = datetime.toHTTPDateString(code);
- }
-
- // *HACK: delete leading zero from hour string in case 'hour12' (code = %I) time format
- // to show time without leading zero, e.g. 08:16 -> 8:16 (EXT-2738).
- // We could have used '%l' format instead, but it's not supported by Windows.
- if(code == "%I" && token == "hour12" && replacement.at(0) == '0')
- {
- replacement = replacement.at(1);
- }
-
- return !code.empty();
-}
-
-// LLStringUtil::format recogizes the following patterns.
-// All substitutions *must* be encased in []'s in the input string.
-// The []'s are optional in the substitution map.
-// [FOO_123]
-// [FOO,number,precision]
-// [FOO,datetime,format]
-
-
-// static
-template<>
-S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING;
- S32 res = 0;
-
- std::string output;
- std::vector<std::string> tokens;
-
- std::string::size_type start = 0;
- std::string::size_type prev_start = 0;
- std::string::size_type key_start = 0;
- while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos)
- {
- output += std::string(s, prev_start, key_start-prev_start);
- prev_start = start;
-
- bool found_replacement = false;
- std::string replacement;
-
- if (tokens.size() == 0)
- {
- found_replacement = false;
- }
- else if (tokens.size() == 1)
- {
- found_replacement = simpleReplacement (replacement, tokens[0], substitutions);
- }
- else if (tokens[1] == "number")
- {
- std::string param = "0";
-
- if (tokens.size() > 2) param = tokens[2];
- found_replacement = simpleReplacement (replacement, tokens[0], substitutions);
- if (found_replacement) formatNumber (replacement, param);
- }
- else if (tokens[1] == "datetime")
- {
- std::string param;
- if (tokens.size() > 2) param = tokens[2];
-
- format_map_t::const_iterator iter = substitutions.find("datetime");
- if (iter != substitutions.end())
- {
- S32 secFromEpoch = 0;
- bool r = LLStringUtil::convertToS32(iter->second, secFromEpoch);
- if (r)
- {
- found_replacement = formatDatetime(replacement, tokens[0], param, secFromEpoch);
- }
- }
- }
-
- if (found_replacement)
- {
- output += replacement;
- res++;
- }
- else
- {
- // we had no replacement, use the string as is
- // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-"
- output += std::string(s, key_start, start-key_start);
- }
- tokens.clear();
- }
- // send the remainder of the string (with no further matches for bracketed names)
- output += std::string(s, start);
- s = output;
- return res;
-}
-
-//static
-template<>
-S32 LLStringUtil::format(std::string& s, const LLSD& substitutions)
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING;
- S32 res = 0;
-
- if (!substitutions.isMap())
- {
- return res;
- }
-
- std::string output;
- std::vector<std::string> tokens;
-
- std::string::size_type start = 0;
- std::string::size_type prev_start = 0;
- std::string::size_type key_start = 0;
- while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos)
- {
- output += std::string(s, prev_start, key_start-prev_start);
- prev_start = start;
-
- bool found_replacement = false;
- std::string replacement;
-
- if (tokens.size() == 0)
- {
- found_replacement = false;
- }
- else if (tokens.size() == 1)
- {
- found_replacement = simpleReplacement (replacement, tokens[0], substitutions);
- }
- else if (tokens[1] == "number")
- {
- std::string param = "0";
-
- if (tokens.size() > 2) param = tokens[2];
- found_replacement = simpleReplacement (replacement, tokens[0], substitutions);
- if (found_replacement) formatNumber (replacement, param);
- }
- else if (tokens[1] == "datetime")
- {
- std::string param;
- if (tokens.size() > 2) param = tokens[2];
-
- S32 secFromEpoch = (S32) substitutions["datetime"].asInteger();
- found_replacement = formatDatetime (replacement, tokens[0], param, secFromEpoch);
- }
-
- if (found_replacement)
- {
- output += replacement;
- res++;
- }
- else
- {
- // we had no replacement, use the string as is
- // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-"
- output += std::string(s, key_start, start-key_start);
- }
- tokens.clear();
- }
- // send the remainder of the string (with no further matches for bracketed names)
- output += std::string(s, start);
- s = output;
- return res;
-}
-
-////////////////////////////////////////////////////////////
-// Testing
-
-#ifdef _DEBUG
-
-template<class T>
-void LLStringUtilBase<T>::testHarness()
-{
- std::string s1;
-
- llassert( s1.c_str() == NULL );
- llassert( s1.size() == 0 );
- llassert( s1.empty() );
-
- std::string s2( "hello");
- llassert( !strcmp( s2.c_str(), "hello" ) );
- llassert( s2.size() == 5 );
- llassert( !s2.empty() );
- std::string s3( s2 );
-
- llassert( "hello" == s2 );
- llassert( s2 == "hello" );
- llassert( s2 > "gello" );
- llassert( "gello" < s2 );
- llassert( "gello" != s2 );
- llassert( s2 != "gello" );
-
- std::string s4 = s2;
- llassert( !s4.empty() );
- s4.empty();
- llassert( s4.empty() );
-
- std::string s5("");
- llassert( s5.empty() );
-
- llassert( isValidIndex(s5, 0) );
- llassert( !isValidIndex(s5, 1) );
-
- s3 = s2;
- s4 = "hello again";
-
- s4 += "!";
- s4 += s4;
- llassert( s4 == "hello again!hello again!" );
-
-
- std::string s6 = s2 + " " + s2;
- std::string s7 = s6;
- llassert( s6 == s7 );
- llassert( !( s6 != s7) );
- llassert( !(s6 < s7) );
- llassert( !(s6 > s7) );
-
- llassert( !(s6 == "hi"));
- llassert( s6 == "hello hello");
- llassert( s6 < "hi");
-
- llassert( s6[1] == 'e' );
- s6[1] = 'f';
- llassert( s6[1] == 'f' );
-
- s2.erase( 4, 1 );
- llassert( s2 == "hell");
- s2.insert( 0, "y" );
- llassert( s2 == "yhell");
- s2.erase( 1, 3 );
- llassert( s2 == "yl");
- s2.insert( 1, "awn, don't yel");
- llassert( s2 == "yawn, don't yell");
-
- std::string s8 = s2.substr( 6, 5 );
- llassert( s8 == "don't" );
-
- std::string s9 = " \t\ntest \t\t\n ";
- trim(s9);
- llassert( s9 == "test" );
-
- s8 = "abc123&*(ABC";
-
- s9 = s8;
- toUpper(s9);
- llassert( s9 == "ABC123&*(ABC" );
-
- s9 = s8;
- toLower(s9);
- llassert( s9 == "abc123&*(abc" );
-
-
- std::string s10( 10, 'x' );
- llassert( s10 == "xxxxxxxxxx" );
-
- std::string s11( "monkey in the middle", 7, 2 );
- llassert( s11 == "in" );
-
- std::string s12; //empty
- s12 += "foo";
- llassert( s12 == "foo" );
-
- std::string s13; //empty
- s13 += 'f';
- llassert( s13 == "f" );
-}
-
-
-#endif // _DEBUG
+/** + * @file llstring.cpp + * @brief String utility functions and the std::string class. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llstring.h" +#include "llerror.h" +#include "llfasttimer.h" +#include "llsd.h" +#include <vector> + +#if LL_WINDOWS +#include "llwin32headerslean.h" +#include <winnls.h> // for WideCharToMultiByte +#endif + +std::string ll_safe_string(const char* in) +{ + if(in) return std::string(in); + return std::string(); +} + +std::string ll_safe_string(const char* in, S32 maxlen) +{ + if(in && maxlen > 0 ) return std::string(in, maxlen); + + return std::string(); +} + +bool is_char_hex(char hex) +{ + if((hex >= '0') && (hex <= '9')) + { + return true; + } + else if((hex >= 'a') && (hex <='f')) + { + return true; + } + else if((hex >= 'A') && (hex <='F')) + { + return true; + } + return false; // uh - oh, not hex any more... +} + +U8 hex_as_nybble(char hex) +{ + if((hex >= '0') && (hex <= '9')) + { + return (U8)(hex - '0'); + } + else if((hex >= 'a') && (hex <='f')) + { + return (U8)(10 + hex - 'a'); + } + else if((hex >= 'A') && (hex <='F')) + { + return (U8)(10 + hex - 'A'); + } + return 0; // uh - oh, not hex any more... +} + +bool iswindividual(llwchar elem) +{ + U32 cur_char = (U32)elem; + bool result = false; + if (0x2E80<= cur_char && cur_char <= 0x9FFF) + { + result = true; + } + else if (0xAC00<= cur_char && cur_char <= 0xD7A0 ) + { + result = true; + } + else if (0xF900<= cur_char && cur_char <= 0xFA60 ) + { + result = true; + } + return result; +} + +bool _read_file_into_string(std::string& str, const std::string& filename) +{ + llifstream ifs(filename.c_str(), llifstream::binary); + if (!ifs.is_open()) + { + LL_INFOS() << "Unable to open file " << filename << LL_ENDL; + return false; + } + + std::ostringstream oss; + + oss << ifs.rdbuf(); + str = oss.str(); + ifs.close(); + return true; +} + + + + +// See http://www.unicode.org/Public/BETA/CVTUTF-1-2/ConvertUTF.c +// for the Unicode implementation - this doesn't match because it was written before finding +// it. + + +std::ostream& operator<<(std::ostream &s, const LLWString &wstr) +{ + std::string utf8_str = wstring_to_utf8str(wstr); + s << utf8_str; + return s; +} + +std::string rawstr_to_utf8(const std::string& raw) +{ + LLWString wstr(utf8str_to_wstring(raw)); + return wstring_to_utf8str(wstr); +} + +std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) +{ + U32 cur_char = (U32)in_char; + char* base = outchars; + if (cur_char < 0x80) + { + *outchars++ = (U8)cur_char; + } + else if (cur_char < 0x800) + { + *outchars++ = 0xC0 | (cur_char >> 6); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x10000) + { + *outchars++ = 0xE0 | (cur_char >> 12); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x200000) + { + *outchars++ = 0xF0 | (cur_char >> 18); + *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x4000000) + { + *outchars++ = 0xF8 | (cur_char >> 24); + *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else if (cur_char < 0x80000000) + { + *outchars++ = 0xFC | (cur_char >> 30); + *outchars++ = 0x80 | ((cur_char >> 24) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 18) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 12) & 0x3F); + *outchars++ = 0x80 | ((cur_char >> 6) & 0x3F); + *outchars++ = 0x80 | (cur_char & 0x3F); + } + else + { + LL_WARNS() << "Invalid Unicode character " << cur_char << "!" << LL_ENDL; + *outchars++ = LL_UNKNOWN_CHAR; + } + return outchars - base; +} + +auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) +{ + const U16* base = inchars; + U16 cur_char = *inchars++; + llwchar char32 = cur_char; + if ((cur_char >= 0xD800) && (cur_char <= 0xDFFF)) + { + // Surrogates + char32 = ((llwchar)(cur_char - 0xD800)) << 10; + cur_char = *inchars++; + char32 += (llwchar)(cur_char - 0xDC00) + 0x0010000UL; + } + else + { + char32 = (llwchar)cur_char; + } + *outchar = char32; + return inchars - base; +} + +llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) +{ + llutf16string out; + + S32 i = 0; + while (i < len) + { + U32 cur_char = utf32str[i]; + if (cur_char > 0xFFFF) + { + out += (0xD7C0 + (cur_char >> 10)); + out += (0xDC00 | (cur_char & 0x3FF)); + } + else + { + out += cur_char; + } + i++; + } + return out; +} + +llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) +{ + LLWString wstr = utf8str_to_wstring ( utf8str, len ); + return wstring_to_utf16str ( wstr ); +} + +LLWString utf16str_to_wstring(const U16* utf16str, size_t len) +{ + LLWString wout; + if (len == 0) return wout; + + S32 i = 0; + const U16* chars16 = utf16str; + while (i < len) + { + llwchar cur_char; + i += utf16chars_to_wchar(chars16+i, &cur_char); + wout += cur_char; + } + return wout; +} + +// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. +S32 utf16str_wstring_length(const llutf16string &utf16str, const S32 utf16_len) +{ + S32 surrogate_pairs = 0; + // ... craziness to make gcc happy (llutf16string.c_str() is tweaked on linux): + const U16 *const utf16_chars = &(*(utf16str.begin())); + S32 i = 0; + while (i < utf16_len) + { + const U16 c = utf16_chars[i++]; + if (c >= 0xD800 && c <= 0xDBFF) // See http://en.wikipedia.org/wiki/UTF-16 + { // Have first byte of a surrogate pair + if (i >= utf16_len) + { + break; + } + const U16 d = utf16_chars[i]; + if (d >= 0xDC00 && d <= 0xDFFF) + { // Have valid second byte of a surrogate pair + surrogate_pairs++; + i++; + } + } + } + return utf16_len - surrogate_pairs; +} + +// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. +S32 wstring_utf16_length(const LLWString &wstr, const S32 woffset, const S32 wlen) +{ + const S32 end = llmin((S32)wstr.length(), woffset + wlen); + if (end < woffset) + { + return 0; + } + else + { + S32 length = end - woffset; + for (S32 i = woffset; i < end; i++) + { + if (wstr[i] >= 0x10000) + { + length++; + } + } + return length; + } +} + +// Given a wstring and an offset in it, returns the length as wstring (i.e., +// number of llwchars) of the longest substring that starts at the offset +// and whose equivalent utf-16 string does not exceeds the given utf16_length. +S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, const S32 woffset, const S32 utf16_length, bool *unaligned) +{ + const auto end = wstr.length(); + bool u{ false }; + S32 n = woffset + utf16_length; + S32 i = woffset; + while (i < end) + { + if (wstr[i] >= 0x10000) + { + --n; + } + if (i >= n) + { + u = (i > n); + break; + } + i++; + } + if (unaligned) + { + *unaligned = u; + } + return i - woffset; +} + +S32 wchar_utf8_length(const llwchar wc) +{ + if (wc < 0x80) + { + return 1; + } + else if (wc < 0x800) + { + return 2; + } + else if (wc < 0x10000) + { + return 3; + } + else if (wc < 0x200000) + { + return 4; + } + else if (wc < 0x4000000) + { + return 5; + } + else + { + return 6; + } +} + +std::string wchar_utf8_preview(const llwchar wc) +{ + std::ostringstream oss; + oss << std::hex << std::uppercase << (U32)wc; + + U8 out_bytes[8]; + U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes); + + if (size > 1) + { + oss << " ["; + for (U32 i = 0; i < size; ++i) + { + if (i) + { + oss << ", "; + } + oss << (int)out_bytes[i]; + } + oss << "]"; + } + + return oss.str(); +} + +S32 wstring_utf8_length(const LLWString& wstr) +{ + S32 len = 0; + for (S32 i = 0; i < (S32)wstr.length(); i++) + { + len += wchar_utf8_length(wstr[i]); + } + return len; +} + +LLWString utf8str_to_wstring(const char* utf8str, size_t len) +{ + LLWString wout; + + S32 i = 0; + while (i < len) + { + llwchar unichar; + U8 cur_char = utf8str[i]; + + if (cur_char < 0x80) + { + // Ascii character, just add it + unichar = cur_char; + } + else + { + S32 cont_bytes = 0; + if ((cur_char >> 5) == 0x6) // Two byte UTF8 -> 1 UTF32 + { + unichar = (0x1F&cur_char); + cont_bytes = 1; + } + else if ((cur_char >> 4) == 0xe) // Three byte UTF8 -> 1 UTF32 + { + unichar = (0x0F&cur_char); + cont_bytes = 2; + } + else if ((cur_char >> 3) == 0x1e) // Four byte UTF8 -> 1 UTF32 + { + unichar = (0x07&cur_char); + cont_bytes = 3; + } + else if ((cur_char >> 2) == 0x3e) // Five byte UTF8 -> 1 UTF32 + { + unichar = (0x03&cur_char); + cont_bytes = 4; + } + else if ((cur_char >> 1) == 0x7e) // Six byte UTF8 -> 1 UTF32 + { + unichar = (0x01&cur_char); + cont_bytes = 5; + } + else + { + wout += LL_UNKNOWN_CHAR; + ++i; + continue; + } + + // Check that this character doesn't go past the end of the string + auto end = (len < (i + cont_bytes)) ? len : (i + cont_bytes); + do + { + ++i; + + cur_char = utf8str[i]; + if ( (cur_char >> 6) == 0x2 ) + { + unichar <<= 6; + unichar += (0x3F&cur_char); + } + else + { + // Malformed sequence - roll back to look at this as a new char + unichar = LL_UNKNOWN_CHAR; + --i; + break; + } + } while(i < end); + + // Handle overlong characters and NULL characters + if ( ((cont_bytes == 1) && (unichar < 0x80)) + || ((cont_bytes == 2) && (unichar < 0x800)) + || ((cont_bytes == 3) && (unichar < 0x10000)) + || ((cont_bytes == 4) && (unichar < 0x200000)) + || ((cont_bytes == 5) && (unichar < 0x4000000)) ) + { + unichar = LL_UNKNOWN_CHAR; + } + } + + wout += unichar; + ++i; + } + return wout; +} + +std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) +{ + std::string out; + + S32 i = 0; + while (i < len) + { + char tchars[8]; /* Flawfinder: ignore */ + auto n = wchar_to_utf8chars(utf32str[i], tchars); + tchars[n] = 0; + out += tchars; + i++; + } + return out; +} + +std::string utf16str_to_utf8str(const U16* utf16str, size_t len) +{ + return wstring_to_utf8str(utf16str_to_wstring(utf16str, len)); +} + +std::string utf8str_trim(const std::string& utf8str) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + LLWStringUtil::trim(wstr); + return wstring_to_utf8str(wstr); +} + + +std::string utf8str_tolower(const std::string& utf8str) +{ + LLWString out_str = utf8str_to_wstring(utf8str); + LLWStringUtil::toLower(out_str); + return wstring_to_utf8str(out_str); +} + + +S32 utf8str_compare_insensitive(const std::string& lhs, const std::string& rhs) +{ + LLWString wlhs = utf8str_to_wstring(lhs); + LLWString wrhs = utf8str_to_wstring(rhs); + return LLWStringUtil::compareInsensitive(wlhs, wrhs); +} + +std::string utf8str_truncate(const std::string& utf8str, const S32 max_len) +{ + if (0 == max_len) + { + return std::string(); + } + if ((S32)utf8str.length() <= max_len) + { + return utf8str; + } + else + { + S32 cur_char = max_len; + + // If we're ASCII, we don't need to do anything + if ((U8)utf8str[cur_char] > 0x7f) + { + // If first two bits are (10), it's the tail end of a multibyte char. We need to shift back + // to the first character + while (0x80 == (0xc0 & utf8str[cur_char])) + { + cur_char--; + // Keep moving forward until we hit the first char; + if (cur_char == 0) + { + // Make sure we don't trash memory if we've got a bogus string. + break; + } + } + } + // The byte index we're on is one we want to get rid of, so we only want to copy up to (cur_char-1) chars + return utf8str.substr(0, cur_char); + } +} + +std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len) +{ + if (0 == symbol_len) + { + return std::string(); + } + if ((S32)utf8str.length() <= symbol_len) + { + return utf8str; + } + else + { + int len = 0, byteIndex = 0; + const char* aStr = utf8str.c_str(); + size_t origSize = utf8str.size(); + + for (byteIndex = 0; len < symbol_len && byteIndex < origSize; byteIndex++) + { + if ((aStr[byteIndex] & 0xc0) != 0x80) + { + len += 1; + } + } + return utf8str.substr(0, byteIndex); + } +} + +std::string utf8str_substChar( + const std::string& utf8str, + const llwchar target_char, + const llwchar replace_char) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + LLWStringUtil::replaceChar(wstr, target_char, replace_char); + //wstr = wstring_substChar(wstr, target_char, replace_char); + return wstring_to_utf8str(wstr); +} + +std::string utf8str_makeASCII(const std::string& utf8str) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + LLWStringUtil::_makeASCII(wstr); + return wstring_to_utf8str(wstr); +} + +std::string mbcsstring_makeASCII(const std::string& wstr) +{ + // Replace non-ASCII chars with replace_char + std::string out_str = wstr; + for (S32 i = 0; i < (S32)out_str.length(); i++) + { + if ((U8)out_str[i] > 0x7f) + { + out_str[i] = LL_UNKNOWN_CHAR; + } + } + return out_str; +} + +std::string utf8str_removeCRLF(const std::string& utf8str) +{ + if (0 == utf8str.length()) + { + return std::string(); + } + const char CR = 13; + + std::string out; + out.reserve(utf8str.length()); + const S32 len = (S32)utf8str.length(); + for( S32 i = 0; i < len; i++ ) + { + if( utf8str[i] != CR ) + { + out.push_back(utf8str[i]); + } + } + return out; +} + +llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length) +{ + switch (length) + { + case 2: + return ((utf8str[offset] & 0x1F) << 6) + + (utf8str[offset + 1] & 0x3F); + case 3: + return ((utf8str[offset] & 0x0F) << 12) + + ((utf8str[offset + 1] & 0x3F) << 6) + + (utf8str[offset + 2] & 0x3F); + case 4: + return ((utf8str[offset] & 0x07) << 18) + + ((utf8str[offset + 1] & 0x3F) << 12) + + ((utf8str[offset + 2] & 0x3F) << 6) + + (utf8str[offset + 3] & 0x3F); + case 5: + return ((utf8str[offset] & 0x03) << 24) + + ((utf8str[offset + 1] & 0x3F) << 18) + + ((utf8str[offset + 2] & 0x3F) << 12) + + ((utf8str[offset + 3] & 0x3F) << 6) + + (utf8str[offset + 4] & 0x3F); + case 6: + return ((utf8str[offset] & 0x01) << 30) + + ((utf8str[offset + 1] & 0x3F) << 24) + + ((utf8str[offset + 2] & 0x3F) << 18) + + ((utf8str[offset + 3] & 0x3F) << 12) + + ((utf8str[offset + 4] & 0x3F) << 6) + + (utf8str[offset + 5] & 0x3F); + case 7: + return ((utf8str[offset + 1] & 0x03) << 30) + + ((utf8str[offset + 2] & 0x3F) << 24) + + ((utf8str[offset + 3] & 0x3F) << 18) + + ((utf8str[offset + 4] & 0x3F) << 12) + + ((utf8str[offset + 5] & 0x3F) << 6) + + (utf8str[offset + 6] & 0x3F); + } + return LL_UNKNOWN_CHAR; +} + +std::string utf8str_showBytesUTF8(const std::string& utf8str) +{ + std::string result; + + bool in_sequence = false; + size_t sequence_size = 0; + size_t byte_index = 0; + size_t source_length = utf8str.size(); + + auto open_sequence = [&]() + { + if (!result.empty() && result.back() != '\n') + result += '\n'; // Use LF as a separator before new UTF-8 sequence + result += '['; + in_sequence = true; + }; + + auto close_sequence = [&]() + { + llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size); + if (unicode != LL_UNKNOWN_CHAR) + { + result += llformat("+%04X", unicode); + } + result += ']'; + in_sequence = false; + sequence_size = 0; + }; + + while (byte_index < source_length) + { + U8 byte = utf8str[byte_index]; + if (byte >= 0x80) // Part of an UTF-8 sequence + { + if (!in_sequence) // Start new UTF-8 sequence + { + open_sequence(); + } + else if (byte >= 0xC0) // Start another UTF-8 sequence + { + close_sequence(); + open_sequence(); + } + else // Continue the same UTF-8 sequence + { + result += '.'; + } + result += llformat("%02X", byte); // The byte is represented in hexadecimal form + ++sequence_size; + } + else // ASCII symbol is represented as a character + { + if (in_sequence) // End of UTF-8 sequence + { + close_sequence(); + if (byte != '\n') + { + result += '\n'; // Use LF as a separator between UTF-8 and ASCII + } + } + result += byte; + } + ++byte_index; + } + + if (in_sequence) // End of UTF-8 sequence + { + close_sequence(); + } + + return result; +} + +// Search for any emoji symbol, return true if found +bool wstring_has_emoji(const LLWString& wstr) +{ + for (const llwchar& wch : wstr) + { + if (LLStringOps::isEmoji(wch)) + return true; + } + + return false; +} + +// Cut emoji symbols if exist +bool wstring_remove_emojis(LLWString& wstr) +{ + bool found = false; + for (size_t i = 0; i < wstr.size(); ++i) + { + if (LLStringOps::isEmoji(wstr[i])) + { + wstr.erase(i--, 1); + found = true; + } + } + return found; +} + +// Cut emoji symbols if exist +bool utf8str_remove_emojis(std::string& utf8str) +{ + LLWString wstr = utf8str_to_wstring(utf8str); + if (!wstring_remove_emojis(wstr)) + return false; + utf8str = wstring_to_utf8str(wstr); + return true; +} + +#if LL_WINDOWS +unsigned int ll_wstring_default_code_page() +{ + return CP_UTF8; +} + +std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned int code_page) +{ + std::string out; + if(in) + { + int len_out = WideCharToMultiByte( + code_page, + 0, + in, + len_in, + NULL, + 0, + 0, + 0); + // We will need two more bytes for the double NULL ending + // created in WideCharToMultiByte(). + char* pout = new char [len_out + 2]; + memset(pout, 0, len_out + 2); + if(pout) + { + WideCharToMultiByte( + code_page, + 0, + in, + len_in, + pout, + len_out, + 0, + 0); + out.assign(pout); + delete[] pout; + } + } + return out; +} + +std::wstring ll_convert_string_to_wide(const char* in, size_t len, unsigned int code_page) +{ + // From review: + // We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input, + // plus one for a null terminator, and be guaranteed to not overflow. + + // Normally, I'd call that sort of thing premature optimization, + // but we *are* seeing string operations taking a bunch of time, especially when constructing widgets. +// int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0); + + // reserve an output buffer that will be destroyed on exit, with a place + // to put NULL terminator + std::vector<wchar_t> w_out(len + 1); + + memset(&w_out[0], 0, w_out.size()); + int real_output_str_len = MultiByteToWideChar(code_page, 0, in, len, + &w_out[0], w_out.size() - 1); + + //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858. + w_out[real_output_str_len] = 0; + + // construct string<wchar_t> from our temporary output buffer + return {&w_out[0]}; +} + +LLWString ll_convert_wide_to_wstring(const wchar_t* in, size_t len) +{ + // Whether or not std::wstring and llutf16string are distinct types, they + // both hold UTF-16LE characters. (See header file comments.) Pretend this + // wchar_t* sequence is really a U16* sequence and use the conversion we + // define above. + return utf16str_to_wstring(reinterpret_cast<const U16*>(in), len); +} + +std::wstring ll_convert_wstring_to_wide(const llwchar* in, size_t len) +{ + // first, convert to llutf16string, for which we have a real implementation + auto utf16str{ wstring_to_utf16str(in, len) }; + // then, because each U16 char must be UTF-16LE encoded, pretend the U16* + // string pointer is a wchar_t* and instantiate a std::wstring of the same + // length. + return { reinterpret_cast<const wchar_t*>(utf16str.c_str()), utf16str.length() }; +} + +std::string ll_convert_string_to_utf8_string(const std::string& in) +{ + // If you pass code_page, you must also pass length, otherwise the code + // page parameter will be mistaken for length. + auto w_mesg = ll_convert_string_to_wide(in, in.length(), CP_ACP); + // CP_UTF8 is default -- see ll_wstring_default_code_page() above. + return ll_convert_wide_to_string(w_mesg); +} + +namespace +{ + +void HeapFree_deleter(void* ptr) +{ + // instead of LocalFree(), per https://stackoverflow.com/a/31541205 + HeapFree(GetProcessHeap(), NULL, ptr); +} + +} // anonymous namespace + +template<> +std::wstring windows_message<std::wstring>(DWORD error) +{ + // derived from https://stackoverflow.com/a/455533 + wchar_t* rawptr = nullptr; + auto okay = FormatMessageW( + // use system message tables for GetLastError() codes + FORMAT_MESSAGE_FROM_SYSTEM | + // internally allocate buffer and return its pointer + FORMAT_MESSAGE_ALLOCATE_BUFFER | + // you cannot pass insertion parameters (thanks Gandalf) + FORMAT_MESSAGE_IGNORE_INSERTS | + // ignore line breaks in message definition text + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, // lpSource, unused with FORMAT_MESSAGE_FROM_SYSTEM + error, // dwMessageId + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId + (LPWSTR)&rawptr, // lpBuffer: force-cast wchar_t** to wchar_t* + 0, // nSize, unused with FORMAT_MESSAGE_ALLOCATE_BUFFER + NULL); // Arguments, unused + + // make a unique_ptr from rawptr so it gets cleaned up properly + std::unique_ptr<wchar_t, void(*)(void*)> bufferptr(rawptr, HeapFree_deleter); + + if (okay && bufferptr) + { + // got the message, return it ('okay' is length in characters) + return { bufferptr.get(), okay }; + } + + // did not get the message, synthesize one + auto format_message_error = GetLastError(); + std::wostringstream out; + out << L"GetLastError() " << error << L" (FormatMessageW() failed with " + << format_message_error << L")"; + return out.str(); +} + +std::optional<std::wstring> llstring_getoptenv(const std::string& key) +{ + auto wkey = ll_convert_string_to_wide(key); + // Take a wild guess as to how big the buffer should be. + std::vector<wchar_t> buffer(1024); + auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); + // If our initial guess was too short, n will indicate the size (in + // wchar_t's) that buffer should have been, including the terminating nul. + if (n > (buffer.size() - 1)) + { + // make it big enough + buffer.resize(n); + // and try again + n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); + } + // did that (ultimately) succeed? + if (n) + { + // great, return populated std::optional + return std::make_optional<std::wstring>(&buffer[0]); + } + + // not successful + auto last_error = GetLastError(); + // Don't bother warning for NOT_FOUND; that's an expected case + if (last_error != ERROR_ENVVAR_NOT_FOUND) + { + LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: " + << windows_message<std::string>(last_error) << LL_ENDL; + } + // return empty std::optional + return {}; +} + +#else // ! LL_WINDOWS + +std::optional<std::string> llstring_getoptenv(const std::string& key) +{ + auto found = getenv(key.c_str()); + if (found) + { + // return populated std::optional + return std::make_optional<std::string>(found); + } + else + { + // return empty std::optional + return {}; + } +} + +#endif // ! LL_WINDOWS + +long LLStringOps::sPacificTimeOffset = 0; +long LLStringOps::sLocalTimeOffset = 0; +bool LLStringOps::sPacificDaylightTime = 0; +std::map<std::string, std::string> LLStringOps::datetimeToCodes; + +std::vector<std::string> LLStringOps::sWeekDayList; +std::vector<std::string> LLStringOps::sWeekDayShortList; +std::vector<std::string> LLStringOps::sMonthList; +std::vector<std::string> LLStringOps::sMonthShortList; + + +std::string LLStringOps::sDayFormat; +std::string LLStringOps::sAM; +std::string LLStringOps::sPM; + +// static +bool LLStringOps::isEmoji(llwchar a) +{ +#if 0 // Do not consider special characters that might have a corresponding + // glyph in the monochorme fallback fonts as a "genuine" emoji. HB + return a == 0xa9 || a == 0xae || (a >= 0x2000 && a < 0x3300) || + (a >= 0x1f000 && a < 0x20000); +#else + // These are indeed "genuine" emojis, we *do want* rendered as such. HB + return a >= 0x1f000 && a < 0x20000; +#endif + } + +S32 LLStringOps::collate(const llwchar* a, const llwchar* b) +{ + #if LL_WINDOWS + // in Windows, wide string functions operator on 16-bit strings, + // not the proper 32 bit wide string + return strcmp(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str()); + #else + return wcscoll(a, b); + #endif +} + +void LLStringOps::setupDatetimeInfo (bool daylight) +{ + time_t nowT, localT, gmtT; + struct tm * tmpT; + + nowT = time (NULL); + + tmpT = gmtime (&nowT); + gmtT = mktime (tmpT); + + tmpT = localtime (&nowT); + localT = mktime (tmpT); + + sLocalTimeOffset = (long) (gmtT - localT); + if (tmpT->tm_isdst) + { + sLocalTimeOffset -= 60 * 60; // 1 hour + } + + sPacificDaylightTime = daylight; + sPacificTimeOffset = (sPacificDaylightTime? 7 : 8 ) * 60 * 60; + + datetimeToCodes["wkday"] = "%a"; // Thu + datetimeToCodes["weekday"] = "%A"; // Thursday + datetimeToCodes["year4"] = "%Y"; // 2009 + datetimeToCodes["year"] = "%Y"; // 2009 + datetimeToCodes["year2"] = "%y"; // 09 + datetimeToCodes["mth"] = "%b"; // Aug + datetimeToCodes["month"] = "%B"; // August + datetimeToCodes["mthnum"] = "%m"; // 08 + datetimeToCodes["day"] = "%d"; // 31 + datetimeToCodes["sday"] = "%-d"; // 9 + datetimeToCodes["hour24"] = "%H"; // 14 + datetimeToCodes["hour"] = "%H"; // 14 + datetimeToCodes["hour12"] = "%I"; // 02 + datetimeToCodes["min"] = "%M"; // 59 + datetimeToCodes["ampm"] = "%p"; // AM + datetimeToCodes["second"] = "%S"; // 59 + datetimeToCodes["timezone"] = "%Z"; // PST +} + +void tokenizeStringToArray(const std::string& data, std::vector<std::string>& output) +{ + output.clear(); + size_t length = data.size(); + + // tokenize it and put it in the array + std::string cur_word; + for(size_t i = 0; i < length; ++i) + { + if(data[i] == ':') + { + output.push_back(cur_word); + cur_word.clear(); + } + else + { + cur_word.append(1, data[i]); + } + } + output.push_back(cur_word); +} + +void LLStringOps::setupWeekDaysNames(const std::string& data) +{ + tokenizeStringToArray(data,sWeekDayList); +} +void LLStringOps::setupWeekDaysShortNames(const std::string& data) +{ + tokenizeStringToArray(data,sWeekDayShortList); +} +void LLStringOps::setupMonthNames(const std::string& data) +{ + tokenizeStringToArray(data,sMonthList); +} +void LLStringOps::setupMonthShortNames(const std::string& data) +{ + tokenizeStringToArray(data,sMonthShortList); +} +void LLStringOps::setupDayFormat(const std::string& data) +{ + sDayFormat = data; +} + + +std::string LLStringOps::getDatetimeCode (std::string key) +{ + std::map<std::string, std::string>::iterator iter; + + iter = datetimeToCodes.find (key); + if (iter != datetimeToCodes.end()) + { + return iter->second; + } + else + { + return std::string(""); + } +} + +std::string LLStringOps::getReadableNumber(F64 num) +{ + if (fabs(num)>=1e9) + { + return llformat("%.2lfB", num / 1e9); + } + else if (fabs(num)>=1e6) + { + return llformat("%.2lfM", num / 1e6); + } + else if (fabs(num)>=1e3) + { + return llformat("%.2lfK", num / 1e3); + } + else + { + return llformat("%.2lf", num); + } +} + +namespace LLStringFn +{ + // NOTE - this restricts output to ascii + void replace_nonprintable_in_ascii(std::basic_string<char>& string, char replacement) + { + const char MIN = 0x20; + std::basic_string<char>::size_type len = string.size(); + for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii) + { + if(string[ii] < MIN) + { + string[ii] = replacement; + } + } + } + + + // NOTE - this restricts output to ascii + void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str, + char replacement) + { + const char MIN = 0x20; + const char PIPE = 0x7c; + std::basic_string<char>::size_type len = str.size(); + for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii) + { + if( (str[ii] < MIN) || (str[ii] == PIPE) ) + { + str[ii] = replacement; + } + } + } + + // https://wiki.lindenlab.com/wiki/Unicode_Guidelines has details on + // allowable code points for XML. Specifically, they are: + // 0x09, 0x0a, 0x0d, and 0x20 on up. JC + std::string strip_invalid_xml(const std::string& instr) + { + std::string output; + output.reserve( instr.size() ); + std::string::const_iterator it = instr.begin(); + while (it != instr.end()) + { + // Must compare as unsigned for >= + // Test most likely match first + const unsigned char c = (unsigned char)*it; + if ( c >= (unsigned char)0x20 // SPACE + || c == (unsigned char)0x09 // TAB + || c == (unsigned char)0x0a // LINE_FEED + || c == (unsigned char)0x0d ) // CARRIAGE_RETURN + { + output.push_back(c); + } + ++it; + } + return output; + } + + /** + * @brief Replace all control characters (c < 0x20) with replacement in + * string. + */ + void replace_ascii_controlchars(std::basic_string<char>& string, char replacement) + { + const unsigned char MIN = 0x20; + std::basic_string<char>::size_type len = string.size(); + for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii) + { + const unsigned char c = (unsigned char) string[ii]; + if(c < MIN) + { + string[ii] = replacement; + } + } + } +} + +//////////////////////////////////////////////////////////// + +// Forward specialization of LLStringUtil::format before use in LLStringUtil::formatDatetime. +template<> +S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions); + +//static +template<> +void LLStringUtil::getTokens(const std::string& instr, std::vector<std::string >& tokens, const std::string& delims) +{ + // Starting at offset 0, scan forward for the next non-delimiter. We're + // done when the only characters left in 'instr' are delimiters. + for (std::string::size_type begIdx, endIdx = 0; + (begIdx = instr.find_first_not_of (delims, endIdx)) != std::string::npos; ) + { + // Found a non-delimiter. After that, find the next delimiter. + endIdx = instr.find_first_of (delims, begIdx); + if (endIdx == std::string::npos) + { + // No more delimiters: this token extends to the end of the string. + endIdx = instr.length(); + } + + // extract the token between begIdx and endIdx; substr() needs length + std::string currToken(instr.substr(begIdx, endIdx - begIdx)); + LLStringUtil::trim (currToken); + tokens.push_back(currToken); + // next scan past delimiters starts at endIdx + } +} + +template<> +LLStringUtil::size_type LLStringUtil::getSubstitution(const std::string& instr, size_type& start, std::vector<std::string>& tokens) +{ + const std::string delims (","); + + // Find the first [ + size_type pos1 = instr.find('[', start); + if (pos1 == std::string::npos) + return std::string::npos; + + //Find the first ] after the initial [ + size_type pos2 = instr.find(']', pos1); + if (pos2 == std::string::npos) + return std::string::npos; + + // Find the last [ before ] in case of nested [[]] + pos1 = instr.find_last_of('[', pos2-1); + if (pos1 == std::string::npos || pos1 < start) + return std::string::npos; + + getTokens(std::string(instr,pos1+1,pos2-pos1-1), tokens, delims); + start = pos2+1; + + return pos1; +} + +// static +template<> +bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const format_map_t& substitutions) +{ + // see if we have a replacement for the bracketed string (without the brackets) + // test first using has() because if we just look up with operator[] we get back an + // empty string even if the value is missing. We want to distinguish between + // missing replacements and deliberately empty replacement strings. + format_map_t::const_iterator iter = substitutions.find(token); + if (iter != substitutions.end()) + { + replacement = iter->second; + return true; + } + // if not, see if there's one WITH brackets + iter = substitutions.find(std::string("[" + token + "]")); + if (iter != substitutions.end()) + { + replacement = iter->second; + return true; + } + + return false; +} + +// static +template<> +bool LLStringUtil::simpleReplacement(std::string &replacement, std::string token, const LLSD& substitutions) +{ + // see if we have a replacement for the bracketed string (without the brackets) + // test first using has() because if we just look up with operator[] we get back an + // empty string even if the value is missing. We want to distinguish between + // missing replacements and deliberately empty replacement strings. + if (substitutions.has(token)) + { + replacement = substitutions[token].asString(); + return true; + } + // if not, see if there's one WITH brackets + else if (substitutions.has(std::string("[" + token + "]"))) + { + replacement = substitutions[std::string("[" + token + "]")].asString(); + return true; + } + + return false; +} + +//static +template<> +void LLStringUtil::setLocale(std::string inLocale) +{ + sLocale = inLocale; +}; + +//static +template<> +std::string LLStringUtil::getLocale(void) +{ + return sLocale; +}; + +// static +template<> +void LLStringUtil::formatNumber(std::string& numStr, std::string decimals) +{ + std::stringstream strStream; + S32 intDecimals = 0; + + convertToS32 (decimals, intDecimals); + if (!sLocale.empty()) + { + // std::locale() throws if the locale is unknown! (EXT-7926) + try + { + strStream.imbue(std::locale(sLocale.c_str())); + } catch (const std::exception &) + { + LL_WARNS_ONCE("Locale") << "Cannot set locale to " << sLocale << LL_ENDL; + } + } + + if (!intDecimals) + { + S32 intStr; + + if (convertToS32(numStr, intStr)) + { + strStream << intStr; + numStr = strStream.str(); + } + } + else + { + F32 floatStr; + + if (convertToF32(numStr, floatStr)) + { + strStream << std::fixed << std::showpoint << std::setprecision(intDecimals) << floatStr; + numStr = strStream.str(); + } + } +} + +// static +template<> +bool LLStringUtil::formatDatetime(std::string& replacement, std::string token, + std::string param, S32 secFromEpoch) +{ + if (param == "local") // local + { + secFromEpoch -= LLStringOps::getLocalTimeOffset(); + } + else if (param != "utc") // slt + { + secFromEpoch -= LLStringOps::getPacificTimeOffset(); + } + + // if never fell into those two ifs above, param must be utc + if (secFromEpoch < 0) secFromEpoch = 0; + + LLDate datetime((F64)secFromEpoch); + std::string code = LLStringOps::getDatetimeCode (token); + + // special case to handle timezone + if (code == "%Z") { + if (param == "utc") + { + replacement = "GMT"; + } + else if (param == "local") + { + replacement = ""; // user knows their own timezone + } + else + { +#if 0 + // EXT-1565 : Zai Lynch, James Linden : 15/Oct/09 + // [BSI] Feedback: Viewer clock mentions SLT, but would prefer it to show PST/PDT + // "slt" = Second Life Time, which is deprecated. + // If not utc or user local time, fallback to Pacific time + replacement = LLStringOps::getPacificDaylightTime() ? "PDT" : "PST"; +#else + // SL-20370 : Steeltoe Linden : 29/Sep/23 + // Change "PDT" to "SLT" on menu bar + replacement = "SLT"; +#endif + } + return true; + } + + //EXT-7013 + //few codes are not suppotred by strtime function (example - weekdays for Japanise) + //so use predefined ones + + //if sWeekDayList is not empty than current locale doesn't support + //weekday name. + time_t loc_seconds = (time_t) secFromEpoch; + if(LLStringOps::sWeekDayList.size() == 7 && code == "%A") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sWeekDayList[gmt->tm_wday]; + } + else if(LLStringOps::sWeekDayShortList.size() == 7 && code == "%a") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sWeekDayShortList[gmt->tm_wday]; + } + else if(LLStringOps::sMonthList.size() == 12 && code == "%B") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sMonthList[gmt->tm_mon]; + } + else if( !LLStringOps::sDayFormat.empty() && code == "%d" ) + { + struct tm * gmt = gmtime (&loc_seconds); + LLStringUtil::format_map_t args; + args["[MDAY]"] = llformat ("%d", gmt->tm_mday); + replacement = LLStringOps::sDayFormat; + LLStringUtil::format(replacement, args); + } + else if (code == "%-d") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = llformat ("%d", gmt->tm_mday); // day of the month without leading zero + } + else if( !LLStringOps::sAM.empty() && !LLStringOps::sPM.empty() && code == "%p" ) + { + struct tm * gmt = gmtime (&loc_seconds); + if(gmt->tm_hour<12) + { + replacement = LLStringOps::sAM; + } + else + { + replacement = LLStringOps::sPM; + } + } + else + { + replacement = datetime.toHTTPDateString(code); + } + + // *HACK: delete leading zero from hour string in case 'hour12' (code = %I) time format + // to show time without leading zero, e.g. 08:16 -> 8:16 (EXT-2738). + // We could have used '%l' format instead, but it's not supported by Windows. + if(code == "%I" && token == "hour12" && replacement.at(0) == '0') + { + replacement = replacement.at(1); + } + + return !code.empty(); +} + +// LLStringUtil::format recogizes the following patterns. +// All substitutions *must* be encased in []'s in the input string. +// The []'s are optional in the substitution map. +// [FOO_123] +// [FOO,number,precision] +// [FOO,datetime,format] + + +// static +template<> +S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; + S32 res = 0; + + std::string output; + std::vector<std::string> tokens; + + std::string::size_type start = 0; + std::string::size_type prev_start = 0; + std::string::size_type key_start = 0; + while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) + { + output += std::string(s, prev_start, key_start-prev_start); + prev_start = start; + + bool found_replacement = false; + std::string replacement; + + if (tokens.size() == 0) + { + found_replacement = false; + } + else if (tokens.size() == 1) + { + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + } + else if (tokens[1] == "number") + { + std::string param = "0"; + + if (tokens.size() > 2) param = tokens[2]; + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + if (found_replacement) formatNumber (replacement, param); + } + else if (tokens[1] == "datetime") + { + std::string param; + if (tokens.size() > 2) param = tokens[2]; + + format_map_t::const_iterator iter = substitutions.find("datetime"); + if (iter != substitutions.end()) + { + S32 secFromEpoch = 0; + bool r = LLStringUtil::convertToS32(iter->second, secFromEpoch); + if (r) + { + found_replacement = formatDatetime(replacement, tokens[0], param, secFromEpoch); + } + } + } + + if (found_replacement) + { + output += replacement; + res++; + } + else + { + // we had no replacement, use the string as is + // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" + output += std::string(s, key_start, start-key_start); + } + tokens.clear(); + } + // send the remainder of the string (with no further matches for bracketed names) + output += std::string(s, start); + s = output; + return res; +} + +//static +template<> +S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; + S32 res = 0; + + if (!substitutions.isMap()) + { + return res; + } + + std::string output; + std::vector<std::string> tokens; + + std::string::size_type start = 0; + std::string::size_type prev_start = 0; + std::string::size_type key_start = 0; + while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) + { + output += std::string(s, prev_start, key_start-prev_start); + prev_start = start; + + bool found_replacement = false; + std::string replacement; + + if (tokens.size() == 0) + { + found_replacement = false; + } + else if (tokens.size() == 1) + { + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + } + else if (tokens[1] == "number") + { + std::string param = "0"; + + if (tokens.size() > 2) param = tokens[2]; + found_replacement = simpleReplacement (replacement, tokens[0], substitutions); + if (found_replacement) formatNumber (replacement, param); + } + else if (tokens[1] == "datetime") + { + std::string param; + if (tokens.size() > 2) param = tokens[2]; + + S32 secFromEpoch = (S32) substitutions["datetime"].asInteger(); + found_replacement = formatDatetime (replacement, tokens[0], param, secFromEpoch); + } + + if (found_replacement) + { + output += replacement; + res++; + } + else + { + // we had no replacement, use the string as is + // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" + output += std::string(s, key_start, start-key_start); + } + tokens.clear(); + } + // send the remainder of the string (with no further matches for bracketed names) + output += std::string(s, start); + s = output; + return res; +} + +//////////////////////////////////////////////////////////// +// Testing + +#ifdef _DEBUG + +template<class T> +void LLStringUtilBase<T>::testHarness() +{ + std::string s1; + + llassert( s1.c_str() == NULL ); + llassert( s1.size() == 0 ); + llassert( s1.empty() ); + + std::string s2( "hello"); + llassert( !strcmp( s2.c_str(), "hello" ) ); + llassert( s2.size() == 5 ); + llassert( !s2.empty() ); + std::string s3( s2 ); + + llassert( "hello" == s2 ); + llassert( s2 == "hello" ); + llassert( s2 > "gello" ); + llassert( "gello" < s2 ); + llassert( "gello" != s2 ); + llassert( s2 != "gello" ); + + std::string s4 = s2; + llassert( !s4.empty() ); + s4.empty(); + llassert( s4.empty() ); + + std::string s5(""); + llassert( s5.empty() ); + + llassert( isValidIndex(s5, 0) ); + llassert( !isValidIndex(s5, 1) ); + + s3 = s2; + s4 = "hello again"; + + s4 += "!"; + s4 += s4; + llassert( s4 == "hello again!hello again!" ); + + + std::string s6 = s2 + " " + s2; + std::string s7 = s6; + llassert( s6 == s7 ); + llassert( !( s6 != s7) ); + llassert( !(s6 < s7) ); + llassert( !(s6 > s7) ); + + llassert( !(s6 == "hi")); + llassert( s6 == "hello hello"); + llassert( s6 < "hi"); + + llassert( s6[1] == 'e' ); + s6[1] = 'f'; + llassert( s6[1] == 'f' ); + + s2.erase( 4, 1 ); + llassert( s2 == "hell"); + s2.insert( 0, "y" ); + llassert( s2 == "yhell"); + s2.erase( 1, 3 ); + llassert( s2 == "yl"); + s2.insert( 1, "awn, don't yel"); + llassert( s2 == "yawn, don't yell"); + + std::string s8 = s2.substr( 6, 5 ); + llassert( s8 == "don't" ); + + std::string s9 = " \t\ntest \t\t\n "; + trim(s9); + llassert( s9 == "test" ); + + s8 = "abc123&*(ABC"; + + s9 = s8; + toUpper(s9); + llassert( s9 == "ABC123&*(ABC" ); + + s9 = s8; + toLower(s9); + llassert( s9 == "abc123&*(abc" ); + + + std::string s10( 10, 'x' ); + llassert( s10 == "xxxxxxxxxx" ); + + std::string s11( "monkey in the middle", 7, 2 ); + llassert( s11 == "in" ); + + std::string s12; //empty + s12 += "foo"; + llassert( s12 == "foo" ); + + std::string s13; //empty + s13 += 'f'; + llassert( s13 == "f" ); +} + + +#endif // _DEBUG diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 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 <boost/call_traits.hpp>
-#include <optional>
-#include <string>
-#include <string_view>
-#include <cstdio>
-#include <cwchar> // std::wcslen()
-//#include <locale>
-#include <iomanip>
-#include <algorithm>
-#include <vector>
-#include <map>
-#include "llformat.h"
-
-#if LL_LINUX
-#include <wctype.h>
-#include <wchar.h>
-#endif
-
-#include <string.h>
-#include <boost/scoped_ptr.hpp>
-
-const char LL_UNKNOWN_CHAR = '?';
-class LLSD;
-
-#if LL_DARWIN || LL_LINUX
-// Template specialization of char_traits for U16s. Only necessary on Mac and Linux (exists on Windows already)
-#include <cstring>
-
-namespace std
-{
-template<>
-struct char_traits<U16>
-{
- typedef U16 char_type;
- typedef int int_type;
- typedef streampos pos_type;
- typedef streamoff off_type;
- typedef mbstate_t state_type;
-
- static void
- assign(char_type& __c1, const char_type& __c2)
- { __c1 = __c2; }
-
- static bool
- eq(const char_type& __c1, const char_type& __c2)
- { return __c1 == __c2; }
-
- static bool
- lt(const char_type& __c1, const char_type& __c2)
- { return __c1 < __c2; }
-
- static int
- compare(const char_type* __s1, const char_type* __s2, size_t __n)
- { return memcmp(__s1, __s2, __n * sizeof(char_type)); }
-
- static size_t
- length(const char_type* __s)
- {
- const char_type *cur_char = __s;
- while (*cur_char != 0)
- {
- ++cur_char;
- }
- return cur_char - __s;
- }
-
- static const char_type*
- find(const char_type* __s, size_t __n, const char_type& __a)
- { return static_cast<const char_type*>(memchr(__s, __a, __n * sizeof(char_type))); }
-
- static char_type*
- move(char_type* __s1, const char_type* __s2, size_t __n)
- { return static_cast<char_type*>(memmove(__s1, __s2, __n * sizeof(char_type))); }
-
- static char_type*
- copy(char_type* __s1, const char_type* __s2, size_t __n)
- { return static_cast<char_type*>(memcpy(__s1, __s2, __n * sizeof(char_type))); } /* Flawfinder: ignore */
-
- static char_type*
- assign(char_type* __s, size_t __n, char_type __a)
- {
- // This isn't right.
- //return static_cast<char_type*>(memset(__s, __a, __n * sizeof(char_type)));
-
- // I don't think there's a standard 'memset' for 16-bit values.
- // Do this the old-fashioned way.
-
- size_t __i;
- for(__i = 0; __i < __n; __i++)
- {
- __s[__i] = __a;
- }
- return __s;
- }
-
- static char_type
- to_char_type(const int_type& __c)
- { return static_cast<char_type>(__c); }
-
- static int_type
- to_int_type(const char_type& __c)
- { return static_cast<int_type>(__c); }
-
- static bool
- eq_int_type(const int_type& __c1, const int_type& __c2)
- { return __c1 == __c2; }
-
- static int_type
- eof() { return static_cast<int_type>(EOF); }
-
- static int_type
- not_eof(const int_type& __c)
- { return (__c == eof()) ? 0 : __c; }
- };
-};
-#endif
-
-class LL_COMMON_API LLStringOps
-{
-private:
- static long sPacificTimeOffset;
- static long sLocalTimeOffset;
- static bool sPacificDaylightTime;
-
- static std::map<std::string, std::string> datetimeToCodes;
-
-public:
- static std::vector<std::string> sWeekDayList;
- static std::vector<std::string> sWeekDayShortList;
- static std::vector<std::string> sMonthList;
- static std::vector<std::string> sMonthShortList;
- static std::string sDayFormat;
-
- static std::string sAM;
- static std::string sPM;
-
- static char toUpper(char elem) { return toupper((unsigned char)elem); }
- static llwchar toUpper(llwchar elem) { return towupper(elem); }
-
- static char toLower(char elem) { return tolower((unsigned char)elem); }
- static llwchar toLower(llwchar elem) { return towlower(elem); }
-
- static bool isSpace(char elem) { return isspace((unsigned char)elem) != 0; }
- static bool isSpace(llwchar elem) { return iswspace(elem) != 0; }
-
- static bool isUpper(char elem) { return isupper((unsigned char)elem) != 0; }
- static bool isUpper(llwchar elem) { return iswupper(elem) != 0; }
-
- static bool isLower(char elem) { return islower((unsigned char)elem) != 0; }
- static bool isLower(llwchar elem) { return iswlower(elem) != 0; }
-
- static bool isDigit(char a) { return isdigit((unsigned char)a) != 0; }
- static bool isDigit(llwchar a) { return iswdigit(a) != 0; }
-
- static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; }
- static bool isPunct(llwchar a) { return iswpunct(a) != 0; }
-
- static bool isAlpha(char a) { return isalpha((unsigned char)a) != 0; }
- static bool isAlpha(llwchar a) { return iswalpha(a) != 0; }
-
- static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; }
- static bool isAlnum(llwchar a) { return iswalnum(a) != 0; }
-
- // Returns true when 'a' corresponds to a "genuine" emoji. HB
- static bool isEmoji(llwchar a);
-
- static S32 collate(const char* a, const char* b) { return strcoll(a, b); }
- static S32 collate(const llwchar* a, const llwchar* b);
-
- static void setupDatetimeInfo(bool pacific_daylight_time);
-
- static void setupWeekDaysNames(const std::string& data);
- static void setupWeekDaysShortNames(const std::string& data);
- static void setupMonthNames(const std::string& data);
- static void setupMonthShortNames(const std::string& data);
- static void setupDayFormat(const std::string& data);
-
-
- static long getPacificTimeOffset(void) { return sPacificTimeOffset;}
- static long getLocalTimeOffset(void) { return sLocalTimeOffset;}
- // Is the Pacific time zone (aka server time zone)
- // currently in daylight savings time?
- static bool getPacificDaylightTime(void) { return sPacificDaylightTime;}
-
- static std::string getDatetimeCode (std::string key);
-
- // Express a value like 1234567 as "1.23M"
- static std::string getReadableNumber(F64 num);
-};
-
-/**
- * @brief Return a string constructed from in without crashing if the
- * pointer is NULL.
- */
-LL_COMMON_API std::string ll_safe_string(const char* in);
-LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen);
-
-
-// Allowing assignments from non-strings into format_map_t is apparently
-// *really* error-prone, so subclass std::string with just basic c'tors.
-class LLFormatMapString
-{
-public:
- LLFormatMapString() {};
- LLFormatMapString(const char* s) : mString(ll_safe_string(s)) {};
- LLFormatMapString(const std::string& s) : mString(s) {};
- operator std::string() const { return mString; }
- bool operator<(const LLFormatMapString& rhs) const { return mString < rhs.mString; }
- std::size_t length() const { return mString.length(); }
-
-private:
- std::string mString;
-};
-
-template <class T>
-class LLStringUtilBase
-{
-private:
- static std::string sLocale;
-
-public:
- typedef std::basic_string<T> string_type;
- typedef typename string_type::size_type size_type;
-
-public:
- /////////////////////////////////////////////////////////////////////////////////////////
- // Static Utility functions that operate on std::strings
-
- static const string_type null;
-
- typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t;
- /// considers any sequence of delims as a single field separator
- LL_COMMON_API static void getTokens(const string_type& instr,
- std::vector<string_type >& tokens,
- const string_type& delims);
- /// like simple scan overload, but returns scanned vector
- static std::vector<string_type> getTokens(const string_type& instr,
- const string_type& delims);
- /// add support for keep_delims and quotes (either could be empty string)
- static void getTokens(const string_type& instr,
- std::vector<string_type>& tokens,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes=string_type());
- /// like keep_delims-and-quotes overload, but returns scanned vector
- static std::vector<string_type> getTokens(const string_type& instr,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes=string_type());
- /// add support for escapes (could be empty string)
- static void getTokens(const string_type& instr,
- std::vector<string_type>& tokens,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes,
- const string_type& escapes);
- /// like escapes overload, but returns scanned vector
- static std::vector<string_type> getTokens(const string_type& instr,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes,
- const string_type& escapes);
-
- LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals);
- LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch);
- LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions);
- LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions);
- LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions);
- LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions);
- LL_COMMON_API static void setLocale (std::string inLocale);
- LL_COMMON_API static std::string getLocale (void);
-
- static bool isValidIndex(const string_type& string, size_type i)
- {
- return !string.empty() && (0 <= i) && (i <= string.size());
- }
-
- static bool contains(const string_type& string, T c, size_type i=0)
- {
- return string.find(c, i) != string_type::npos;
- }
-
- static void trimHead(string_type& string);
- static void trimTail(string_type& string);
- static void trim(string_type& string) { trimHead(string); trimTail(string); }
- static void truncate(string_type& string, size_type count);
-
- static void toUpper(string_type& string);
- static void toLower(string_type& string);
-
- // True if this is the head of s.
- static bool isHead( const string_type& string, const T* s );
-
- /**
- * @brief Returns true if string starts with substr
- *
- * If etither string or substr are empty, this method returns false.
- */
- static bool startsWith(
- const string_type& string,
- const string_type& substr);
-
- /**
- * @brief Returns true if string ends in substr
- *
- * If etither string or substr are empty, this method returns false.
- */
- static bool endsWith(
- const string_type& string,
- const string_type& substr);
-
- /**
- * get environment string value with proper Unicode handling
- * (key is always UTF-8)
- * detect absence by return value == dflt
- */
- static string_type getenv(const std::string& key, const string_type& dflt="");
- /**
- * get optional environment string value with proper Unicode handling
- * (key is always UTF-8)
- * detect absence by (! return value)
- */
- static std::optional<string_type> getoptenv(const std::string& key);
-
- static void addCRLF(string_type& string);
- static void removeCRLF(string_type& string);
- static void removeWindowsCR(string_type& string);
-
- static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab );
- static void replaceNonstandardASCII( string_type& string, T replacement );
- static void replaceChar( string_type& string, T target, T replacement );
- static void replaceString( string_type& string, string_type target, string_type replacement );
- static string_type capitalize(const string_type& str);
- static void capitalize(string_type& str);
-
- static bool containsNonprintable(const string_type& string);
- static void stripNonprintable(string_type& string);
-
- /**
- * Double-quote an argument string if needed, unless it's already
- * double-quoted. Decide whether it's needed based on the presence of any
- * character in @a triggers (default space or double-quote). If we quote
- * it, escape any embedded double-quote with the @a escape string (default
- * backslash).
- *
- * Passing triggers="" means always quote, unless it's already double-quoted.
- */
- static string_type quote(const string_type& str,
- const string_type& triggers=" \"",
- const string_type& escape="\\");
-
- /**
- * @brief Unsafe way to make ascii characters. You should probably
- * only call this when interacting with the host operating system.
- * The 1 byte std::string does not work correctly.
- * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII
- * should work.
- */
- static void _makeASCII(string_type& string);
-
- // Conversion to other data types
- static bool convertToBOOL(const string_type& string, bool& value);
- static bool convertToU8(const string_type& string, U8& value);
- static bool convertToS8(const string_type& string, S8& value);
- static bool convertToS16(const string_type& string, S16& value);
- static bool convertToU16(const string_type& string, U16& value);
- static bool convertToU32(const string_type& string, U32& value);
- static bool convertToS32(const string_type& string, S32& value);
- static bool convertToF32(const string_type& string, F32& value);
- static bool convertToF64(const string_type& string, F64& value);
-
- /////////////////////////////////////////////////////////////////////////////////////////
- // Utility functions for working with char*'s and strings
-
- // Like strcmp but also handles empty strings. Uses
- // current locale.
- static S32 compareStrings(const T* lhs, const T* rhs);
- static S32 compareStrings(const string_type& lhs, const string_type& rhs);
-
- // case insensitive version of above. Uses current locale on
- // Win32, and falls back to a non-locale aware comparison on
- // Linux.
- static S32 compareInsensitive(const T* lhs, const T* rhs);
- static S32 compareInsensitive(const string_type& lhs, const string_type& rhs);
-
- // Case sensitive comparison with good handling of numbers. Does not use current locale.
- // a.k.a. strdictcmp()
- static S32 compareDict(const string_type& a, const string_type& b);
-
- // Case *in*sensitive comparison with good handling of numbers. Does not use current locale.
- // a.k.a. strdictcmp()
- static S32 compareDictInsensitive(const string_type& a, const string_type& b);
-
- // Puts compareDict() in a form appropriate for LL container classes to use for sorting.
- static bool precedesDict( const string_type& a, const string_type& b );
-
- // A replacement for strncpy.
- // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds
- // up to dst_size-1 characters of src.
- static void copy(T* dst, const T* src, size_type dst_size);
-
- // Copies src into dst at a given offset.
- static void copyInto(string_type& dst, const string_type& src, size_type offset);
-
- static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); }
-
-
-#ifdef _DEBUG
- LL_COMMON_API static void testHarness();
-#endif
-
-private:
- LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector<string_type >& tokens);
-};
-
-template<class T> const std::basic_string<T> LLStringUtilBase<T>::null;
-template<class T> std::string LLStringUtilBase<T>::sLocale;
-
-typedef LLStringUtilBase<char> LLStringUtil;
-typedef LLStringUtilBase<llwchar> LLWStringUtil;
-typedef std::basic_string<llwchar> LLWString;
-
-//@ Use this where we want to disallow input in the form of "foo"
-// This is used to catch places where english text is embedded in the code
-// instead of in a translatable XUI file.
-class LLStringExplicit : public std::string
-{
-public:
- explicit LLStringExplicit(const char* s) : std::string(s) {}
- LLStringExplicit(const std::string& s) : std::string(s) {}
- LLStringExplicit(const std::string& s, size_type pos, size_type n = std::string::npos) : std::string(s, pos, n) {}
-};
-
-struct LLDictionaryLess
-{
-public:
- bool operator()(const std::string& a, const std::string& b) const
- {
- return (LLStringUtil::precedesDict(a, b));
- }
-};
-
-
-/**
- * Simple support functions
- */
-
-/**
- * @brief chop off the trailing characters in a string.
- *
- * This function works on bytes rather than glyphs, so this will
- * incorrectly truncate non-single byte strings.
- * Use utf8str_truncate() for utf8 strings
- * @return a copy of in string minus the trailing count bytes.
- */
-inline std::string chop_tail_copy(
- const std::string& in,
- std::string::size_type count)
-{
- return std::string(in, 0, in.length() - count);
-}
-
-/**
- * @brief This translates a nybble stored as a hex value from 0-f back
- * to a nybble in the low order bits of the return byte.
- */
-LL_COMMON_API bool is_char_hex(char hex);
-LL_COMMON_API U8 hex_as_nybble(char hex);
-
-/**
- * @brief read the contents of a file into a string.
- *
- * Since this function has no concept of character encoding, most
- * anything you do with this method ill-advised. Please avoid.
- * @param str [out] The string which will have.
- * @param filename The full name of the file to read.
- * @return Returns true on success. If false, str is unmodified.
- */
-LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename);
-LL_COMMON_API bool iswindividual(llwchar elem);
-
-/**
- * Unicode support
- */
-
-/// generic conversion aliases
-template<typename TO, typename FROM, typename Enable=void>
-struct ll_convert_impl
-{
- // Don't even provide a generic implementation. We specialize for every
- // combination we do support.
- TO operator()(const FROM& in) const;
-};
-
-// Use a function template to get the nice ll_convert<TO>(from_value) API.
-template<typename TO, typename FROM>
-TO ll_convert(const FROM& in)
-{
- return ll_convert_impl<TO, FROM>()(in);
-}
-
-// degenerate case
-template<typename T>
-struct ll_convert_impl<T, T>
-{
- T operator()(const T& in) const { return in; }
-};
-
-// simple construction from char*
-template<typename T>
-struct ll_convert_impl<T, const typename T::value_type*>
-{
- T operator()(const typename T::value_type* in) const { return { in }; }
-};
-
-// specialize ll_convert_impl<TO, FROM> to return EXPR
-#define ll_convert_alias(TO, FROM, EXPR) \
-template<> \
-struct ll_convert_impl<TO, FROM> \
-{ \
- /* param_type optimally passes both char* and string */ \
- TO operator()(typename boost::call_traits<FROM>::param_type in) const { return EXPR; } \
-}
-
-// If all we're doing is copying characters, pass this to ll_convert_alias as
-// EXPR. Since it expands into the 'return EXPR' slot in the ll_convert_impl
-// specialization above, it implies TO{ in.begin(), in.end() }.
-#define LL_CONVERT_COPY_CHARS { in.begin(), in.end() }
-
-// Generic name for strlen() / wcslen() - the default implementation should
-// (!) work with U16 and llwchar, but we don't intend to engage it.
-template <typename CHARTYPE>
-size_t ll_convert_length(const CHARTYPE* zstr)
-{
- const CHARTYPE* zp;
- // classic C string scan
- for (zp = zstr; *zp; ++zp)
- ;
- return (zp - zstr);
-}
-
-// specialize where we have a library function; may use intrinsic operations
-template <>
-inline size_t ll_convert_length<wchar_t>(const wchar_t* zstr) { return std::wcslen(zstr); }
-template <>
-inline size_t ll_convert_length<char> (const char* zstr) { return std::strlen(zstr); }
-
-// ll_convert_forms() is short for a bunch of boilerplate. It defines
-// longname(const char*, len), longname(const char*), longname(const string&)
-// and longname(const string&, len) so calls written pre-ll_convert() will
-// work. Most of these overloads will be unified once we turn on C++17 and can
-// use std::string_view.
-// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*)
-// and ll_convert<OUTSTR>(const string&) will work.
-#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \
-LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \
-inline auto longname(const INSTR& in, size_t len) \
-{ \
- return longname(in.c_str(), len); \
-} \
-inline auto longname(const INSTR::value_type* in) \
-{ \
- return longname(in, ll_convert_length(in)); \
-} \
-inline auto longname(const INSTR& in) \
-{ \
- return longname(in.c_str(), in.length()); \
-} \
-/* string param */ \
-aliasmacro(OUTSTR, INSTR, longname(in)); \
-/* char* param */ \
-aliasmacro(OUTSTR, const INSTR::value_type*, longname(in))
-
-// Make the incoming string a utf8 string. Replaces any unknown glyph
-// with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest
-// of the data may not be recovered.
-LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw);
-
-//
-// We should never use UTF16 except when communicating with Win32!
-// https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t
-// nat 2018-12-14: I consider the whole llutf16string thing a mistake, because
-// the Windows APIs we want to call are all defined in terms of wchar_t*
-// (or worse, LPCTSTR).
-// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types
-
-// While there is no point coding for an ASCII-only world (! defined(UNICODE)),
-// use of U16 and llutf16string for Windows APIs locks in /Zc:wchar_t-. Going
-// forward, we should code in terms of wchar_t and std::wstring so as to
-// support either setting of /Zc:wchar_t.
-
-// The first link above states that char can be used to hold ASCII or any
-// multi-byte character set, and distinguishes wchar_t (UTF-16LE), char16_t
-// (UTF-16) and char32_t (UTF-32). Nonetheless, within this code base:
-// * char and std::string always hold UTF-8 (of which ASCII is a subset). It
-// is a BUG if they are used to pass strings in any other multi-byte
-// encoding.
-// * wchar_t and std::wstring should be our interface to Windows wide-string
-// APIs, and therefore hold UTF-16LE.
-// * U16 and llutf16string are the previous but DEPRECATED UTF-16LE type. Do
-// not introduce new uses of U16 or llutf16string for string data.
-// * llwchar and LLWString hold UTF-32 strings.
-// * Do not introduce char16_t or std::u16string.
-// * Do not introduce char32_t or std::u32string.
-//
-// This typedef may or may not be identical to std::wstring, depending on
-// LL_WCHAR_T_NATIVE.
-typedef std::basic_string<U16> llutf16string;
-
-// Considering wchar_t, llwchar and U16, there are three relevant cases:
-#if LLWCHAR_IS_WCHAR_T // every which way but Windows
-// llwchar is identical to wchar_t, LLWString is identical to std::wstring.
-// U16 is distinct, llutf16string is distinct (though pretty useless).
-// Given conversions to/from LLWString and to/from llutf16string, conversions
-// involving std::wstring would collide.
-#define ll_convert_wstr_alias(TO, FROM, EXPR) // nothing
-// but we can define conversions involving llutf16string without collisions
-#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
-
-#elif defined(LL_WCHAR_T_NATIVE) // Windows, either clang or MS /Zc:wchar_t
-// llwchar (32-bit), wchar_t (16-bit) and U16 are all different types.
-// Conversions to/from LLWString, to/from std::wstring and to/from llutf16string
-// can all be defined.
-#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
-#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
-
-#else // ! LL_WCHAR_T_NATIVE: Windows with MS /Zc:wchar_t-
-// wchar_t is identical to U16, std::wstring is identical to llutf16string.
-// Given conversions to/from LLWString and to/from std::wstring, conversions
-// involving llutf16string would collide.
-#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing
-// but we can define conversions involving std::wstring without collisions
-#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR)
-#endif
-
-ll_convert_forms(ll_convert_u16_alias, LLWString, llutf16string, utf16str_to_wstring);
-ll_convert_forms(ll_convert_u16_alias, llutf16string, LLWString, wstring_to_utf16str);
-ll_convert_forms(ll_convert_u16_alias, llutf16string, std::string, utf8str_to_utf16str);
-ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_wstring);
-
-// Same function, better name. JC
-inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); }
-
-LL_COMMON_API std::ptrdiff_t wchar_to_utf8chars(llwchar inchar, char* outchars);
-
-ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str);
-ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str);
-
-// an older alias for utf16str_to_utf8str(llutf16string)
-inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);}
-
-// Length of this UTF32 string in bytes when transformed to UTF8
-LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr);
-
-// Length in bytes of this wide char in a UTF8 string
-LL_COMMON_API S32 wchar_utf8_length(const llwchar wc);
-
-LL_COMMON_API std::string wchar_utf8_preview(const llwchar wc);
-
-LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str);
-
-// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string.
-LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len);
-
-// Length in utf16string (UTF-16) of wlen wchars beginning at woffset.
-LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen);
-
-// Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.)
-LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, bool *unaligned = nullptr);
-
-/**
- * @brief Properly truncate a utf8 string to a maximum byte count.
- *
- * The returned string may be less than max_len if the truncation
- * happens in the middle of a glyph. If max_len is longer than the
- * string passed in, the return value == utf8str.
- * @param utf8str A valid utf8 string to truncate.
- * @param max_len The maximum number of bytes in the return value.
- * @return Returns a valid utf8 string with byte count <= max_len.
- */
-LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32 max_len);
-
-LL_COMMON_API std::string utf8str_trim(const std::string& utf8str);
-
-LL_COMMON_API S32 utf8str_compare_insensitive(
- const std::string& lhs,
- const std::string& rhs);
-
-/**
-* @brief Properly truncate a utf8 string to a maximum character count.
-*
-* If symbol_len is longer than the string passed in, the return
-* value == utf8str.
-* @param utf8str A valid utf8 string to truncate.
-* @param symbol_len The maximum number of symbols in the return value.
-* @return Returns a valid utf8 string with symbol count <= max_len.
-*/
-LL_COMMON_API std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len);
-
-/**
- * @brief Replace all occurences of target_char with replace_char
- *
- * @param utf8str A utf8 string to process.
- * @param target_char The wchar to be replaced
- * @param replace_char The wchar which is written on replace
- */
-LL_COMMON_API std::string utf8str_substChar(
- const std::string& utf8str,
- const llwchar target_char,
- const llwchar replace_char);
-
-LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str);
-
-// Hack - used for evil notecards.
-LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str);
-
-LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str);
-
-LL_COMMON_API llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length);
-
-LL_COMMON_API std::string utf8str_showBytesUTF8(const std::string& utf8str);
-
-LL_COMMON_API bool wstring_has_emoji(const LLWString& wstr);
-
-LL_COMMON_API bool wstring_remove_emojis(LLWString& wstr);
-
-LL_COMMON_API bool utf8str_remove_emojis(std::string& utf8str);
-
-#if LL_WINDOWS
-/* @name Windows string helpers
- */
-//@{
-
-/**
- * @brief Convert a wide string to/from std::string
- * Convert a Windows wide string to/from our LLWString
- *
- * This replaces the unsafe W2A macro from ATL.
- */
-// Avoid requiring this header to #include the Windows header file declaring
-// our actual default code_page by delegating this function to our .cpp file.
-LL_COMMON_API unsigned int ll_wstring_default_code_page();
-
-// This is like ll_convert_forms(), with the added complexity of a code page
-// parameter that may or may not be passed.
-#define ll_convert_cp_forms(aliasmacro, OUTSTR, INSTR, longname) \
-/* declare the only nontrivial implementation (in .cpp file) */ \
-LL_COMMON_API OUTSTR longname( \
- const INSTR::value_type* in, \
- size_t len, \
- unsigned int code_page=ll_wstring_default_code_page()); \
-/* if passed only a char pointer, scan for nul terminator */ \
-inline auto longname(const INSTR::value_type* in) \
-{ \
- return longname(in, ll_convert_length(in)); \
-} \
-/* if passed string and length, extract its char pointer */ \
-inline auto longname( \
- const INSTR& in, \
- size_t len, \
- unsigned int code_page=ll_wstring_default_code_page()) \
-{ \
- return longname(in.c_str(), len, code_page); \
-} \
-/* if passed only a string object, no scan, pass known length */ \
-inline auto longname(const INSTR& in) \
-{ \
- return longname(in.c_str(), in.length()); \
-} \
-aliasmacro(OUTSTR, INSTR, longname(in)); \
-aliasmacro(OUTSTR, const INSTR::value_type*, longname(in))
-
-ll_convert_cp_forms(ll_convert_wstr_alias, std::string, std::wstring, ll_convert_wide_to_string);
-ll_convert_cp_forms(ll_convert_wstr_alias, std::wstring, std::string, ll_convert_string_to_wide);
- ll_convert_forms(ll_convert_wstr_alias, LLWString, std::wstring, ll_convert_wide_to_wstring);
- ll_convert_forms(ll_convert_wstr_alias, std::wstring, LLWString, ll_convert_wstring_to_wide);
-
-/**
- * Converts incoming string into utf8 string
- *
- */
-LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in);
-
-/// Get Windows message string for passed GetLastError() code
-// VS 2013 doesn't let us forward-declare this template, which is what we
-// started with, so the implementation could reference the specialization we
-// haven't yet declared. Somewhat weirdly, just stating the generic
-// implementation in terms of the specialization works, even in this order...
-
-// the general case is just a conversion from the sole implementation
-// Microsoft says DWORD is a typedef for unsigned long
-// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types
-// so rather than drag windows.h into everybody's include space...
-template<typename STRING>
-STRING windows_message(unsigned long error)
-{
- return ll_convert<STRING>(windows_message<std::wstring>(error));
-}
-
-/// There's only one real implementation
-template<>
-LL_COMMON_API std::wstring windows_message<std::wstring>(unsigned long error);
-
-/// Get Windows message string, implicitly calling GetLastError()
-template<typename STRING>
-STRING windows_message() { return windows_message<STRING>(GetLastError()); }
-
-//@}
-
-LL_COMMON_API std::optional<std::wstring> llstring_getoptenv(const std::string& key);
-
-#else // ! LL_WINDOWS
-
-LL_COMMON_API std::optional<std::string> llstring_getoptenv(const std::string& key);
-
-#endif // ! LL_WINDOWS
-
-/**
- * Many of the 'strip' and 'replace' methods of LLStringUtilBase need
- * specialization to work with the signed char type.
- * Sadly, it is not possible (AFAIK) to specialize a single method of
- * a template class.
- * That stuff should go here.
- */
-namespace LLStringFn
-{
- /**
- * @brief Replace all non-printable characters with replacement in
- * string.
- * NOTE - this will zap non-ascii
- *
- * @param [in,out] string the to modify. out value is the string
- * with zero non-printable characters.
- * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
- */
- LL_COMMON_API void replace_nonprintable_in_ascii(
- std::basic_string<char>& string,
- char replacement);
-
-
- /**
- * @brief Replace all non-printable characters and pipe characters
- * with replacement in a string.
- * NOTE - this will zap non-ascii
- *
- * @param [in,out] the string to modify. out value is the string
- * with zero non-printable characters and zero pipe characters.
- * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
- */
- LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str,
- char replacement);
-
-
- /**
- * @brief Remove all characters that are not allowed in XML 1.0.
- * Returns a copy of the string with those characters removed.
- * Works with US ASCII and UTF-8 encoded strings. JC
- */
- LL_COMMON_API std::string strip_invalid_xml(const std::string& input);
-
-
- /**
- * @brief Replace all control characters (0 <= c < 0x20) with replacement in
- * string. This is safe for utf-8
- *
- * @param [in,out] string the to modify. out value is the string
- * with zero non-printable characters.
- * @param The replacement character. use LL_UNKNOWN_CHAR if unsure.
- */
- LL_COMMON_API void replace_ascii_controlchars(
- std::basic_string<char>& string,
- char replacement);
-}
-
-////////////////////////////////////////////////////////////
-// NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp.
-// There is no LLWStringUtil::format implementation currently.
-// Calling these for anything other than LLStringUtil will produce link errors.
-
-////////////////////////////////////////////////////////////
-
-// static
-template <class T>
-std::vector<typename LLStringUtilBase<T>::string_type>
-LLStringUtilBase<T>::getTokens(const string_type& instr, const string_type& delims)
-{
- std::vector<string_type> tokens;
- getTokens(instr, tokens, delims);
- return tokens;
-}
-
-// static
-template <class T>
-std::vector<typename LLStringUtilBase<T>::string_type>
-LLStringUtilBase<T>::getTokens(const string_type& instr,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes)
-{
- std::vector<string_type> tokens;
- getTokens(instr, tokens, drop_delims, keep_delims, quotes);
- return tokens;
-}
-
-// static
-template <class T>
-std::vector<typename LLStringUtilBase<T>::string_type>
-LLStringUtilBase<T>::getTokens(const string_type& instr,
- const string_type& drop_delims,
- const string_type& keep_delims,
- const string_type& quotes,
- const string_type& escapes)
-{
- std::vector<string_type> tokens;
- getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes);
- return tokens;
-}
-
-namespace LLStringUtilBaseImpl
-{
-
-/**
- * Input string scanner helper for getTokens(), or really any other
- * character-parsing routine that may have to deal with escape characters.
- * This implementation defines the concept (also an interface, should you
- * choose to implement the concept by subclassing) and provides trivial
- * implementations for a string @em without escape processing.
- */
-template <class T>
-struct InString
-{
- typedef std::basic_string<T> string_type;
- typedef typename string_type::const_iterator const_iterator;
-
- InString(const_iterator b, const_iterator e):
- mIter(b),
- mEnd(e)
- {}
- virtual ~InString() {}
-
- bool done() const { return mIter == mEnd; }
- /// Is the current character (*mIter) escaped? This implementation can
- /// answer trivially because it doesn't support escapes.
- virtual bool escaped() const { return false; }
- /// Obtain the current character and advance @c mIter.
- virtual T next() { return *mIter++; }
- /// Does the current character match specified character?
- virtual bool is(T ch) const { return (! done()) && *mIter == ch; }
- /// Is the current character any one of the specified characters?
- virtual bool oneof(const string_type& delims) const
- {
- return (! done()) && LLStringUtilBase<T>::contains(delims, *mIter);
- }
-
- /**
- * Scan forward from @from until either @a delim or end. This is primarily
- * useful for processing quoted substrings.
- *
- * If we do see @a delim, append everything from @from until (excluding)
- * @a delim to @a into, advance @c mIter to skip @a delim, and return @c
- * true.
- *
- * If we do not see @a delim, do not alter @a into or @c mIter and return
- * @c false. Do not pass GO, do not collect $200.
- *
- * @note The @c false case described above implements normal getTokens()
- * treatment of an unmatched open quote: treat the quote character as if
- * escaped, that is, simply collect it as part of the current token. Other
- * plausible behaviors directly affect the way getTokens() deals with an
- * unmatched quote: e.g. throwing an exception to treat it as an error, or
- * assuming a close quote beyond end of string (in which case return @c
- * true).
- */
- virtual bool collect_until(string_type& into, const_iterator from, T delim)
- {
- const_iterator found = std::find(from, mEnd, delim);
- // If we didn't find delim, change nothing, just tell caller.
- if (found == mEnd)
- return false;
- // Found delim! Append everything between from and found.
- into.append(from, found);
- // advance past delim in input
- mIter = found + 1;
- return true;
- }
-
- const_iterator mIter, mEnd;
-};
-
-/// InString subclass that handles escape characters
-template <class T>
-class InEscString: public InString<T>
-{
-public:
- typedef InString<T> super;
- typedef typename super::string_type string_type;
- typedef typename super::const_iterator const_iterator;
- using super::done;
- using super::mIter;
- using super::mEnd;
-
- InEscString(const_iterator b, const_iterator e, const string_type& escapes):
- super(b, e),
- mEscapes(escapes)
- {
- // Even though we've already initialized 'mIter' via our base-class
- // constructor, set it again to check for initial escape char.
- setiter(b);
- }
-
- /// This implementation uses the answer cached by setiter().
- virtual bool escaped() const { return mIsEsc; }
- virtual T next()
- {
- // If we're looking at the escape character of an escape sequence,
- // skip that character. This is the one time we can modify 'mIter'
- // without using setiter: for this one case we DO NOT CARE if the
- // escaped character is itself an escape.
- if (mIsEsc)
- ++mIter;
- // If we were looking at an escape character, this is the escaped
- // character; otherwise it's just the next character.
- T result(*mIter);
- // Advance mIter, checking for escape sequence.
- setiter(mIter + 1);
- return result;
- }
-
- virtual bool is(T ch) const
- {
- // Like base-class is(), except that an escaped character matches
- // nothing.
- return (! done()) && (! mIsEsc) && *mIter == ch;
- }
-
- virtual bool oneof(const string_type& delims) const
- {
- // Like base-class oneof(), except that an escaped character matches
- // nothing.
- return (! done()) && (! mIsEsc) && LLStringUtilBase<T>::contains(delims, *mIter);
- }
-
- virtual bool collect_until(string_type& into, const_iterator from, T delim)
- {
- // Deal with escapes in the characters we collect; that is, an escaped
- // character must become just that character without the preceding
- // escape. Collect characters in a separate string rather than
- // directly appending to 'into' in case we do not find delim, in which
- // case we're supposed to leave 'into' unmodified.
- string_type collected;
- // For scanning purposes, we're going to work directly with 'mIter'.
- // Save its current value in case we fail to see delim.
- const_iterator save_iter(mIter);
- // Okay, set 'mIter', checking for escape.
- setiter(from);
- while (! done())
- {
- // If we see an unescaped delim, stop and report success.
- if ((! mIsEsc) && *mIter == delim)
- {
- // Append collected chars to 'into'.
- into.append(collected);
- // Don't forget to advance 'mIter' past delim.
- setiter(mIter + 1);
- return true;
- }
- // We're not at end, and either we're not looking at delim or it's
- // escaped. Collect this character and keep going.
- collected.push_back(next());
- }
- // Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell
- // caller.
- setiter(save_iter);
- return false;
- }
-
-private:
- void setiter(const_iterator i)
- {
- mIter = i;
-
- // Every time we change 'mIter', set 'mIsEsc' to be able to repetitively
- // answer escaped() without having to rescan 'mEscapes'. mIsEsc caches
- // contains(mEscapes, *mIter).
-
- // We're looking at an escaped char if we're not already at end (that
- // is, *mIter is even meaningful); if *mIter is in fact one of the
- // specified escape characters; and if there's one more character
- // following it. That is, if an escape character is the very last
- // character of the input string, it loses its special meaning.
- mIsEsc = (! done()) &&
- LLStringUtilBase<T>::contains(mEscapes, *mIter) &&
- (mIter+1) != mEnd;
- }
-
- const string_type mEscapes;
- bool mIsEsc;
-};
-
-/// getTokens() implementation based on InString concept
-template <typename INSTRING, typename string_type>
-void getTokens(INSTRING& instr, std::vector<string_type>& tokens,
- const string_type& drop_delims, const string_type& keep_delims,
- const string_type& quotes)
-{
- // There are times when we want to match either drop_delims or
- // keep_delims. Concatenate them up front to speed things up.
- string_type all_delims(drop_delims + keep_delims);
- // no tokens yet
- tokens.clear();
-
- // try for another token
- while (! instr.done())
- {
- // scan past any drop_delims
- while (instr.oneof(drop_delims))
- {
- // skip this drop_delim
- instr.next();
- // but if that was the end of the string, done
- if (instr.done())
- return;
- }
- // found the start of another token: make a slot for it
- tokens.push_back(string_type());
- if (instr.oneof(keep_delims))
- {
- // *iter is a keep_delim, a token of exactly 1 character. Append
- // that character to the new token and proceed.
- tokens.back().push_back(instr.next());
- continue;
- }
- // Here we have a non-delimiter token, which might consist of a mix of
- // quoted and unquoted parts. Use bash rules for quoting: you can
- // embed a quoted substring in the midst of an unquoted token (e.g.
- // ~/"sub dir"/myfile.txt); you can ram two quoted substrings together
- // to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge
- // from bash in that bash considers an unmatched quote an error. Our
- // param signature doesn't allow for errors, so just pretend it's not
- // a quote and embed it.
- // At this level, keep scanning until we hit the next delimiter of
- // either type (drop_delims or keep_delims).
- while (! instr.oneof(all_delims))
- {
- // If we're looking at an open quote, search forward for
- // a close quote, collecting characters along the way.
- if (instr.oneof(quotes) &&
- instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter))
- {
- // collect_until is cleverly designed to do exactly what we
- // need here. No further action needed if it returns true.
- }
- else
- {
- // Either *iter isn't a quote, or there's no matching close
- // quote: in other words, just an ordinary char. Append it to
- // current token.
- tokens.back().push_back(instr.next());
- }
- // having scanned that segment of this token, if we've reached the
- // end of the string, we're done
- if (instr.done())
- return;
- }
- }
-}
-
-} // namespace LLStringUtilBaseImpl
-
-// static
-template <class T>
-void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
- const string_type& drop_delims, const string_type& keep_delims,
- const string_type& quotes)
-{
- // Because this overload doesn't support escapes, use simple InString to
- // manage input range.
- LLStringUtilBaseImpl::InString<T> instring(string.begin(), string.end());
- LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes);
-}
-
-// static
-template <class T>
-void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens,
- const string_type& drop_delims, const string_type& keep_delims,
- const string_type& quotes, const string_type& escapes)
-{
- // This overload must deal with escapes. Delegate that to InEscString
- // (unless there ARE no escapes).
- std::unique_ptr< LLStringUtilBaseImpl::InString<T> > instrp;
- if (escapes.empty())
- instrp.reset(new LLStringUtilBaseImpl::InString<T>(string.begin(), string.end()));
- else
- instrp.reset(new LLStringUtilBaseImpl::InEscString<T>(string.begin(), string.end(), escapes));
- LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes);
-}
-
-// static
-template<class T>
-S32 LLStringUtilBase<T>::compareStrings(const T* lhs, const T* rhs)
-{
- S32 result;
- if( lhs == rhs )
- {
- result = 0;
- }
- else
- if ( !lhs || !lhs[0] )
- {
- result = ((!rhs || !rhs[0]) ? 0 : 1);
- }
- else
- if ( !rhs || !rhs[0])
- {
- result = -1;
- }
- else
- {
- result = LLStringOps::collate(lhs, rhs);
- }
- return result;
-}
-
-//static
-template<class T>
-S32 LLStringUtilBase<T>::compareStrings(const string_type& lhs, const string_type& rhs)
-{
- return LLStringOps::collate(lhs.c_str(), rhs.c_str());
-}
-
-// static
-template<class T>
-S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs )
-{
- S32 result;
- if( lhs == rhs )
- {
- result = 0;
- }
- else
- if ( !lhs || !lhs[0] )
- {
- result = ((!rhs || !rhs[0]) ? 0 : 1);
- }
- else
- if ( !rhs || !rhs[0] )
- {
- result = -1;
- }
- else
- {
- string_type lhs_string(lhs);
- string_type rhs_string(rhs);
- LLStringUtilBase<T>::toUpper(lhs_string);
- LLStringUtilBase<T>::toUpper(rhs_string);
- result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
- }
- return result;
-}
-
-//static
-template<class T>
-S32 LLStringUtilBase<T>::compareInsensitive(const string_type& lhs, const string_type& rhs)
-{
- string_type lhs_string(lhs);
- string_type rhs_string(rhs);
- LLStringUtilBase<T>::toUpper(lhs_string);
- LLStringUtilBase<T>::toUpper(rhs_string);
- return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str());
-}
-
-// Case sensitive comparison with good handling of numbers. Does not use current locale.
-// a.k.a. strdictcmp()
-
-//static
-template<class T>
-S32 LLStringUtilBase<T>::compareDict(const string_type& astr, const string_type& bstr)
-{
- const T* a = astr.c_str();
- const T* b = bstr.c_str();
- T ca, cb;
- S32 ai, bi, cnt = 0;
- S32 bias = 0;
-
- ca = *(a++);
- cb = *(b++);
- while( ca && cb ){
- if( bias==0 ){
- if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); bias--; }
- if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); bias++; }
- }else{
- if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); }
- if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); }
- }
- if( LLStringOps::isDigit(ca) ){
- if( cnt-->0 ){
- if( cb!=ca ) break;
- }else{
- if( !LLStringOps::isDigit(cb) ) break;
- for(ai=0; LLStringOps::isDigit(a[ai]); ai++);
- for(bi=0; LLStringOps::isDigit(b[bi]); bi++);
- if( ai<bi ){ ca=0; break; }
- if( bi<ai ){ cb=0; break; }
- if( ca!=cb ) break;
- cnt = ai;
- }
- }else if( ca!=cb ){ break;
- }
- ca = *(a++);
- cb = *(b++);
- }
- if( ca==cb ) ca += bias;
- return ca-cb;
-}
-
-// static
-template<class T>
-S32 LLStringUtilBase<T>::compareDictInsensitive(const string_type& astr, const string_type& bstr)
-{
- const T* a = astr.c_str();
- const T* b = bstr.c_str();
- T ca, cb;
- S32 ai, bi, cnt = 0;
-
- ca = *(a++);
- cb = *(b++);
- while( ca && cb ){
- if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); }
- if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); }
- if( LLStringOps::isDigit(ca) ){
- if( cnt-->0 ){
- if( cb!=ca ) break;
- }else{
- if( !LLStringOps::isDigit(cb) ) break;
- for(ai=0; LLStringOps::isDigit(a[ai]); ai++);
- for(bi=0; LLStringOps::isDigit(b[bi]); bi++);
- if( ai<bi ){ ca=0; break; }
- if( bi<ai ){ cb=0; break; }
- if( ca!=cb ) break;
- cnt = ai;
- }
- }else if( ca!=cb ){ break;
- }
- ca = *(a++);
- cb = *(b++);
- }
- return ca-cb;
-}
-
-// Puts compareDict() in a form appropriate for LL container classes to use for sorting.
-// static
-template<class T>
-bool LLStringUtilBase<T>::precedesDict( const string_type& a, const string_type& b )
-{
- if( a.size() && b.size() )
- {
- return (LLStringUtilBase<T>::compareDict(a.c_str(), b.c_str()) < 0);
- }
- else
- {
- return (!b.empty());
- }
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::toUpper(string_type& string)
-{
- if( !string.empty() )
- {
- std::transform(
- string.begin(),
- string.end(),
- string.begin(),
- (T(*)(T)) &LLStringOps::toUpper);
- }
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::toLower(string_type& string)
-{
- if( !string.empty() )
- {
- std::transform(
- string.begin(),
- string.end(),
- string.begin(),
- (T(*)(T)) &LLStringOps::toLower);
- }
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::trimHead(string_type& string)
-{
- if( !string.empty() )
- {
- size_type i = 0;
- while( i < string.length() && LLStringOps::isSpace( string[i] ) )
- {
- i++;
- }
- string.erase(0, i);
- }
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::trimTail(string_type& string)
-{
- if( string.size() )
- {
- size_type len = string.length();
- size_type i = len;
- while( i > 0 && LLStringOps::isSpace( string[i-1] ) )
- {
- i--;
- }
-
- string.erase( i, len - i );
- }
-}
-
-
-// Replace line feeds with carriage return-line feed pairs.
-//static
-template<class T>
-void LLStringUtilBase<T>::addCRLF(string_type& string)
-{
- const T LF = 10;
- const T CR = 13;
-
- // Count the number of line feeds
- size_type count = 0;
- size_type len = string.size();
- size_type i;
- for( i = 0; i < len; i++ )
- {
- if( string[i] == LF )
- {
- count++;
- }
- }
-
- // Insert a carriage return before each line feed
- if( count )
- {
- size_type size = len + count;
- T *t = new T[size];
- size_type j = 0;
- for( i = 0; i < len; ++i )
- {
- if( string[i] == LF )
- {
- t[j] = CR;
- ++j;
- }
- t[j] = string[i];
- ++j;
- }
-
- string.assign(t, size);
- delete[] t;
- }
-}
-
-// Remove all carriage returns
-//static
-template<class T>
-void LLStringUtilBase<T>::removeCRLF(string_type& string)
-{
- const T CR = 13;
-
- size_type cr_count = 0;
- size_type len = string.size();
- size_type i;
- for( i = 0; i < len - cr_count; i++ )
- {
- if( string[i+cr_count] == CR )
- {
- cr_count++;
- }
-
- string[i] = string[i+cr_count];
- }
- string.erase(i, cr_count);
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::removeWindowsCR(string_type& string)
-{
- if (string.empty())
- {
- return;
- }
- const T LF = 10;
- const T CR = 13;
-
- size_type cr_count = 0;
- size_type len = string.size();
- size_type i;
- for( i = 0; i < len - cr_count - 1; i++ )
- {
- if( string[i+cr_count] == CR && string[i+cr_count+1] == LF)
- {
- cr_count++;
- }
-
- string[i] = string[i+cr_count];
- }
- string.erase(i, cr_count);
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement )
-{
- size_type found_pos = 0;
- while( (found_pos = string.find(target, found_pos)) != string_type::npos )
- {
- string[found_pos] = replacement;
- found_pos++; // avoid infinite defeat if target == replacement
- }
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::replaceString( string_type& string, string_type target, string_type replacement )
-{
- size_type found_pos = 0;
- while( (found_pos = string.find(target, found_pos)) != string_type::npos )
- {
- string.replace( found_pos, target.length(), replacement );
- found_pos += replacement.length(); // avoid infinite defeat if replacement contains target
- }
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::replaceNonstandardASCII( string_type& string, T replacement )
-{
- const char LF = 10;
- const S8 MIN = 32;
-// const S8 MAX = 127;
-
- size_type len = string.size();
- for( size_type i = 0; i < len; i++ )
- {
- // No need to test MAX < mText[i] because we treat mText[i] as a signed char,
- // which has a max value of 127.
- if( ( S8(string[i]) < MIN ) && (string[i] != LF) )
- {
- string[i] = replacement;
- }
- }
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab )
-{
- const T TAB = '\t';
- const T SPACE = ' ';
-
- string_type out_str;
- // Replace tabs with spaces
- for (size_type i = 0; i < str.length(); i++)
- {
- if (str[i] == TAB)
- {
- for (size_type j = 0; j < spaces_per_tab; j++)
- out_str += SPACE;
- }
- else
- {
- out_str += str[i];
- }
- }
- str = out_str;
-}
-
-//static
-template<class T>
-std::basic_string<T> LLStringUtilBase<T>::capitalize(const string_type& str)
-{
- string_type result(str);
- capitalize(result);
- return result;
-}
-
-//static
-template<class T>
-void LLStringUtilBase<T>::capitalize(string_type& str)
-{
- if (str.size())
- {
- auto last = str[0] = toupper(str[0]);
- for (U32 i = 1; i < str.size(); ++i)
- {
- last = (last == ' ' || last == '-' || last == '_') ? str[i] = toupper(str[i]) : str[i];
- }
- }
-}
-
-//static
-template<class T>
-bool LLStringUtilBase<T>::containsNonprintable(const string_type& string)
-{
- const char MIN = 32;
- bool rv = false;
- for (size_type i = 0; i < string.size(); i++)
- {
- if(string[i] < MIN)
- {
- rv = true;
- break;
- }
- }
- return rv;
-}
-
-// *TODO: reimplement in terms of algorithm
-//static
-template<class T>
-void LLStringUtilBase<T>::stripNonprintable(string_type& string)
-{
- const char MIN = 32;
- size_type j = 0;
- if (string.empty())
- {
- return;
- }
- size_t src_size = string.size();
- char* c_string = new char[src_size + 1];
- if(c_string == NULL)
- {
- return;
- }
- copy(c_string, string.c_str(), src_size+1);
- char* write_head = &c_string[0];
- for (size_type i = 0; i < src_size; i++)
- {
- char* read_head = &string[i];
- write_head = &c_string[j];
- if(!(*read_head < MIN))
- {
- *write_head = *read_head;
- ++j;
- }
- }
- c_string[j]= '\0';
- string = c_string;
- delete []c_string;
-}
-
-// *TODO: reimplement in terms of algorithm
-template<class T>
-std::basic_string<T> LLStringUtilBase<T>::quote(const string_type& str,
- const string_type& triggers,
- const string_type& escape)
-{
- size_type len(str.length());
- // If the string is already quoted, assume user knows what s/he's doing.
- if (len >= 2 && str[0] == '"' && str[len-1] == '"')
- {
- return str;
- }
-
- // Not already quoted: do we need to? triggers.empty() is a special case
- // meaning "always quote."
- if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos)
- {
- // no trigger characters, don't bother quoting
- return str;
- }
-
- // For whatever reason, we must quote this string.
- string_type result;
- result.push_back('"');
- for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
- {
- if (*ci == '"')
- {
- result.append(escape);
- }
- result.push_back(*ci);
- }
- result.push_back('"');
- return result;
-}
-
-template<class T>
-void LLStringUtilBase<T>::_makeASCII(string_type& string)
-{
- // Replace non-ASCII chars with LL_UNKNOWN_CHAR
- for (size_type i = 0; i < string.length(); i++)
- {
- if (string[i] > 0x7f)
- {
- string[i] = LL_UNKNOWN_CHAR;
- }
- }
-}
-
-// static
-template<class T>
-void LLStringUtilBase<T>::copy( T* dst, const T* src, size_type dst_size )
-{
- if( dst_size > 0 )
- {
- size_type min_len = 0;
- if( src )
- {
- min_len = llmin( dst_size - 1, strlen( src ) ); /* Flawfinder: ignore */
- memcpy(dst, src, min_len * sizeof(T)); /* Flawfinder: ignore */
- }
- dst[min_len] = '\0';
- }
-}
-
-// static
-template<class T>
-void LLStringUtilBase<T>::copyInto(string_type& dst, const string_type& src, size_type offset)
-{
- if ( offset == dst.length() )
- {
- // special case - append to end of string and avoid expensive
- // (when strings are large) string manipulations
- dst += src;
- }
- else
- {
- string_type tail = dst.substr(offset);
-
- dst = dst.substr(0, offset);
- dst += src;
- dst += tail;
- };
-}
-
-// True if this is the head of s.
-//static
-template<class T>
-bool LLStringUtilBase<T>::isHead( const string_type& string, const T* s )
-{
- if( string.empty() )
- {
- // Early exit
- return false;
- }
- else
- {
- return (strncmp( s, string.c_str(), string.size() ) == 0);
- }
-}
-
-// static
-template<class T>
-bool LLStringUtilBase<T>::startsWith(
- const string_type& string,
- const string_type& substr)
-{
- if(string.empty() || (substr.empty())) return false;
- if (substr.length() > string.length()) return false;
- if (0 == string.compare(0, substr.length(), substr)) return true;
- return false;
-}
-
-// static
-template<class T>
-bool LLStringUtilBase<T>::endsWith(
- const string_type& string,
- const string_type& substr)
-{
- if(string.empty() || (substr.empty())) return false;
- size_t sub_len = substr.length();
- size_t str_len = string.length();
- if (sub_len > str_len) return false;
- if (0 == string.compare(str_len - sub_len, sub_len, substr)) return true;
- return false;
-}
-
-// static
-template<class T>
-auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> std::optional<string_type>
-{
- auto found(llstring_getoptenv(key));
- if (found)
- {
- // return populated std::optional
- return { ll_convert<string_type>(*found) };
- }
- else
- {
- // empty std::optional
- return {};
- }
-}
-
-// static
-template<class T>
-auto LLStringUtilBase<T>::getenv(const std::string& key, const string_type& dflt) -> string_type
-{
- auto found(getoptenv(key));
- if (found)
- {
- return *found;
- }
- else
- {
- return dflt;
- }
-}
-
-template<class T>
-bool LLStringUtilBase<T>::convertToBOOL(const string_type& string, bool& value)
-{
- if( string.empty() )
- {
- return false;
- }
-
- string_type temp( string );
- trim(temp);
- if(
- (temp == "1") ||
- (temp == "T") ||
- (temp == "t") ||
- (temp == "TRUE") ||
- (temp == "true") ||
- (temp == "True") )
- {
- value = true;
- return true;
- }
- else
- if(
- (temp == "0") ||
- (temp == "F") ||
- (temp == "f") ||
- (temp == "FALSE") ||
- (temp == "false") ||
- (temp == "False") )
- {
- value = false;
- return true;
- }
-
- return false;
-}
-
-template<class T>
-bool LLStringUtilBase<T>::convertToU8(const string_type& string, U8& value)
-{
- S32 value32 = 0;
- bool success = convertToS32(string, value32);
- if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) )
- {
- value = (U8) value32;
- return true;
- }
- return false;
-}
-
-template<class T>
-bool LLStringUtilBase<T>::convertToS8(const string_type& string, S8& value)
-{
- S32 value32 = 0;
- bool success = convertToS32(string, value32);
- if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) )
- {
- value = (S8) value32;
- return true;
- }
- return false;
-}
-
-template<class T>
-bool LLStringUtilBase<T>::convertToS16(const string_type& string, S16& value)
-{
- S32 value32 = 0;
- bool success = convertToS32(string, value32);
- if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) )
- {
- value = (S16) value32;
- return true;
- }
- return false;
-}
-
-template<class T>
-bool LLStringUtilBase<T>::convertToU16(const string_type& string, U16& value)
-{
- S32 value32 = 0;
- bool success = convertToS32(string, value32);
- if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) )
- {
- value = (U16) value32;
- return true;
- }
- return false;
-}
-
-template<class T>
-bool LLStringUtilBase<T>::convertToU32(const string_type& string, U32& value)
-{
- if( string.empty() )
- {
- return false;
- }
-
- string_type temp( string );
- trim(temp);
- U32 v;
- std::basic_istringstream<T> i_stream((string_type)temp);
- if(i_stream >> v)
- {
- value = v;
- return true;
- }
- return false;
-}
-
-template<class T>
-bool LLStringUtilBase<T>::convertToS32(const string_type& string, S32& value)
-{
- if( string.empty() )
- {
- return false;
- }
-
- string_type temp( string );
- trim(temp);
- S32 v;
- std::basic_istringstream<T> i_stream((string_type)temp);
- if(i_stream >> v)
- {
- //TODO: figure out overflow and underflow reporting here
- //if((LONG_MAX == v) || (LONG_MIN == v))
- //{
- // // Underflow or overflow
- // return false;
- //}
-
- value = v;
- return true;
- }
- return false;
-}
-
-template<class T>
-bool LLStringUtilBase<T>::convertToF32(const string_type& string, F32& value)
-{
- F64 value64 = 0.0;
- bool success = convertToF64(string, value64);
- if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) )
- {
- value = (F32) value64;
- return true;
- }
- return false;
-}
-
-template<class T>
-bool LLStringUtilBase<T>::convertToF64(const string_type& string, F64& value)
-{
- if( string.empty() )
- {
- return false;
- }
-
- string_type temp( string );
- trim(temp);
- F64 v;
- std::basic_istringstream<T> i_stream((string_type)temp);
- if(i_stream >> v)
- {
- //TODO: figure out overflow and underflow reporting here
- //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) )
- //{
- // // Underflow or overflow
- // return false;
- //}
-
- value = v;
- return true;
- }
- return false;
-}
-
-template<class T>
-void LLStringUtilBase<T>::truncate(string_type& string, size_type count)
-{
- size_type cur_size = string.size();
- string.resize(count < cur_size ? count : cur_size);
-}
-
-// The good thing about *declaration* macros, vs. usage macros, is that now
-// we're done with them: we don't need them to bleed into the consuming source
-// file.
-#undef ll_convert_alias
-#undef ll_convert_u16_alias
-#undef ll_convert_wstr_alias
-#undef LL_CONVERT_COPY_CHARS
-#undef ll_convert_forms
-#undef ll_convert_cp_forms
-
-#endif // LL_STRING_H
+/** + * @file llstring.h + * @brief String utility functions and std::string class. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSTRING_H +#define LL_LLSTRING_H + +#include <boost/call_traits.hpp> +#include <optional> +#include <string> +#include <string_view> +#include <cstdio> +#include <cwchar> // std::wcslen() +//#include <locale> +#include <iomanip> +#include <algorithm> +#include <vector> +#include <map> +#include "llformat.h" + +#if LL_LINUX +#include <wctype.h> +#include <wchar.h> +#endif + +#include <string.h> +#include <boost/scoped_ptr.hpp> + +const char LL_UNKNOWN_CHAR = '?'; +class LLSD; + +#if LL_DARWIN || LL_LINUX +// Template specialization of char_traits for U16s. Only necessary on Mac and Linux (exists on Windows already) +#include <cstring> + +namespace std +{ +template<> +struct char_traits<U16> +{ + typedef U16 char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + typedef mbstate_t state_type; + + static void + assign(char_type& __c1, const char_type& __c2) + { __c1 = __c2; } + + static bool + eq(const char_type& __c1, const char_type& __c2) + { return __c1 == __c2; } + + static bool + lt(const char_type& __c1, const char_type& __c2) + { return __c1 < __c2; } + + static int + compare(const char_type* __s1, const char_type* __s2, size_t __n) + { return memcmp(__s1, __s2, __n * sizeof(char_type)); } + + static size_t + length(const char_type* __s) + { + const char_type *cur_char = __s; + while (*cur_char != 0) + { + ++cur_char; + } + return cur_char - __s; + } + + static const char_type* + find(const char_type* __s, size_t __n, const char_type& __a) + { return static_cast<const char_type*>(memchr(__s, __a, __n * sizeof(char_type))); } + + static char_type* + move(char_type* __s1, const char_type* __s2, size_t __n) + { return static_cast<char_type*>(memmove(__s1, __s2, __n * sizeof(char_type))); } + + static char_type* + copy(char_type* __s1, const char_type* __s2, size_t __n) + { return static_cast<char_type*>(memcpy(__s1, __s2, __n * sizeof(char_type))); } /* Flawfinder: ignore */ + + static char_type* + assign(char_type* __s, size_t __n, char_type __a) + { + // This isn't right. + //return static_cast<char_type*>(memset(__s, __a, __n * sizeof(char_type))); + + // I don't think there's a standard 'memset' for 16-bit values. + // Do this the old-fashioned way. + + size_t __i; + for(__i = 0; __i < __n; __i++) + { + __s[__i] = __a; + } + return __s; + } + + static char_type + to_char_type(const int_type& __c) + { return static_cast<char_type>(__c); } + + static int_type + to_int_type(const char_type& __c) + { return static_cast<int_type>(__c); } + + static bool + eq_int_type(const int_type& __c1, const int_type& __c2) + { return __c1 == __c2; } + + static int_type + eof() { return static_cast<int_type>(EOF); } + + static int_type + not_eof(const int_type& __c) + { return (__c == eof()) ? 0 : __c; } + }; +}; +#endif + +class LL_COMMON_API LLStringOps +{ +private: + static long sPacificTimeOffset; + static long sLocalTimeOffset; + static bool sPacificDaylightTime; + + static std::map<std::string, std::string> datetimeToCodes; + +public: + static std::vector<std::string> sWeekDayList; + static std::vector<std::string> sWeekDayShortList; + static std::vector<std::string> sMonthList; + static std::vector<std::string> sMonthShortList; + static std::string sDayFormat; + + static std::string sAM; + static std::string sPM; + + static char toUpper(char elem) { return toupper((unsigned char)elem); } + static llwchar toUpper(llwchar elem) { return towupper(elem); } + + static char toLower(char elem) { return tolower((unsigned char)elem); } + static llwchar toLower(llwchar elem) { return towlower(elem); } + + static bool isSpace(char elem) { return isspace((unsigned char)elem) != 0; } + static bool isSpace(llwchar elem) { return iswspace(elem) != 0; } + + static bool isUpper(char elem) { return isupper((unsigned char)elem) != 0; } + static bool isUpper(llwchar elem) { return iswupper(elem) != 0; } + + static bool isLower(char elem) { return islower((unsigned char)elem) != 0; } + static bool isLower(llwchar elem) { return iswlower(elem) != 0; } + + static bool isDigit(char a) { return isdigit((unsigned char)a) != 0; } + static bool isDigit(llwchar a) { return iswdigit(a) != 0; } + + static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; } + static bool isPunct(llwchar a) { return iswpunct(a) != 0; } + + static bool isAlpha(char a) { return isalpha((unsigned char)a) != 0; } + static bool isAlpha(llwchar a) { return iswalpha(a) != 0; } + + static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; } + static bool isAlnum(llwchar a) { return iswalnum(a) != 0; } + + // Returns true when 'a' corresponds to a "genuine" emoji. HB + static bool isEmoji(llwchar a); + + static S32 collate(const char* a, const char* b) { return strcoll(a, b); } + static S32 collate(const llwchar* a, const llwchar* b); + + static void setupDatetimeInfo(bool pacific_daylight_time); + + static void setupWeekDaysNames(const std::string& data); + static void setupWeekDaysShortNames(const std::string& data); + static void setupMonthNames(const std::string& data); + static void setupMonthShortNames(const std::string& data); + static void setupDayFormat(const std::string& data); + + + static long getPacificTimeOffset(void) { return sPacificTimeOffset;} + static long getLocalTimeOffset(void) { return sLocalTimeOffset;} + // Is the Pacific time zone (aka server time zone) + // currently in daylight savings time? + static bool getPacificDaylightTime(void) { return sPacificDaylightTime;} + + static std::string getDatetimeCode (std::string key); + + // Express a value like 1234567 as "1.23M" + static std::string getReadableNumber(F64 num); +}; + +/** + * @brief Return a string constructed from in without crashing if the + * pointer is NULL. + */ +LL_COMMON_API std::string ll_safe_string(const char* in); +LL_COMMON_API std::string ll_safe_string(const char* in, S32 maxlen); + + +// Allowing assignments from non-strings into format_map_t is apparently +// *really* error-prone, so subclass std::string with just basic c'tors. +class LLFormatMapString +{ +public: + LLFormatMapString() {}; + LLFormatMapString(const char* s) : mString(ll_safe_string(s)) {}; + LLFormatMapString(const std::string& s) : mString(s) {}; + operator std::string() const { return mString; } + bool operator<(const LLFormatMapString& rhs) const { return mString < rhs.mString; } + std::size_t length() const { return mString.length(); } + +private: + std::string mString; +}; + +template <class T> +class LLStringUtilBase +{ +private: + static std::string sLocale; + +public: + typedef std::basic_string<T> string_type; + typedef typename string_type::size_type size_type; + +public: + ///////////////////////////////////////////////////////////////////////////////////////// + // Static Utility functions that operate on std::strings + + static const string_type null; + + typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t; + /// considers any sequence of delims as a single field separator + LL_COMMON_API static void getTokens(const string_type& instr, + std::vector<string_type >& tokens, + const string_type& delims); + /// like simple scan overload, but returns scanned vector + static std::vector<string_type> getTokens(const string_type& instr, + const string_type& delims); + /// add support for keep_delims and quotes (either could be empty string) + static void getTokens(const string_type& instr, + std::vector<string_type>& tokens, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes=string_type()); + /// like keep_delims-and-quotes overload, but returns scanned vector + static std::vector<string_type> getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes=string_type()); + /// add support for escapes (could be empty string) + static void getTokens(const string_type& instr, + std::vector<string_type>& tokens, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes); + /// like escapes overload, but returns scanned vector + static std::vector<string_type> getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes); + + LL_COMMON_API static void formatNumber(string_type& numStr, string_type decimals); + LL_COMMON_API static bool formatDatetime(string_type& replacement, string_type token, string_type param, S32 secFromEpoch); + LL_COMMON_API static S32 format(string_type& s, const format_map_t& substitutions); + LL_COMMON_API static S32 format(string_type& s, const LLSD& substitutions); + LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const format_map_t& substitutions); + LL_COMMON_API static bool simpleReplacement(string_type& replacement, string_type token, const LLSD& substitutions); + LL_COMMON_API static void setLocale (std::string inLocale); + LL_COMMON_API static std::string getLocale (void); + + static bool isValidIndex(const string_type& string, size_type i) + { + return !string.empty() && (0 <= i) && (i <= string.size()); + } + + static bool contains(const string_type& string, T c, size_type i=0) + { + return string.find(c, i) != string_type::npos; + } + + static void trimHead(string_type& string); + static void trimTail(string_type& string); + static void trim(string_type& string) { trimHead(string); trimTail(string); } + static void truncate(string_type& string, size_type count); + + static void toUpper(string_type& string); + static void toLower(string_type& string); + + // True if this is the head of s. + static bool isHead( const string_type& string, const T* s ); + + /** + * @brief Returns true if string starts with substr + * + * If etither string or substr are empty, this method returns false. + */ + static bool startsWith( + const string_type& string, + const string_type& substr); + + /** + * @brief Returns true if string ends in substr + * + * If etither string or substr are empty, this method returns false. + */ + static bool endsWith( + const string_type& string, + const string_type& substr); + + /** + * get environment string value with proper Unicode handling + * (key is always UTF-8) + * detect absence by return value == dflt + */ + static string_type getenv(const std::string& key, const string_type& dflt=""); + /** + * get optional environment string value with proper Unicode handling + * (key is always UTF-8) + * detect absence by (! return value) + */ + static std::optional<string_type> getoptenv(const std::string& key); + + static void addCRLF(string_type& string); + static void removeCRLF(string_type& string); + static void removeWindowsCR(string_type& string); + + static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab ); + static void replaceNonstandardASCII( string_type& string, T replacement ); + static void replaceChar( string_type& string, T target, T replacement ); + static void replaceString( string_type& string, string_type target, string_type replacement ); + static string_type capitalize(const string_type& str); + static void capitalize(string_type& str); + + static bool containsNonprintable(const string_type& string); + static void stripNonprintable(string_type& string); + + /** + * Double-quote an argument string if needed, unless it's already + * double-quoted. Decide whether it's needed based on the presence of any + * character in @a triggers (default space or double-quote). If we quote + * it, escape any embedded double-quote with the @a escape string (default + * backslash). + * + * Passing triggers="" means always quote, unless it's already double-quoted. + */ + static string_type quote(const string_type& str, + const string_type& triggers=" \"", + const string_type& escape="\\"); + + /** + * @brief Unsafe way to make ascii characters. You should probably + * only call this when interacting with the host operating system. + * The 1 byte std::string does not work correctly. + * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII + * should work. + */ + static void _makeASCII(string_type& string); + + // Conversion to other data types + static bool convertToBOOL(const string_type& string, bool& value); + static bool convertToU8(const string_type& string, U8& value); + static bool convertToS8(const string_type& string, S8& value); + static bool convertToS16(const string_type& string, S16& value); + static bool convertToU16(const string_type& string, U16& value); + static bool convertToU32(const string_type& string, U32& value); + static bool convertToS32(const string_type& string, S32& value); + static bool convertToF32(const string_type& string, F32& value); + static bool convertToF64(const string_type& string, F64& value); + + ///////////////////////////////////////////////////////////////////////////////////////// + // Utility functions for working with char*'s and strings + + // Like strcmp but also handles empty strings. Uses + // current locale. + static S32 compareStrings(const T* lhs, const T* rhs); + static S32 compareStrings(const string_type& lhs, const string_type& rhs); + + // case insensitive version of above. Uses current locale on + // Win32, and falls back to a non-locale aware comparison on + // Linux. + static S32 compareInsensitive(const T* lhs, const T* rhs); + static S32 compareInsensitive(const string_type& lhs, const string_type& rhs); + + // Case sensitive comparison with good handling of numbers. Does not use current locale. + // a.k.a. strdictcmp() + static S32 compareDict(const string_type& a, const string_type& b); + + // Case *in*sensitive comparison with good handling of numbers. Does not use current locale. + // a.k.a. strdictcmp() + static S32 compareDictInsensitive(const string_type& a, const string_type& b); + + // Puts compareDict() in a form appropriate for LL container classes to use for sorting. + static bool precedesDict( const string_type& a, const string_type& b ); + + // A replacement for strncpy. + // If the dst buffer is dst_size bytes long or more, ensures that dst is null terminated and holds + // up to dst_size-1 characters of src. + static void copy(T* dst, const T* src, size_type dst_size); + + // Copies src into dst at a given offset. + static void copyInto(string_type& dst, const string_type& src, size_type offset); + + static bool isPartOfWord(T c) { return (c == (T)'_') || LLStringOps::isAlnum(c); } + + +#ifdef _DEBUG + LL_COMMON_API static void testHarness(); +#endif + +private: + LL_COMMON_API static size_type getSubstitution(const string_type& instr, size_type& start, std::vector<string_type >& tokens); +}; + +template<class T> const std::basic_string<T> LLStringUtilBase<T>::null; +template<class T> std::string LLStringUtilBase<T>::sLocale; + +typedef LLStringUtilBase<char> LLStringUtil; +typedef LLStringUtilBase<llwchar> LLWStringUtil; +typedef std::basic_string<llwchar> LLWString; + +//@ Use this where we want to disallow input in the form of "foo" +// This is used to catch places where english text is embedded in the code +// instead of in a translatable XUI file. +class LLStringExplicit : public std::string +{ +public: + explicit LLStringExplicit(const char* s) : std::string(s) {} + LLStringExplicit(const std::string& s) : std::string(s) {} + LLStringExplicit(const std::string& s, size_type pos, size_type n = std::string::npos) : std::string(s, pos, n) {} +}; + +struct LLDictionaryLess +{ +public: + bool operator()(const std::string& a, const std::string& b) const + { + return (LLStringUtil::precedesDict(a, b)); + } +}; + + +/** + * Simple support functions + */ + +/** + * @brief chop off the trailing characters in a string. + * + * This function works on bytes rather than glyphs, so this will + * incorrectly truncate non-single byte strings. + * Use utf8str_truncate() for utf8 strings + * @return a copy of in string minus the trailing count bytes. + */ +inline std::string chop_tail_copy( + const std::string& in, + std::string::size_type count) +{ + return std::string(in, 0, in.length() - count); +} + +/** + * @brief This translates a nybble stored as a hex value from 0-f back + * to a nybble in the low order bits of the return byte. + */ +LL_COMMON_API bool is_char_hex(char hex); +LL_COMMON_API U8 hex_as_nybble(char hex); + +/** + * @brief read the contents of a file into a string. + * + * Since this function has no concept of character encoding, most + * anything you do with this method ill-advised. Please avoid. + * @param str [out] The string which will have. + * @param filename The full name of the file to read. + * @return Returns true on success. If false, str is unmodified. + */ +LL_COMMON_API bool _read_file_into_string(std::string& str, const std::string& filename); +LL_COMMON_API bool iswindividual(llwchar elem); + +/** + * Unicode support + */ + +/// generic conversion aliases +template<typename TO, typename FROM, typename Enable=void> +struct ll_convert_impl +{ + // Don't even provide a generic implementation. We specialize for every + // combination we do support. + TO operator()(const FROM& in) const; +}; + +// Use a function template to get the nice ll_convert<TO>(from_value) API. +template<typename TO, typename FROM> +TO ll_convert(const FROM& in) +{ + return ll_convert_impl<TO, FROM>()(in); +} + +// degenerate case +template<typename T> +struct ll_convert_impl<T, T> +{ + T operator()(const T& in) const { return in; } +}; + +// simple construction from char* +template<typename T> +struct ll_convert_impl<T, const typename T::value_type*> +{ + T operator()(const typename T::value_type* in) const { return { in }; } +}; + +// specialize ll_convert_impl<TO, FROM> to return EXPR +#define ll_convert_alias(TO, FROM, EXPR) \ +template<> \ +struct ll_convert_impl<TO, FROM> \ +{ \ + /* param_type optimally passes both char* and string */ \ + TO operator()(typename boost::call_traits<FROM>::param_type in) const { return EXPR; } \ +} + +// If all we're doing is copying characters, pass this to ll_convert_alias as +// EXPR. Since it expands into the 'return EXPR' slot in the ll_convert_impl +// specialization above, it implies TO{ in.begin(), in.end() }. +#define LL_CONVERT_COPY_CHARS { in.begin(), in.end() } + +// Generic name for strlen() / wcslen() - the default implementation should +// (!) work with U16 and llwchar, but we don't intend to engage it. +template <typename CHARTYPE> +size_t ll_convert_length(const CHARTYPE* zstr) +{ + const CHARTYPE* zp; + // classic C string scan + for (zp = zstr; *zp; ++zp) + ; + return (zp - zstr); +} + +// specialize where we have a library function; may use intrinsic operations +template <> +inline size_t ll_convert_length<wchar_t>(const wchar_t* zstr) { return std::wcslen(zstr); } +template <> +inline size_t ll_convert_length<char> (const char* zstr) { return std::strlen(zstr); } + +// ll_convert_forms() is short for a bunch of boilerplate. It defines +// longname(const char*, len), longname(const char*), longname(const string&) +// and longname(const string&, len) so calls written pre-ll_convert() will +// work. Most of these overloads will be unified once we turn on C++17 and can +// use std::string_view. +// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*) +// and ll_convert<OUTSTR>(const string&) will work. +#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \ +LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \ +inline auto longname(const INSTR& in, size_t len) \ +{ \ + return longname(in.c_str(), len); \ +} \ +inline auto longname(const INSTR::value_type* in) \ +{ \ + return longname(in, ll_convert_length(in)); \ +} \ +inline auto longname(const INSTR& in) \ +{ \ + return longname(in.c_str(), in.length()); \ +} \ +/* string param */ \ +aliasmacro(OUTSTR, INSTR, longname(in)); \ +/* char* param */ \ +aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) + +// Make the incoming string a utf8 string. Replaces any unknown glyph +// with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest +// of the data may not be recovered. +LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw); + +// +// We should never use UTF16 except when communicating with Win32! +// https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t +// nat 2018-12-14: I consider the whole llutf16string thing a mistake, because +// the Windows APIs we want to call are all defined in terms of wchar_t* +// (or worse, LPCTSTR). +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types + +// While there is no point coding for an ASCII-only world (! defined(UNICODE)), +// use of U16 and llutf16string for Windows APIs locks in /Zc:wchar_t-. Going +// forward, we should code in terms of wchar_t and std::wstring so as to +// support either setting of /Zc:wchar_t. + +// The first link above states that char can be used to hold ASCII or any +// multi-byte character set, and distinguishes wchar_t (UTF-16LE), char16_t +// (UTF-16) and char32_t (UTF-32). Nonetheless, within this code base: +// * char and std::string always hold UTF-8 (of which ASCII is a subset). It +// is a BUG if they are used to pass strings in any other multi-byte +// encoding. +// * wchar_t and std::wstring should be our interface to Windows wide-string +// APIs, and therefore hold UTF-16LE. +// * U16 and llutf16string are the previous but DEPRECATED UTF-16LE type. Do +// not introduce new uses of U16 or llutf16string for string data. +// * llwchar and LLWString hold UTF-32 strings. +// * Do not introduce char16_t or std::u16string. +// * Do not introduce char32_t or std::u32string. +// +// This typedef may or may not be identical to std::wstring, depending on +// LL_WCHAR_T_NATIVE. +typedef std::basic_string<U16> llutf16string; + +// Considering wchar_t, llwchar and U16, there are three relevant cases: +#if LLWCHAR_IS_WCHAR_T // every which way but Windows +// llwchar is identical to wchar_t, LLWString is identical to std::wstring. +// U16 is distinct, llutf16string is distinct (though pretty useless). +// Given conversions to/from LLWString and to/from llutf16string, conversions +// involving std::wstring would collide. +#define ll_convert_wstr_alias(TO, FROM, EXPR) // nothing +// but we can define conversions involving llutf16string without collisions +#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) + +#elif defined(LL_WCHAR_T_NATIVE) // Windows, either clang or MS /Zc:wchar_t +// llwchar (32-bit), wchar_t (16-bit) and U16 are all different types. +// Conversions to/from LLWString, to/from std::wstring and to/from llutf16string +// can all be defined. +#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) +#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) + +#else // ! LL_WCHAR_T_NATIVE: Windows with MS /Zc:wchar_t- +// wchar_t is identical to U16, std::wstring is identical to llutf16string. +// Given conversions to/from LLWString and to/from std::wstring, conversions +// involving llutf16string would collide. +#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing +// but we can define conversions involving std::wstring without collisions +#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) +#endif + +ll_convert_forms(ll_convert_u16_alias, LLWString, llutf16string, utf16str_to_wstring); +ll_convert_forms(ll_convert_u16_alias, llutf16string, LLWString, wstring_to_utf16str); +ll_convert_forms(ll_convert_u16_alias, llutf16string, std::string, utf8str_to_utf16str); +ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_wstring); + +// Same function, better name. JC +inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } + +LL_COMMON_API std::ptrdiff_t wchar_to_utf8chars(llwchar inchar, char* outchars); + +ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str); +ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str); + +// an older alias for utf16str_to_utf8str(llutf16string) +inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);} + +// Length of this UTF32 string in bytes when transformed to UTF8 +LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr); + +// Length in bytes of this wide char in a UTF8 string +LL_COMMON_API S32 wchar_utf8_length(const llwchar wc); + +LL_COMMON_API std::string wchar_utf8_preview(const llwchar wc); + +LL_COMMON_API std::string utf8str_tolower(const std::string& utf8str); + +// Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. +LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len); + +// Length in utf16string (UTF-16) of wlen wchars beginning at woffset. +LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); + +// Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) +LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, bool *unaligned = nullptr); + +/** + * @brief Properly truncate a utf8 string to a maximum byte count. + * + * The returned string may be less than max_len if the truncation + * happens in the middle of a glyph. If max_len is longer than the + * string passed in, the return value == utf8str. + * @param utf8str A valid utf8 string to truncate. + * @param max_len The maximum number of bytes in the return value. + * @return Returns a valid utf8 string with byte count <= max_len. + */ +LL_COMMON_API std::string utf8str_truncate(const std::string& utf8str, const S32 max_len); + +LL_COMMON_API std::string utf8str_trim(const std::string& utf8str); + +LL_COMMON_API S32 utf8str_compare_insensitive( + const std::string& lhs, + const std::string& rhs); + +/** +* @brief Properly truncate a utf8 string to a maximum character count. +* +* If symbol_len is longer than the string passed in, the return +* value == utf8str. +* @param utf8str A valid utf8 string to truncate. +* @param symbol_len The maximum number of symbols in the return value. +* @return Returns a valid utf8 string with symbol count <= max_len. +*/ +LL_COMMON_API std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len); + +/** + * @brief Replace all occurences of target_char with replace_char + * + * @param utf8str A utf8 string to process. + * @param target_char The wchar to be replaced + * @param replace_char The wchar which is written on replace + */ +LL_COMMON_API std::string utf8str_substChar( + const std::string& utf8str, + const llwchar target_char, + const llwchar replace_char); + +LL_COMMON_API std::string utf8str_makeASCII(const std::string& utf8str); + +// Hack - used for evil notecards. +LL_COMMON_API std::string mbcsstring_makeASCII(const std::string& str); + +LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str); + +LL_COMMON_API llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t length); + +LL_COMMON_API std::string utf8str_showBytesUTF8(const std::string& utf8str); + +LL_COMMON_API bool wstring_has_emoji(const LLWString& wstr); + +LL_COMMON_API bool wstring_remove_emojis(LLWString& wstr); + +LL_COMMON_API bool utf8str_remove_emojis(std::string& utf8str); + +#if LL_WINDOWS +/* @name Windows string helpers + */ +//@{ + +/** + * @brief Convert a wide string to/from std::string + * Convert a Windows wide string to/from our LLWString + * + * This replaces the unsafe W2A macro from ATL. + */ +// Avoid requiring this header to #include the Windows header file declaring +// our actual default code_page by delegating this function to our .cpp file. +LL_COMMON_API unsigned int ll_wstring_default_code_page(); + +// This is like ll_convert_forms(), with the added complexity of a code page +// parameter that may or may not be passed. +#define ll_convert_cp_forms(aliasmacro, OUTSTR, INSTR, longname) \ +/* declare the only nontrivial implementation (in .cpp file) */ \ +LL_COMMON_API OUTSTR longname( \ + const INSTR::value_type* in, \ + size_t len, \ + unsigned int code_page=ll_wstring_default_code_page()); \ +/* if passed only a char pointer, scan for nul terminator */ \ +inline auto longname(const INSTR::value_type* in) \ +{ \ + return longname(in, ll_convert_length(in)); \ +} \ +/* if passed string and length, extract its char pointer */ \ +inline auto longname( \ + const INSTR& in, \ + size_t len, \ + unsigned int code_page=ll_wstring_default_code_page()) \ +{ \ + return longname(in.c_str(), len, code_page); \ +} \ +/* if passed only a string object, no scan, pass known length */ \ +inline auto longname(const INSTR& in) \ +{ \ + return longname(in.c_str(), in.length()); \ +} \ +aliasmacro(OUTSTR, INSTR, longname(in)); \ +aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) + +ll_convert_cp_forms(ll_convert_wstr_alias, std::string, std::wstring, ll_convert_wide_to_string); +ll_convert_cp_forms(ll_convert_wstr_alias, std::wstring, std::string, ll_convert_string_to_wide); + ll_convert_forms(ll_convert_wstr_alias, LLWString, std::wstring, ll_convert_wide_to_wstring); + ll_convert_forms(ll_convert_wstr_alias, std::wstring, LLWString, ll_convert_wstring_to_wide); + +/** + * Converts incoming string into utf8 string + * + */ +LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in); + +/// Get Windows message string for passed GetLastError() code +// VS 2013 doesn't let us forward-declare this template, which is what we +// started with, so the implementation could reference the specialization we +// haven't yet declared. Somewhat weirdly, just stating the generic +// implementation in terms of the specialization works, even in this order... + +// the general case is just a conversion from the sole implementation +// Microsoft says DWORD is a typedef for unsigned long +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types +// so rather than drag windows.h into everybody's include space... +template<typename STRING> +STRING windows_message(unsigned long error) +{ + return ll_convert<STRING>(windows_message<std::wstring>(error)); +} + +/// There's only one real implementation +template<> +LL_COMMON_API std::wstring windows_message<std::wstring>(unsigned long error); + +/// Get Windows message string, implicitly calling GetLastError() +template<typename STRING> +STRING windows_message() { return windows_message<STRING>(GetLastError()); } + +//@} + +LL_COMMON_API std::optional<std::wstring> llstring_getoptenv(const std::string& key); + +#else // ! LL_WINDOWS + +LL_COMMON_API std::optional<std::string> llstring_getoptenv(const std::string& key); + +#endif // ! LL_WINDOWS + +/** + * Many of the 'strip' and 'replace' methods of LLStringUtilBase need + * specialization to work with the signed char type. + * Sadly, it is not possible (AFAIK) to specialize a single method of + * a template class. + * That stuff should go here. + */ +namespace LLStringFn +{ + /** + * @brief Replace all non-printable characters with replacement in + * string. + * NOTE - this will zap non-ascii + * + * @param [in,out] string the to modify. out value is the string + * with zero non-printable characters. + * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. + */ + LL_COMMON_API void replace_nonprintable_in_ascii( + std::basic_string<char>& string, + char replacement); + + + /** + * @brief Replace all non-printable characters and pipe characters + * with replacement in a string. + * NOTE - this will zap non-ascii + * + * @param [in,out] the string to modify. out value is the string + * with zero non-printable characters and zero pipe characters. + * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. + */ + LL_COMMON_API void replace_nonprintable_and_pipe_in_ascii(std::basic_string<char>& str, + char replacement); + + + /** + * @brief Remove all characters that are not allowed in XML 1.0. + * Returns a copy of the string with those characters removed. + * Works with US ASCII and UTF-8 encoded strings. JC + */ + LL_COMMON_API std::string strip_invalid_xml(const std::string& input); + + + /** + * @brief Replace all control characters (0 <= c < 0x20) with replacement in + * string. This is safe for utf-8 + * + * @param [in,out] string the to modify. out value is the string + * with zero non-printable characters. + * @param The replacement character. use LL_UNKNOWN_CHAR if unsure. + */ + LL_COMMON_API void replace_ascii_controlchars( + std::basic_string<char>& string, + char replacement); +} + +//////////////////////////////////////////////////////////// +// NOTE: LLStringUtil::format, getTokens, and support functions moved to llstring.cpp. +// There is no LLWStringUtil::format implementation currently. +// Calling these for anything other than LLStringUtil will produce link errors. + +//////////////////////////////////////////////////////////// + +// static +template <class T> +std::vector<typename LLStringUtilBase<T>::string_type> +LLStringUtilBase<T>::getTokens(const string_type& instr, const string_type& delims) +{ + std::vector<string_type> tokens; + getTokens(instr, tokens, delims); + return tokens; +} + +// static +template <class T> +std::vector<typename LLStringUtilBase<T>::string_type> +LLStringUtilBase<T>::getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes) +{ + std::vector<string_type> tokens; + getTokens(instr, tokens, drop_delims, keep_delims, quotes); + return tokens; +} + +// static +template <class T> +std::vector<typename LLStringUtilBase<T>::string_type> +LLStringUtilBase<T>::getTokens(const string_type& instr, + const string_type& drop_delims, + const string_type& keep_delims, + const string_type& quotes, + const string_type& escapes) +{ + std::vector<string_type> tokens; + getTokens(instr, tokens, drop_delims, keep_delims, quotes, escapes); + return tokens; +} + +namespace LLStringUtilBaseImpl +{ + +/** + * Input string scanner helper for getTokens(), or really any other + * character-parsing routine that may have to deal with escape characters. + * This implementation defines the concept (also an interface, should you + * choose to implement the concept by subclassing) and provides trivial + * implementations for a string @em without escape processing. + */ +template <class T> +struct InString +{ + typedef std::basic_string<T> string_type; + typedef typename string_type::const_iterator const_iterator; + + InString(const_iterator b, const_iterator e): + mIter(b), + mEnd(e) + {} + virtual ~InString() {} + + bool done() const { return mIter == mEnd; } + /// Is the current character (*mIter) escaped? This implementation can + /// answer trivially because it doesn't support escapes. + virtual bool escaped() const { return false; } + /// Obtain the current character and advance @c mIter. + virtual T next() { return *mIter++; } + /// Does the current character match specified character? + virtual bool is(T ch) const { return (! done()) && *mIter == ch; } + /// Is the current character any one of the specified characters? + virtual bool oneof(const string_type& delims) const + { + return (! done()) && LLStringUtilBase<T>::contains(delims, *mIter); + } + + /** + * Scan forward from @from until either @a delim or end. This is primarily + * useful for processing quoted substrings. + * + * If we do see @a delim, append everything from @from until (excluding) + * @a delim to @a into, advance @c mIter to skip @a delim, and return @c + * true. + * + * If we do not see @a delim, do not alter @a into or @c mIter and return + * @c false. Do not pass GO, do not collect $200. + * + * @note The @c false case described above implements normal getTokens() + * treatment of an unmatched open quote: treat the quote character as if + * escaped, that is, simply collect it as part of the current token. Other + * plausible behaviors directly affect the way getTokens() deals with an + * unmatched quote: e.g. throwing an exception to treat it as an error, or + * assuming a close quote beyond end of string (in which case return @c + * true). + */ + virtual bool collect_until(string_type& into, const_iterator from, T delim) + { + const_iterator found = std::find(from, mEnd, delim); + // If we didn't find delim, change nothing, just tell caller. + if (found == mEnd) + return false; + // Found delim! Append everything between from and found. + into.append(from, found); + // advance past delim in input + mIter = found + 1; + return true; + } + + const_iterator mIter, mEnd; +}; + +/// InString subclass that handles escape characters +template <class T> +class InEscString: public InString<T> +{ +public: + typedef InString<T> super; + typedef typename super::string_type string_type; + typedef typename super::const_iterator const_iterator; + using super::done; + using super::mIter; + using super::mEnd; + + InEscString(const_iterator b, const_iterator e, const string_type& escapes): + super(b, e), + mEscapes(escapes) + { + // Even though we've already initialized 'mIter' via our base-class + // constructor, set it again to check for initial escape char. + setiter(b); + } + + /// This implementation uses the answer cached by setiter(). + virtual bool escaped() const { return mIsEsc; } + virtual T next() + { + // If we're looking at the escape character of an escape sequence, + // skip that character. This is the one time we can modify 'mIter' + // without using setiter: for this one case we DO NOT CARE if the + // escaped character is itself an escape. + if (mIsEsc) + ++mIter; + // If we were looking at an escape character, this is the escaped + // character; otherwise it's just the next character. + T result(*mIter); + // Advance mIter, checking for escape sequence. + setiter(mIter + 1); + return result; + } + + virtual bool is(T ch) const + { + // Like base-class is(), except that an escaped character matches + // nothing. + return (! done()) && (! mIsEsc) && *mIter == ch; + } + + virtual bool oneof(const string_type& delims) const + { + // Like base-class oneof(), except that an escaped character matches + // nothing. + return (! done()) && (! mIsEsc) && LLStringUtilBase<T>::contains(delims, *mIter); + } + + virtual bool collect_until(string_type& into, const_iterator from, T delim) + { + // Deal with escapes in the characters we collect; that is, an escaped + // character must become just that character without the preceding + // escape. Collect characters in a separate string rather than + // directly appending to 'into' in case we do not find delim, in which + // case we're supposed to leave 'into' unmodified. + string_type collected; + // For scanning purposes, we're going to work directly with 'mIter'. + // Save its current value in case we fail to see delim. + const_iterator save_iter(mIter); + // Okay, set 'mIter', checking for escape. + setiter(from); + while (! done()) + { + // If we see an unescaped delim, stop and report success. + if ((! mIsEsc) && *mIter == delim) + { + // Append collected chars to 'into'. + into.append(collected); + // Don't forget to advance 'mIter' past delim. + setiter(mIter + 1); + return true; + } + // We're not at end, and either we're not looking at delim or it's + // escaped. Collect this character and keep going. + collected.push_back(next()); + } + // Here we hit 'mEnd' without ever seeing delim. Restore mIter and tell + // caller. + setiter(save_iter); + return false; + } + +private: + void setiter(const_iterator i) + { + mIter = i; + + // Every time we change 'mIter', set 'mIsEsc' to be able to repetitively + // answer escaped() without having to rescan 'mEscapes'. mIsEsc caches + // contains(mEscapes, *mIter). + + // We're looking at an escaped char if we're not already at end (that + // is, *mIter is even meaningful); if *mIter is in fact one of the + // specified escape characters; and if there's one more character + // following it. That is, if an escape character is the very last + // character of the input string, it loses its special meaning. + mIsEsc = (! done()) && + LLStringUtilBase<T>::contains(mEscapes, *mIter) && + (mIter+1) != mEnd; + } + + const string_type mEscapes; + bool mIsEsc; +}; + +/// getTokens() implementation based on InString concept +template <typename INSTRING, typename string_type> +void getTokens(INSTRING& instr, std::vector<string_type>& tokens, + const string_type& drop_delims, const string_type& keep_delims, + const string_type& quotes) +{ + // There are times when we want to match either drop_delims or + // keep_delims. Concatenate them up front to speed things up. + string_type all_delims(drop_delims + keep_delims); + // no tokens yet + tokens.clear(); + + // try for another token + while (! instr.done()) + { + // scan past any drop_delims + while (instr.oneof(drop_delims)) + { + // skip this drop_delim + instr.next(); + // but if that was the end of the string, done + if (instr.done()) + return; + } + // found the start of another token: make a slot for it + tokens.push_back(string_type()); + if (instr.oneof(keep_delims)) + { + // *iter is a keep_delim, a token of exactly 1 character. Append + // that character to the new token and proceed. + tokens.back().push_back(instr.next()); + continue; + } + // Here we have a non-delimiter token, which might consist of a mix of + // quoted and unquoted parts. Use bash rules for quoting: you can + // embed a quoted substring in the midst of an unquoted token (e.g. + // ~/"sub dir"/myfile.txt); you can ram two quoted substrings together + // to make a single token (e.g. 'He said, "'"Don't."'"'). We diverge + // from bash in that bash considers an unmatched quote an error. Our + // param signature doesn't allow for errors, so just pretend it's not + // a quote and embed it. + // At this level, keep scanning until we hit the next delimiter of + // either type (drop_delims or keep_delims). + while (! instr.oneof(all_delims)) + { + // If we're looking at an open quote, search forward for + // a close quote, collecting characters along the way. + if (instr.oneof(quotes) && + instr.collect_until(tokens.back(), instr.mIter+1, *instr.mIter)) + { + // collect_until is cleverly designed to do exactly what we + // need here. No further action needed if it returns true. + } + else + { + // Either *iter isn't a quote, or there's no matching close + // quote: in other words, just an ordinary char. Append it to + // current token. + tokens.back().push_back(instr.next()); + } + // having scanned that segment of this token, if we've reached the + // end of the string, we're done + if (instr.done()) + return; + } + } +} + +} // namespace LLStringUtilBaseImpl + +// static +template <class T> +void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens, + const string_type& drop_delims, const string_type& keep_delims, + const string_type& quotes) +{ + // Because this overload doesn't support escapes, use simple InString to + // manage input range. + LLStringUtilBaseImpl::InString<T> instring(string.begin(), string.end()); + LLStringUtilBaseImpl::getTokens(instring, tokens, drop_delims, keep_delims, quotes); +} + +// static +template <class T> +void LLStringUtilBase<T>::getTokens(const string_type& string, std::vector<string_type>& tokens, + const string_type& drop_delims, const string_type& keep_delims, + const string_type& quotes, const string_type& escapes) +{ + // This overload must deal with escapes. Delegate that to InEscString + // (unless there ARE no escapes). + std::unique_ptr< LLStringUtilBaseImpl::InString<T> > instrp; + if (escapes.empty()) + instrp.reset(new LLStringUtilBaseImpl::InString<T>(string.begin(), string.end())); + else + instrp.reset(new LLStringUtilBaseImpl::InEscString<T>(string.begin(), string.end(), escapes)); + LLStringUtilBaseImpl::getTokens(*instrp, tokens, drop_delims, keep_delims, quotes); +} + +// static +template<class T> +S32 LLStringUtilBase<T>::compareStrings(const T* lhs, const T* rhs) +{ + S32 result; + if( lhs == rhs ) + { + result = 0; + } + else + if ( !lhs || !lhs[0] ) + { + result = ((!rhs || !rhs[0]) ? 0 : 1); + } + else + if ( !rhs || !rhs[0]) + { + result = -1; + } + else + { + result = LLStringOps::collate(lhs, rhs); + } + return result; +} + +//static +template<class T> +S32 LLStringUtilBase<T>::compareStrings(const string_type& lhs, const string_type& rhs) +{ + return LLStringOps::collate(lhs.c_str(), rhs.c_str()); +} + +// static +template<class T> +S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs ) +{ + S32 result; + if( lhs == rhs ) + { + result = 0; + } + else + if ( !lhs || !lhs[0] ) + { + result = ((!rhs || !rhs[0]) ? 0 : 1); + } + else + if ( !rhs || !rhs[0] ) + { + result = -1; + } + else + { + string_type lhs_string(lhs); + string_type rhs_string(rhs); + LLStringUtilBase<T>::toUpper(lhs_string); + LLStringUtilBase<T>::toUpper(rhs_string); + result = LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); + } + return result; +} + +//static +template<class T> +S32 LLStringUtilBase<T>::compareInsensitive(const string_type& lhs, const string_type& rhs) +{ + string_type lhs_string(lhs); + string_type rhs_string(rhs); + LLStringUtilBase<T>::toUpper(lhs_string); + LLStringUtilBase<T>::toUpper(rhs_string); + return LLStringOps::collate(lhs_string.c_str(), rhs_string.c_str()); +} + +// Case sensitive comparison with good handling of numbers. Does not use current locale. +// a.k.a. strdictcmp() + +//static +template<class T> +S32 LLStringUtilBase<T>::compareDict(const string_type& astr, const string_type& bstr) +{ + const T* a = astr.c_str(); + const T* b = bstr.c_str(); + T ca, cb; + S32 ai, bi, cnt = 0; + S32 bias = 0; + + ca = *(a++); + cb = *(b++); + while( ca && cb ){ + if( bias==0 ){ + if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); bias--; } + if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); bias++; } + }else{ + if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); } + if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); } + } + if( LLStringOps::isDigit(ca) ){ + if( cnt-->0 ){ + if( cb!=ca ) break; + }else{ + if( !LLStringOps::isDigit(cb) ) break; + for(ai=0; LLStringOps::isDigit(a[ai]); ai++); + for(bi=0; LLStringOps::isDigit(b[bi]); bi++); + if( ai<bi ){ ca=0; break; } + if( bi<ai ){ cb=0; break; } + if( ca!=cb ) break; + cnt = ai; + } + }else if( ca!=cb ){ break; + } + ca = *(a++); + cb = *(b++); + } + if( ca==cb ) ca += bias; + return ca-cb; +} + +// static +template<class T> +S32 LLStringUtilBase<T>::compareDictInsensitive(const string_type& astr, const string_type& bstr) +{ + const T* a = astr.c_str(); + const T* b = bstr.c_str(); + T ca, cb; + S32 ai, bi, cnt = 0; + + ca = *(a++); + cb = *(b++); + while( ca && cb ){ + if( LLStringOps::isUpper(ca) ){ ca = LLStringOps::toLower(ca); } + if( LLStringOps::isUpper(cb) ){ cb = LLStringOps::toLower(cb); } + if( LLStringOps::isDigit(ca) ){ + if( cnt-->0 ){ + if( cb!=ca ) break; + }else{ + if( !LLStringOps::isDigit(cb) ) break; + for(ai=0; LLStringOps::isDigit(a[ai]); ai++); + for(bi=0; LLStringOps::isDigit(b[bi]); bi++); + if( ai<bi ){ ca=0; break; } + if( bi<ai ){ cb=0; break; } + if( ca!=cb ) break; + cnt = ai; + } + }else if( ca!=cb ){ break; + } + ca = *(a++); + cb = *(b++); + } + return ca-cb; +} + +// Puts compareDict() in a form appropriate for LL container classes to use for sorting. +// static +template<class T> +bool LLStringUtilBase<T>::precedesDict( const string_type& a, const string_type& b ) +{ + if( a.size() && b.size() ) + { + return (LLStringUtilBase<T>::compareDict(a.c_str(), b.c_str()) < 0); + } + else + { + return (!b.empty()); + } +} + +//static +template<class T> +void LLStringUtilBase<T>::toUpper(string_type& string) +{ + if( !string.empty() ) + { + std::transform( + string.begin(), + string.end(), + string.begin(), + (T(*)(T)) &LLStringOps::toUpper); + } +} + +//static +template<class T> +void LLStringUtilBase<T>::toLower(string_type& string) +{ + if( !string.empty() ) + { + std::transform( + string.begin(), + string.end(), + string.begin(), + (T(*)(T)) &LLStringOps::toLower); + } +} + +//static +template<class T> +void LLStringUtilBase<T>::trimHead(string_type& string) +{ + if( !string.empty() ) + { + size_type i = 0; + while( i < string.length() && LLStringOps::isSpace( string[i] ) ) + { + i++; + } + string.erase(0, i); + } +} + +//static +template<class T> +void LLStringUtilBase<T>::trimTail(string_type& string) +{ + if( string.size() ) + { + size_type len = string.length(); + size_type i = len; + while( i > 0 && LLStringOps::isSpace( string[i-1] ) ) + { + i--; + } + + string.erase( i, len - i ); + } +} + + +// Replace line feeds with carriage return-line feed pairs. +//static +template<class T> +void LLStringUtilBase<T>::addCRLF(string_type& string) +{ + const T LF = 10; + const T CR = 13; + + // Count the number of line feeds + size_type count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len; i++ ) + { + if( string[i] == LF ) + { + count++; + } + } + + // Insert a carriage return before each line feed + if( count ) + { + size_type size = len + count; + T *t = new T[size]; + size_type j = 0; + for( i = 0; i < len; ++i ) + { + if( string[i] == LF ) + { + t[j] = CR; + ++j; + } + t[j] = string[i]; + ++j; + } + + string.assign(t, size); + delete[] t; + } +} + +// Remove all carriage returns +//static +template<class T> +void LLStringUtilBase<T>::removeCRLF(string_type& string) +{ + const T CR = 13; + + size_type cr_count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len - cr_count; i++ ) + { + if( string[i+cr_count] == CR ) + { + cr_count++; + } + + string[i] = string[i+cr_count]; + } + string.erase(i, cr_count); +} + +//static +template<class T> +void LLStringUtilBase<T>::removeWindowsCR(string_type& string) +{ + if (string.empty()) + { + return; + } + const T LF = 10; + const T CR = 13; + + size_type cr_count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len - cr_count - 1; i++ ) + { + if( string[i+cr_count] == CR && string[i+cr_count+1] == LF) + { + cr_count++; + } + + string[i] = string[i+cr_count]; + } + string.erase(i, cr_count); +} + +//static +template<class T> +void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement ) +{ + size_type found_pos = 0; + while( (found_pos = string.find(target, found_pos)) != string_type::npos ) + { + string[found_pos] = replacement; + found_pos++; // avoid infinite defeat if target == replacement + } +} + +//static +template<class T> +void LLStringUtilBase<T>::replaceString( string_type& string, string_type target, string_type replacement ) +{ + size_type found_pos = 0; + while( (found_pos = string.find(target, found_pos)) != string_type::npos ) + { + string.replace( found_pos, target.length(), replacement ); + found_pos += replacement.length(); // avoid infinite defeat if replacement contains target + } +} + +//static +template<class T> +void LLStringUtilBase<T>::replaceNonstandardASCII( string_type& string, T replacement ) +{ + const char LF = 10; + const S8 MIN = 32; +// const S8 MAX = 127; + + size_type len = string.size(); + for( size_type i = 0; i < len; i++ ) + { + // No need to test MAX < mText[i] because we treat mText[i] as a signed char, + // which has a max value of 127. + if( ( S8(string[i]) < MIN ) && (string[i] != LF) ) + { + string[i] = replacement; + } + } +} + +//static +template<class T> +void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab ) +{ + const T TAB = '\t'; + const T SPACE = ' '; + + string_type out_str; + // Replace tabs with spaces + for (size_type i = 0; i < str.length(); i++) + { + if (str[i] == TAB) + { + for (size_type j = 0; j < spaces_per_tab; j++) + out_str += SPACE; + } + else + { + out_str += str[i]; + } + } + str = out_str; +} + +//static +template<class T> +std::basic_string<T> LLStringUtilBase<T>::capitalize(const string_type& str) +{ + string_type result(str); + capitalize(result); + return result; +} + +//static +template<class T> +void LLStringUtilBase<T>::capitalize(string_type& str) +{ + if (str.size()) + { + auto last = str[0] = toupper(str[0]); + for (U32 i = 1; i < str.size(); ++i) + { + last = (last == ' ' || last == '-' || last == '_') ? str[i] = toupper(str[i]) : str[i]; + } + } +} + +//static +template<class T> +bool LLStringUtilBase<T>::containsNonprintable(const string_type& string) +{ + const char MIN = 32; + bool rv = false; + for (size_type i = 0; i < string.size(); i++) + { + if(string[i] < MIN) + { + rv = true; + break; + } + } + return rv; +} + +// *TODO: reimplement in terms of algorithm +//static +template<class T> +void LLStringUtilBase<T>::stripNonprintable(string_type& string) +{ + const char MIN = 32; + size_type j = 0; + if (string.empty()) + { + return; + } + size_t src_size = string.size(); + char* c_string = new char[src_size + 1]; + if(c_string == NULL) + { + return; + } + copy(c_string, string.c_str(), src_size+1); + char* write_head = &c_string[0]; + for (size_type i = 0; i < src_size; i++) + { + char* read_head = &string[i]; + write_head = &c_string[j]; + if(!(*read_head < MIN)) + { + *write_head = *read_head; + ++j; + } + } + c_string[j]= '\0'; + string = c_string; + delete []c_string; +} + +// *TODO: reimplement in terms of algorithm +template<class T> +std::basic_string<T> LLStringUtilBase<T>::quote(const string_type& str, + const string_type& triggers, + const string_type& escape) +{ + size_type len(str.length()); + // If the string is already quoted, assume user knows what s/he's doing. + if (len >= 2 && str[0] == '"' && str[len-1] == '"') + { + return str; + } + + // Not already quoted: do we need to? triggers.empty() is a special case + // meaning "always quote." + if ((! triggers.empty()) && str.find_first_of(triggers) == string_type::npos) + { + // no trigger characters, don't bother quoting + return str; + } + + // For whatever reason, we must quote this string. + string_type result; + result.push_back('"'); + for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) + { + if (*ci == '"') + { + result.append(escape); + } + result.push_back(*ci); + } + result.push_back('"'); + return result; +} + +template<class T> +void LLStringUtilBase<T>::_makeASCII(string_type& string) +{ + // Replace non-ASCII chars with LL_UNKNOWN_CHAR + for (size_type i = 0; i < string.length(); i++) + { + if (string[i] > 0x7f) + { + string[i] = LL_UNKNOWN_CHAR; + } + } +} + +// static +template<class T> +void LLStringUtilBase<T>::copy( T* dst, const T* src, size_type dst_size ) +{ + if( dst_size > 0 ) + { + size_type min_len = 0; + if( src ) + { + min_len = llmin( dst_size - 1, strlen( src ) ); /* Flawfinder: ignore */ + memcpy(dst, src, min_len * sizeof(T)); /* Flawfinder: ignore */ + } + dst[min_len] = '\0'; + } +} + +// static +template<class T> +void LLStringUtilBase<T>::copyInto(string_type& dst, const string_type& src, size_type offset) +{ + if ( offset == dst.length() ) + { + // special case - append to end of string and avoid expensive + // (when strings are large) string manipulations + dst += src; + } + else + { + string_type tail = dst.substr(offset); + + dst = dst.substr(0, offset); + dst += src; + dst += tail; + }; +} + +// True if this is the head of s. +//static +template<class T> +bool LLStringUtilBase<T>::isHead( const string_type& string, const T* s ) +{ + if( string.empty() ) + { + // Early exit + return false; + } + else + { + return (strncmp( s, string.c_str(), string.size() ) == 0); + } +} + +// static +template<class T> +bool LLStringUtilBase<T>::startsWith( + const string_type& string, + const string_type& substr) +{ + if(string.empty() || (substr.empty())) return false; + if (substr.length() > string.length()) return false; + if (0 == string.compare(0, substr.length(), substr)) return true; + return false; +} + +// static +template<class T> +bool LLStringUtilBase<T>::endsWith( + const string_type& string, + const string_type& substr) +{ + if(string.empty() || (substr.empty())) return false; + size_t sub_len = substr.length(); + size_t str_len = string.length(); + if (sub_len > str_len) return false; + if (0 == string.compare(str_len - sub_len, sub_len, substr)) return true; + return false; +} + +// static +template<class T> +auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> std::optional<string_type> +{ + auto found(llstring_getoptenv(key)); + if (found) + { + // return populated std::optional + return { ll_convert<string_type>(*found) }; + } + else + { + // empty std::optional + return {}; + } +} + +// static +template<class T> +auto LLStringUtilBase<T>::getenv(const std::string& key, const string_type& dflt) -> string_type +{ + auto found(getoptenv(key)); + if (found) + { + return *found; + } + else + { + return dflt; + } +} + +template<class T> +bool LLStringUtilBase<T>::convertToBOOL(const string_type& string, bool& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + if( + (temp == "1") || + (temp == "T") || + (temp == "t") || + (temp == "TRUE") || + (temp == "true") || + (temp == "True") ) + { + value = true; + return true; + } + else + if( + (temp == "0") || + (temp == "F") || + (temp == "f") || + (temp == "FALSE") || + (temp == "false") || + (temp == "False") ) + { + value = false; + return true; + } + + return false; +} + +template<class T> +bool LLStringUtilBase<T>::convertToU8(const string_type& string, U8& value) +{ + S32 value32 = 0; + bool success = convertToS32(string, value32); + if( success && (U8_MIN <= value32) && (value32 <= U8_MAX) ) + { + value = (U8) value32; + return true; + } + return false; +} + +template<class T> +bool LLStringUtilBase<T>::convertToS8(const string_type& string, S8& value) +{ + S32 value32 = 0; + bool success = convertToS32(string, value32); + if( success && (S8_MIN <= value32) && (value32 <= S8_MAX) ) + { + value = (S8) value32; + return true; + } + return false; +} + +template<class T> +bool LLStringUtilBase<T>::convertToS16(const string_type& string, S16& value) +{ + S32 value32 = 0; + bool success = convertToS32(string, value32); + if( success && (S16_MIN <= value32) && (value32 <= S16_MAX) ) + { + value = (S16) value32; + return true; + } + return false; +} + +template<class T> +bool LLStringUtilBase<T>::convertToU16(const string_type& string, U16& value) +{ + S32 value32 = 0; + bool success = convertToS32(string, value32); + if( success && (U16_MIN <= value32) && (value32 <= U16_MAX) ) + { + value = (U16) value32; + return true; + } + return false; +} + +template<class T> +bool LLStringUtilBase<T>::convertToU32(const string_type& string, U32& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + U32 v; + std::basic_istringstream<T> i_stream((string_type)temp); + if(i_stream >> v) + { + value = v; + return true; + } + return false; +} + +template<class T> +bool LLStringUtilBase<T>::convertToS32(const string_type& string, S32& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + S32 v; + std::basic_istringstream<T> i_stream((string_type)temp); + if(i_stream >> v) + { + //TODO: figure out overflow and underflow reporting here + //if((LONG_MAX == v) || (LONG_MIN == v)) + //{ + // // Underflow or overflow + // return false; + //} + + value = v; + return true; + } + return false; +} + +template<class T> +bool LLStringUtilBase<T>::convertToF32(const string_type& string, F32& value) +{ + F64 value64 = 0.0; + bool success = convertToF64(string, value64); + if( success && (-F32_MAX <= value64) && (value64 <= F32_MAX) ) + { + value = (F32) value64; + return true; + } + return false; +} + +template<class T> +bool LLStringUtilBase<T>::convertToF64(const string_type& string, F64& value) +{ + if( string.empty() ) + { + return false; + } + + string_type temp( string ); + trim(temp); + F64 v; + std::basic_istringstream<T> i_stream((string_type)temp); + if(i_stream >> v) + { + //TODO: figure out overflow and underflow reporting here + //if( ((-HUGE_VAL == v) || (HUGE_VAL == v))) ) + //{ + // // Underflow or overflow + // return false; + //} + + value = v; + return true; + } + return false; +} + +template<class T> +void LLStringUtilBase<T>::truncate(string_type& string, size_type count) +{ + size_type cur_size = string.size(); + string.resize(count < cur_size ? count : cur_size); +} + +// The good thing about *declaration* macros, vs. usage macros, is that now +// we're done with them: we don't need them to bleed into the consuming source +// file. +#undef ll_convert_alias +#undef ll_convert_u16_alias +#undef ll_convert_wstr_alias +#undef LL_CONVERT_COPY_CHARS +#undef ll_convert_forms +#undef ll_convert_cp_forms + +#endif // LL_STRING_H diff --git a/indra/llcommon/llstringtable.h b/indra/llcommon/llstringtable.h index 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 <list>
-#include <set>
-
-#if LL_WINDOWS
-# if (_MSC_VER >= 1300 && _MSC_VER < 1400)
-# define STRING_TABLE_HASH_MAP 1
-# endif
-#else
-//# define STRING_TABLE_HASH_MAP 1
-#endif
-
-const U32 MAX_STRINGS_LENGTH = 256;
-
-class LL_COMMON_API LLStringTableEntry
-{
-public:
- LLStringTableEntry(const char *str);
- ~LLStringTableEntry();
-
- void incCount() { mCount++; }
- bool decCount() { return --mCount != 0; }
-
- char *mString;
- S32 mCount;
-};
-
-class LL_COMMON_API LLStringTable
-{
-public:
- LLStringTable(int tablesize);
- ~LLStringTable();
-
- char *checkString(const char *str);
- char *checkString(const std::string& str);
- LLStringTableEntry *checkStringEntry(const char *str);
- LLStringTableEntry *checkStringEntry(const std::string& str);
-
- char *addString(const char *str);
- char *addString(const std::string& str);
- LLStringTableEntry *addStringEntry(const char *str);
- LLStringTableEntry *addStringEntry(const std::string& str);
- void removeString(const char *str);
-
- S32 mMaxEntries;
- S32 mUniqueEntries;
-
-#if STRING_TABLE_HASH_MAP
-#if LL_WINDOWS
- typedef std::hash_multimap<U32, LLStringTableEntry *> string_hash_t;
-#else
- typedef __gnu_cxx::hash_multimap<U32, LLStringTableEntry *> string_hash_t;
-#endif
- string_hash_t mStringHash;
-#else
- typedef std::list<LLStringTableEntry *> string_list_t;
- typedef string_list_t * string_list_ptr_t;
- string_list_ptr_t *mStringList;
-#endif
-};
-
-extern LL_COMMON_API LLStringTable gStringTable;
-
-//============================================================================
-
-// This class is designed to be used locally,
-// e.g. as a member of an LLXmlTree
-// Strings can be inserted only, then quickly looked up
-
-typedef const std::string* LLStdStringHandle;
-
-class LL_COMMON_API LLStdStringTable
-{
-public:
- LLStdStringTable(S32 tablesize = 0)
- {
- if (tablesize == 0)
- {
- tablesize = 256; // default
- }
- // Make sure tablesize is power of 2
- for (S32 i = 31; i>0; i--)
- {
- if (tablesize & (1<<i))
- {
- if (tablesize >= (3<<(i-1)))
- tablesize = (1<<(i+1));
- else
- tablesize = (1<<i);
- break;
- }
- }
- mTableSize = tablesize;
- mStringList = new string_set_t[tablesize];
- }
- ~LLStdStringTable()
- {
- cleanup();
- delete[] mStringList;
- }
- void cleanup()
- {
- // remove strings
- for (S32 i = 0; i<mTableSize; i++)
- {
- string_set_t& stringset = mStringList[i];
- for (LLStdStringHandle str : stringset)
- {
- delete str;
- }
- stringset.clear();
- }
- }
-
- LLStdStringHandle lookup(const std::string& s)
- {
- U32 hashval = makehash(s);
- return lookup(hashval, s);
- }
-
- LLStdStringHandle checkString(const std::string& s)
- {
- U32 hashval = makehash(s);
- return lookup(hashval, s);
- }
-
- LLStdStringHandle insert(const std::string& s)
- {
- U32 hashval = makehash(s);
- LLStdStringHandle result = lookup(hashval, s);
- if (result == NULL)
- {
- result = new std::string(s);
- mStringList[hashval].insert(result);
- }
- return result;
- }
- LLStdStringHandle addString(const std::string& s)
- {
- return insert(s);
- }
-
-private:
- U32 makehash(const std::string& s)
- {
- S32 len = (S32)s.size();
- const char* c = s.c_str();
- U32 hashval = 0;
- for (S32 i=0; i<len; i++)
- {
- hashval = ((hashval<<5) + hashval) + *c++;
- }
- return hashval & (mTableSize-1);
- }
- LLStdStringHandle lookup(U32 hashval, const std::string& s)
- {
- string_set_t& stringset = mStringList[hashval];
- LLStdStringHandle handle = &s;
- string_set_t::iterator iter = stringset.find(handle); // compares actual strings
- if (iter != stringset.end())
- {
- return *iter;
- }
- else
- {
- return NULL;
- }
- }
-
-private:
- S32 mTableSize;
- typedef std::set<LLStdStringHandle, compare_pointer_contents<std::string> > string_set_t;
- string_set_t* mStringList; // [mTableSize]
-};
-
-
-#endif
+/** + * @file llstringtable.h + * @brief The LLStringTable class provides a _fast_ method for finding + * unique copies of strings. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_STRING_TABLE_H +#define LL_STRING_TABLE_H + +#include "lldefs.h" +#include "llformat.h" +#include "llstl.h" +#include <list> +#include <set> + +#if LL_WINDOWS +# if (_MSC_VER >= 1300 && _MSC_VER < 1400) +# define STRING_TABLE_HASH_MAP 1 +# endif +#else +//# define STRING_TABLE_HASH_MAP 1 +#endif + +const U32 MAX_STRINGS_LENGTH = 256; + +class LL_COMMON_API LLStringTableEntry +{ +public: + LLStringTableEntry(const char *str); + ~LLStringTableEntry(); + + void incCount() { mCount++; } + bool decCount() { return --mCount != 0; } + + char *mString; + S32 mCount; +}; + +class LL_COMMON_API LLStringTable +{ +public: + LLStringTable(int tablesize); + ~LLStringTable(); + + char *checkString(const char *str); + char *checkString(const std::string& str); + LLStringTableEntry *checkStringEntry(const char *str); + LLStringTableEntry *checkStringEntry(const std::string& str); + + char *addString(const char *str); + char *addString(const std::string& str); + LLStringTableEntry *addStringEntry(const char *str); + LLStringTableEntry *addStringEntry(const std::string& str); + void removeString(const char *str); + + S32 mMaxEntries; + S32 mUniqueEntries; + +#if STRING_TABLE_HASH_MAP +#if LL_WINDOWS + typedef std::hash_multimap<U32, LLStringTableEntry *> string_hash_t; +#else + typedef __gnu_cxx::hash_multimap<U32, LLStringTableEntry *> string_hash_t; +#endif + string_hash_t mStringHash; +#else + typedef std::list<LLStringTableEntry *> string_list_t; + typedef string_list_t * string_list_ptr_t; + string_list_ptr_t *mStringList; +#endif +}; + +extern LL_COMMON_API LLStringTable gStringTable; + +//============================================================================ + +// This class is designed to be used locally, +// e.g. as a member of an LLXmlTree +// Strings can be inserted only, then quickly looked up + +typedef const std::string* LLStdStringHandle; + +class LL_COMMON_API LLStdStringTable +{ +public: + LLStdStringTable(S32 tablesize = 0) + { + if (tablesize == 0) + { + tablesize = 256; // default + } + // Make sure tablesize is power of 2 + for (S32 i = 31; i>0; i--) + { + if (tablesize & (1<<i)) + { + if (tablesize >= (3<<(i-1))) + tablesize = (1<<(i+1)); + else + tablesize = (1<<i); + break; + } + } + mTableSize = tablesize; + mStringList = new string_set_t[tablesize]; + } + ~LLStdStringTable() + { + cleanup(); + delete[] mStringList; + } + void cleanup() + { + // remove strings + for (S32 i = 0; i<mTableSize; i++) + { + string_set_t& stringset = mStringList[i]; + for (LLStdStringHandle str : stringset) + { + delete str; + } + stringset.clear(); + } + } + + LLStdStringHandle lookup(const std::string& s) + { + U32 hashval = makehash(s); + return lookup(hashval, s); + } + + LLStdStringHandle checkString(const std::string& s) + { + U32 hashval = makehash(s); + return lookup(hashval, s); + } + + LLStdStringHandle insert(const std::string& s) + { + U32 hashval = makehash(s); + LLStdStringHandle result = lookup(hashval, s); + if (result == NULL) + { + result = new std::string(s); + mStringList[hashval].insert(result); + } + return result; + } + LLStdStringHandle addString(const std::string& s) + { + return insert(s); + } + +private: + U32 makehash(const std::string& s) + { + S32 len = (S32)s.size(); + const char* c = s.c_str(); + U32 hashval = 0; + for (S32 i=0; i<len; i++) + { + hashval = ((hashval<<5) + hashval) + *c++; + } + return hashval & (mTableSize-1); + } + LLStdStringHandle lookup(U32 hashval, const std::string& s) + { + string_set_t& stringset = mStringList[hashval]; + LLStdStringHandle handle = &s; + string_set_t::iterator iter = stringset.find(handle); // compares actual strings + if (iter != stringset.end()) + { + return *iter; + } + else + { + return NULL; + } + } + +private: + S32 mTableSize; + typedef std::set<LLStdStringHandle, compare_pointer_contents<std::string> > string_set_t; + string_set_t* mStringList; // [mTableSize] +}; + + +#endif diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 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 <iostream>
-#ifdef LL_USESYSTEMLIBS
-# include <zlib.h>
-#else
-# include "zlib-ng/zlib.h"
-#endif
-
-#include "llprocessor.h"
-#include "llerrorcontrol.h"
-#include "llevents.h"
-#include "llformat.h"
-#include "llregex.h"
-#include "lltimer.h"
-#include "llsdserialize.h"
-#include "llsdutil.h"
-#include <boost/bind.hpp>
-#include <boost/circular_buffer.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/range.hpp>
-#include <boost/utility/enable_if.hpp>
-#include <boost/type_traits/is_integral.hpp>
-#include <boost/type_traits/is_float.hpp>
-#include "llfasttimer.h"
-
-using namespace llsd;
-
-#if LL_WINDOWS
-# include "llwin32headerslean.h"
-# include <psapi.h> // GetPerformanceInfo() et al.
-# include <VersionHelpers.h>
-#elif LL_DARWIN
-# include "llsys_objc.h"
-# include <errno.h>
-# include <sys/sysctl.h>
-# include <sys/utsname.h>
-# include <stdint.h>
-# include <CoreServices/CoreServices.h>
-# include <stdexcept>
-# include <mach/host_info.h>
-# include <mach/mach_host.h>
-# include <mach/task.h>
-# include <mach/task_info.h>
-#elif LL_LINUX
-# include <errno.h>
-# include <sys/utsname.h>
-# include <unistd.h>
-# include <sys/sysinfo.h>
-# include <stdexcept>
-const char MEMINFO_FILE[] = "/proc/meminfo";
-# include <gnu/libc-version.h>
-#endif
-
-LLCPUInfo gSysCPU;
-
-// Don't log memory info any more often than this. It also serves as our
-// framerate sample size.
-static const F32 MEM_INFO_THROTTLE = 20;
-// Sliding window of samples. We intentionally limit the length of time we
-// remember "the slowest" framerate because framerate is very slow at login.
-// If we only triggered FrameWatcher logging when the session framerate
-// dropped below the login framerate, we'd have very little additional data.
-static const F32 MEM_INFO_WINDOW = 10*60;
-
-LLOSInfo::LLOSInfo() :
- mMajorVer(0), mMinorVer(0), mBuild(0), mOSVersionString("")
-{
-
-#if LL_WINDOWS
-
- if (IsWindows10OrGreater())
- {
- mMajorVer = 10;
- mMinorVer = 0;
- mOSStringSimple = "Microsoft Windows 10 ";
- }
- else if (IsWindows8Point1OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 3;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2012 R2 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows 8.1 ";
- }
- }
- else if (IsWindows8OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 2;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2012 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows 8 ";
- }
- }
- else if (IsWindows7SP1OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 1;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2008 R2 SP1 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows 7 SP1 ";
- }
- }
- else if (IsWindows7OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 1;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2008 R2 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows 7 ";
- }
- }
- else if (IsWindowsVistaSP2OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 0;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2008 SP2 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows Vista SP2 ";
- }
- }
- else
- {
- mOSStringSimple = "Unsupported Windows version ";
- }
-
- ///get native system info if available..
- typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); ///function pointer for loading GetNativeSystemInfo
- SYSTEM_INFO si; //System Info object file contains architecture info
- PGNSI pGNSI; //pointer object
- ZeroMemory(&si, sizeof(SYSTEM_INFO)); //zero out the memory in information
- pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); //load kernel32 get function
- if (NULL != pGNSI) //check if it has failed
- pGNSI(&si); //success
- else
- GetSystemInfo(&si); //if it fails get regular system info
- //(Warning: If GetSystemInfo it may result in incorrect information in a WOW64 machine, if the kernel fails to load)
-
- // Try calling GetVersionEx using the OSVERSIONINFOEX structure.
- OSVERSIONINFOEX osvi;
- ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
- if (GetVersionEx((OSVERSIONINFO *)&osvi))
- {
- mBuild = osvi.dwBuildNumber & 0xffff;
- }
- else
- {
- // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
- if (GetVersionEx((OSVERSIONINFO *)&osvi))
- {
- mBuild = osvi.dwBuildNumber & 0xffff;
- }
- }
-
- S32 ubr = 0; // Windows 10 Update Build Revision, can be retrieved from a registry
- if (mMajorVer == 10)
- {
- DWORD cbData(sizeof(DWORD));
- DWORD data(0);
- HKEY key;
- LSTATUS ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key);
- if (ERROR_SUCCESS == ret_code)
- {
- ret_code = RegQueryValueExW(key, L"UBR", 0, NULL, reinterpret_cast<LPBYTE>(&data), &cbData);
- if (ERROR_SUCCESS == ret_code)
- {
- ubr = data;
- }
- }
-
- if (mBuild >= 22000)
- {
- // At release Windows 11 version was 10.0.22000.194
- // Windows 10 version was 10.0.19043.1266
- // There is no warranty that Win10 build won't increase,
- // so until better solution is found or Microsoft updates
- // SDK with IsWindows11OrGreater(), indicate "10/11"
- //
- // Current alternatives:
- // Query WMI's Win32_OperatingSystem for OS string. Slow
- // and likely to return 'compatibility' string.
- // Check presence of dlls/libs or may be their version.
- mOSStringSimple = "Microsoft Windows 10/11 ";
- }
- }
-
- //msdn microsoft finds 32 bit and 64 bit flavors this way..
- //http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx (example code that contains quite a few more flavors
- //of windows than this code does (in case it is needed for the future)
- if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) //check for 64 bit
- {
- mOSStringSimple += "64-bit ";
- }
- else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)
- {
- mOSStringSimple += "32-bit ";
- }
-
- mOSString = mOSStringSimple;
- if (mBuild > 0)
- {
- mOSString += llformat("(Build %d", mBuild);
- if (ubr > 0)
- {
- mOSString += llformat(".%d", ubr);
- }
- mOSString += ")";
- }
-
- LLStringUtil::trim(mOSStringSimple);
- LLStringUtil::trim(mOSString);
-
-#elif LL_DARWIN
-
- // Initialize mOSStringSimple to something like:
- // "macOS 10.13.1"
- {
- const char * DARWIN_PRODUCT_NAME = "macOS";
-
- int64_t major_version, minor_version, bugfix_version = 0;
-
- if (LLGetDarwinOSInfo(major_version, minor_version, bugfix_version))
- {
- mMajorVer = major_version;
- mMinorVer = minor_version;
- mBuild = bugfix_version;
-
- std::stringstream os_version_string;
- os_version_string << DARWIN_PRODUCT_NAME << " " << mMajorVer << "." << mMinorVer << "." << mBuild;
-
- // Put it in the OS string we are compiling
- mOSStringSimple.append(os_version_string.str());
- }
- else
- {
- mOSStringSimple.append("Unable to collect OS info");
- }
- }
-
- // Initialize mOSString to something like:
- // "macOS 10.13.1 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386"
- struct utsname un;
- if(uname(&un) != -1)
- {
- mOSString = mOSStringSimple;
- mOSString.append(" ");
- mOSString.append(un.sysname);
- mOSString.append(" ");
- mOSString.append(un.release);
- mOSString.append(" ");
- mOSString.append(un.version);
- mOSString.append(" ");
- mOSString.append(un.machine);
- }
- else
- {
- mOSString = mOSStringSimple;
- }
-
-#elif LL_LINUX
-
- struct utsname un;
- if(uname(&un) != -1)
- {
- mOSStringSimple.append(un.sysname);
- mOSStringSimple.append(" ");
- mOSStringSimple.append(un.release);
-
- mOSString = mOSStringSimple;
- mOSString.append(" ");
- mOSString.append(un.version);
- mOSString.append(" ");
- mOSString.append(un.machine);
-
- // Simplify 'Simple'
- std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0));
- if (ostype == "Linux")
- {
- // Only care about major and minor Linux versions, truncate at second '.'
- std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0);
- std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos;
- std::string simple = mOSStringSimple.substr(0, idx2);
- if (simple.length() > 0)
- mOSStringSimple = simple;
- }
- }
- else
- {
- mOSStringSimple.append("Unable to collect OS info");
- mOSString = mOSStringSimple;
- }
-
- const char OS_VERSION_MATCH_EXPRESSION[] = "([0-9]+)\\.([0-9]+)(\\.([0-9]+))?";
- boost::regex os_version_parse(OS_VERSION_MATCH_EXPRESSION);
- boost::smatch matched;
-
- std::string glibc_version(gnu_get_libc_version());
- if ( ll_regex_match(glibc_version, matched, os_version_parse) )
- {
- LL_INFOS("AppInit") << "Using glibc version '" << glibc_version << "' as OS version" << LL_ENDL;
-
- std::string version_value;
-
- if ( matched[1].matched ) // Major version
- {
- version_value.assign(matched[1].first, matched[1].second);
- if (sscanf(version_value.c_str(), "%d", &mMajorVer) != 1)
- {
- LL_WARNS("AppInit") << "failed to parse major version '" << version_value << "' as a number" << LL_ENDL;
- }
- }
- else
- {
- LL_ERRS("AppInit")
- << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION
- << "' returned true, but major version [1] did not match"
- << LL_ENDL;
- }
-
- if ( matched[2].matched ) // Minor version
- {
- version_value.assign(matched[2].first, matched[2].second);
- if (sscanf(version_value.c_str(), "%d", &mMinorVer) != 1)
- {
- LL_ERRS("AppInit") << "failed to parse minor version '" << version_value << "' as a number" << LL_ENDL;
- }
- }
- else
- {
- LL_ERRS("AppInit")
- << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION
- << "' returned true, but minor version [1] did not match"
- << LL_ENDL;
- }
-
- if ( matched[4].matched ) // Build version (optional) - note that [3] includes the '.'
- {
- version_value.assign(matched[4].first, matched[4].second);
- if (sscanf(version_value.c_str(), "%d", &mBuild) != 1)
- {
- LL_ERRS("AppInit") << "failed to parse build version '" << version_value << "' as a number" << LL_ENDL;
- }
- }
- else
- {
- LL_INFOS("AppInit")
- << "OS build version not provided; using zero"
- << LL_ENDL;
- }
- }
- else
- {
- LL_WARNS("AppInit") << "glibc version '" << glibc_version << "' cannot be parsed to three numbers; using all zeros" << LL_ENDL;
- }
-
-#else
-
- struct utsname un;
- if(uname(&un) != -1)
- {
- mOSStringSimple.append(un.sysname);
- mOSStringSimple.append(" ");
- mOSStringSimple.append(un.release);
-
- mOSString = mOSStringSimple;
- mOSString.append(" ");
- mOSString.append(un.version);
- mOSString.append(" ");
- mOSString.append(un.machine);
-
- // Simplify 'Simple'
- std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0));
- if (ostype == "Linux")
- {
- // Only care about major and minor Linux versions, truncate at second '.'
- std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0);
- std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos;
- std::string simple = mOSStringSimple.substr(0, idx2);
- if (simple.length() > 0)
- mOSStringSimple = simple;
- }
- }
- else
- {
- mOSStringSimple.append("Unable to collect OS info");
- mOSString = mOSStringSimple;
- }
-
-#endif
-
- std::stringstream dotted_version_string;
- dotted_version_string << mMajorVer << "." << mMinorVer << "." << mBuild;
- mOSVersionString.append(dotted_version_string.str());
-
- mOSBitness = is64Bit() ? 64 : 32;
- LL_INFOS("LLOSInfo") << "OS bitness: " << mOSBitness << LL_ENDL;
-}
-
-#ifndef LL_WINDOWS
-// static
-long LLOSInfo::getMaxOpenFiles()
-{
- const long OPEN_MAX_GUESS = 256;
-
-#ifdef OPEN_MAX
- static long open_max = OPEN_MAX;
-#else
- static long open_max = 0;
-#endif
-
- if (0 == open_max)
- {
- // First time through.
- errno = 0;
- if ( (open_max = sysconf(_SC_OPEN_MAX)) < 0)
- {
- if (0 == errno)
- {
- // Indeterminate.
- open_max = OPEN_MAX_GUESS;
- }
- else
- {
- LL_ERRS() << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << LL_ENDL;
- }
- }
- }
- return open_max;
-}
-#endif
-
-void LLOSInfo::stream(std::ostream& s) const
-{
- s << mOSString;
-}
-
-const std::string& LLOSInfo::getOSString() const
-{
- return mOSString;
-}
-
-const std::string& LLOSInfo::getOSStringSimple() const
-{
- return mOSStringSimple;
-}
-
-const std::string& LLOSInfo::getOSVersionString() const
-{
- return mOSVersionString;
-}
-
-const S32 LLOSInfo::getOSBitness() const
-{
- return mOSBitness;
-}
-
-//static
-U32 LLOSInfo::getProcessVirtualSizeKB()
-{
- U32 virtual_size = 0;
-#if LL_LINUX
-# define STATUS_SIZE 2048
- LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb");
- if (status_filep)
- {
- S32 numRead = 0;
- char buff[STATUS_SIZE]; /* Flawfinder: ignore */
-
- size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep);
- buff[nbytes] = '\0';
-
- // All these guys return numbers in KB
- char *memp = strstr(buff, "VmSize:");
- if (memp)
- {
- numRead += sscanf(memp, "%*s %u", &virtual_size);
- }
- fclose(status_filep);
- }
-#endif
- return virtual_size;
-}
-
-//static
-U32 LLOSInfo::getProcessResidentSizeKB()
-{
- U32 resident_size = 0;
-#if LL_LINUX
- LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb");
- if (status_filep != NULL)
- {
- S32 numRead = 0;
- char buff[STATUS_SIZE]; /* Flawfinder: ignore */
-
- size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep);
- buff[nbytes] = '\0';
-
- // All these guys return numbers in KB
- char *memp = strstr(buff, "VmRSS:");
- if (memp)
- {
- numRead += sscanf(memp, "%*s %u", &resident_size);
- }
- fclose(status_filep);
- }
-#endif
- return resident_size;
-}
-
-//static
-bool LLOSInfo::is64Bit()
-{
-#if LL_WINDOWS
-#if defined(_WIN64)
- return true;
-#elif defined(_WIN32)
- // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate
- bool f64 = false;
- return IsWow64Process(GetCurrentProcess(), &f64) && f64;
-#else
- return false;
-#endif
-#else // ! LL_WINDOWS
- // we only build a 64-bit mac viewer and currently we don't build for linux at all
- return true;
-#endif
-}
-
-LLCPUInfo::LLCPUInfo()
-{
- std::ostringstream out;
- LLProcessorInfo proc;
- // proc.WriteInfoTextFile("procInfo.txt");
- mHasSSE = proc.hasSSE();
- mHasSSE2 = proc.hasSSE2();
- mHasSSE3 = proc.hasSSE3();
- mHasSSE3S = proc.hasSSE3S();
- mHasSSE41 = proc.hasSSE41();
- mHasSSE42 = proc.hasSSE42();
- mHasSSE4a = proc.hasSSE4a();
- mHasAltivec = proc.hasAltivec();
- mCPUMHz = (F64)proc.getCPUFrequency();
- mFamily = proc.getCPUFamilyName();
- mCPUString = "Unknown";
-
- out << proc.getCPUBrandName();
- if (200 < mCPUMHz && mCPUMHz < 10000) // *NOTE: cpu speed is often way wrong, do a sanity check
- {
- out << " (" << mCPUMHz << " MHz)";
- }
- mCPUString = out.str();
- LLStringUtil::trim(mCPUString);
-
- if (mHasSSE)
- {
- mSSEVersions.append("1");
- }
- if (mHasSSE2)
- {
- mSSEVersions.append("2");
- }
- if (mHasSSE3)
- {
- mSSEVersions.append("3");
- }
- if (mHasSSE3S)
- {
- mSSEVersions.append("3S");
- }
- if (mHasSSE41)
- {
- mSSEVersions.append("4.1");
- }
- if (mHasSSE42)
- {
- mSSEVersions.append("4.2");
- }
- if (mHasSSE4a)
- {
- mSSEVersions.append("4a");
- }
-}
-
-bool LLCPUInfo::hasAltivec() const
-{
- return mHasAltivec;
-}
-
-bool LLCPUInfo::hasSSE() const
-{
- return mHasSSE;
-}
-
-bool LLCPUInfo::hasSSE2() const
-{
- return mHasSSE2;
-}
-
-bool LLCPUInfo::hasSSE3() const
-{
- return mHasSSE3;
-}
-
-bool LLCPUInfo::hasSSE3S() const
-{
- return mHasSSE3S;
-}
-
-bool LLCPUInfo::hasSSE41() const
-{
- return mHasSSE41;
-}
-
-bool LLCPUInfo::hasSSE42() const
-{
- return mHasSSE42;
-}
-
-bool LLCPUInfo::hasSSE4a() const
-{
- return mHasSSE4a;
-}
-
-F64 LLCPUInfo::getMHz() const
-{
- return mCPUMHz;
-}
-
-std::string LLCPUInfo::getCPUString() const
-{
- return mCPUString;
-}
-
-const LLSD& LLCPUInfo::getSSEVersions() const
-{
- return mSSEVersions;
-}
-
-void LLCPUInfo::stream(std::ostream& s) const
-{
- // gather machine information.
- s << LLProcessorInfo().getCPUFeatureDescription();
-
- // These are interesting as they reflect our internal view of the
- // CPU's attributes regardless of platform
- s << "->mHasSSE: " << (U32)mHasSSE << std::endl;
- s << "->mHasSSE2: " << (U32)mHasSSE2 << std::endl;
- s << "->mHasSSE3: " << (U32)mHasSSE3 << std::endl;
- s << "->mHasSSE3S: " << (U32)mHasSSE3S << std::endl;
- s << "->mHasSSE41: " << (U32)mHasSSE41 << std::endl;
- s << "->mHasSSE42: " << (U32)mHasSSE42 << std::endl;
- s << "->mHasSSE4a: " << (U32)mHasSSE4a << std::endl;
- s << "->mHasAltivec: " << (U32)mHasAltivec << std::endl;
- s << "->mCPUMHz: " << mCPUMHz << std::endl;
- s << "->mCPUString: " << mCPUString << std::endl;
-}
-
-// Helper class for LLMemoryInfo: accumulate stats in the form we store for
-// LLMemoryInfo::getStatsMap().
-class Stats
-{
-public:
- Stats():
- mStats(LLSD::emptyMap())
- {}
-
- // Store every integer type as LLSD::Integer.
- template <class T>
- void add(const LLSD::String& name, const T& value,
- typename boost::enable_if<boost::is_integral<T> >::type* = 0)
- {
- mStats[name] = LLSD::Integer(value);
- }
-
- // Store every floating-point type as LLSD::Real.
- template <class T>
- void add(const LLSD::String& name, const T& value,
- typename boost::enable_if<boost::is_float<T> >::type* = 0)
- {
- mStats[name] = LLSD::Real(value);
- }
-
- // Hope that LLSD::Date values are sufficiently unambiguous.
- void add(const LLSD::String& name, const LLSD::Date& value)
- {
- mStats[name] = value;
- }
-
- LLSD get() const { return mStats; }
-
-private:
- LLSD mStats;
-};
-
-LLMemoryInfo::LLMemoryInfo()
-{
- refresh();
-}
-
-#if LL_WINDOWS
-static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB)
-{
- // Moved this here from llfloaterabout.cpp
-
- //! \bug
- // For some reason, the reported amount of memory is always wrong.
- // The original adjustment assumes it's always off by one meg, however
- // errors of as much as 2520 KB have been observed in the value
- // returned from the GetMemoryStatusEx function. Here we keep the
- // original adjustment from llfoaterabout.cpp until this can be
- // fixed somehow.
- inKB += U32Megabytes(1);
-
- return inKB;
-}
-#endif
-
-#if LL_DARWIN
-// static
-U32Kilobytes LLMemoryInfo::getHardwareMemSize()
-{
- // This might work on Linux as well. Someone check...
- uint64_t phys = 0;
- int mib[2] = { CTL_HW, HW_MEMSIZE };
-
- size_t len = sizeof(phys);
- sysctl(mib, 2, &phys, &len, NULL, 0);
-
- return U64Bytes(phys);
-}
-#endif
-
-U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const
-{
-#if LL_WINDOWS
- return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger()));
-
-#elif LL_DARWIN
- return getHardwareMemSize();
-
-#elif LL_LINUX
- U64 phys = 0;
- phys = (U64)(getpagesize()) * (U64)(get_phys_pages());
- return U64Bytes(phys);
-
-#else
- return 0;
-
-#endif
-}
-
-//static
-void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb)
-{
-#if LL_WINDOWS
- // Sigh, this shouldn't be a static method, then we wouldn't have to
- // reload this data separately from refresh()
- LLSD statsMap(loadStatsMap());
-
- avail_physical_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger();
- avail_virtual_mem_kb = (U32Kilobytes)statsMap["Avail Virtual KB"].asInteger();
-
-#elif LL_DARWIN
- // mStatsMap is derived from vm_stat, look for (e.g.) "kb free":
- // $ vm_stat
- // Mach Virtual Memory Statistics: (page size of 4096 bytes)
- // Pages free: 462078.
- // Pages active: 142010.
- // Pages inactive: 220007.
- // Pages wired down: 159552.
- // "Translation faults": 220825184.
- // Pages copy-on-write: 2104153.
- // Pages zero filled: 167034876.
- // Pages reactivated: 65153.
- // Pageins: 2097212.
- // Pageouts: 41759.
- // Object cache: 841598 hits of 7629869 lookups (11% hit rate)
- avail_physical_mem_kb = (U32Kilobytes)-1 ;
- avail_virtual_mem_kb = (U32Kilobytes)-1 ;
-
-#elif LL_LINUX
- // mStatsMap is derived from MEMINFO_FILE:
- // $ cat /proc/meminfo
- // MemTotal: 4108424 kB
- // MemFree: 1244064 kB
- // Buffers: 85164 kB
- // Cached: 1990264 kB
- // SwapCached: 0 kB
- // Active: 1176648 kB
- // Inactive: 1427532 kB
- // Active(anon): 529152 kB
- // Inactive(anon): 15924 kB
- // Active(file): 647496 kB
- // Inactive(file): 1411608 kB
- // Unevictable: 16 kB
- // Mlocked: 16 kB
- // HighTotal: 3266316 kB
- // HighFree: 721308 kB
- // LowTotal: 842108 kB
- // LowFree: 522756 kB
- // SwapTotal: 6384632 kB
- // SwapFree: 6384632 kB
- // Dirty: 28 kB
- // Writeback: 0 kB
- // AnonPages: 528820 kB
- // Mapped: 89472 kB
- // Shmem: 16324 kB
- // Slab: 159624 kB
- // SReclaimable: 145168 kB
- // SUnreclaim: 14456 kB
- // KernelStack: 2560 kB
- // PageTables: 5560 kB
- // NFS_Unstable: 0 kB
- // Bounce: 0 kB
- // WritebackTmp: 0 kB
- // CommitLimit: 8438844 kB
- // Committed_AS: 1271596 kB
- // VmallocTotal: 122880 kB
- // VmallocUsed: 65252 kB
- // VmallocChunk: 52356 kB
- // HardwareCorrupted: 0 kB
- // HugePages_Total: 0
- // HugePages_Free: 0
- // HugePages_Rsvd: 0
- // HugePages_Surp: 0
- // Hugepagesize: 2048 kB
- // DirectMap4k: 434168 kB
- // DirectMap2M: 477184 kB
- // (could also run 'free', but easier to read a file than run a program)
- avail_physical_mem_kb = (U32Kilobytes)-1 ;
- avail_virtual_mem_kb = (U32Kilobytes)-1 ;
-
-#else
- //do not know how to collect available memory info for other systems.
- //leave it blank here for now.
-
- avail_physical_mem_kb = (U32Kilobytes)-1 ;
- avail_virtual_mem_kb = (U32Kilobytes)-1 ;
-#endif
-}
-
-void LLMemoryInfo::stream(std::ostream& s) const
-{
- // We want these memory stats to be easy to grep from the log, along with
- // the timestamp. So preface each line with the timestamp and a
- // distinctive marker. Without that, we'd have to search the log for the
- // introducer line, then read subsequent lines, etc...
- std::string pfx(LLError::utcTime() + " <mem> ");
-
- // Max key length
- size_t key_width(0);
- for (const auto& [key, value] : inMap(mStatsMap))
- {
- size_t len(key.length());
- if (len > key_width)
- {
- key_width = len;
- }
- }
-
- // Now stream stats
- for (const auto& [key, value] : inMap(mStatsMap))
- {
- s << pfx << std::setw(narrow<size_t>(key_width+1)) << (key + ':') << ' ';
- if (value.isInteger())
- s << std::setw(12) << value.asInteger();
- else if (value.isReal())
- s << std::fixed << std::setprecision(1) << value.asReal();
- else if (value.isDate())
- value.asDate().toStream(s);
- else
- s << value; // just use default LLSD formatting
- s << std::endl;
- }
-}
-
-LLSD LLMemoryInfo::getStatsMap() const
-{
- return mStatsMap;
-}
-
-LLMemoryInfo& LLMemoryInfo::refresh()
-{
- LL_PROFILE_ZONE_SCOPED
- mStatsMap = loadStatsMap();
-
- LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n";
- LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT);
- LL_ENDL;
-
- return *this;
-}
-
-LLSD LLMemoryInfo::loadStatsMap()
-{
- LL_PROFILE_ZONE_SCOPED;
-
- // This implementation is derived from stream() code (as of 2011-06-29).
- Stats stats;
-
- // associate timestamp for analysis over time
- stats.add("timestamp", LLDate::now());
-
-#if LL_WINDOWS
- MEMORYSTATUSEX state;
- state.dwLength = sizeof(state);
- GlobalMemoryStatusEx(&state);
-
- DWORDLONG div = 1024;
-
- stats.add("Percent Memory use", state.dwMemoryLoad/div);
- stats.add("Total Physical KB", state.ullTotalPhys/div);
- stats.add("Avail Physical KB", state.ullAvailPhys/div);
- stats.add("Total page KB", state.ullTotalPageFile/div);
- stats.add("Avail page KB", state.ullAvailPageFile/div);
- stats.add("Total Virtual KB", state.ullTotalVirtual/div);
- stats.add("Avail Virtual KB", state.ullAvailVirtual/div);
-
- // SL-12122 - Call to GetPerformanceInfo() was removed here. Took
- // on order of 10 ms, causing unacceptable frame time spike every
- // second, and results were never used. If this is needed in the
- // future, must find a way to avoid frame time impact (e.g. move
- // to another thread, call much less often).
-
- PROCESS_MEMORY_COUNTERS_EX pmem;
- pmem.cb = sizeof(pmem);
- // GetProcessMemoryInfo() is documented to accept either
- // PROCESS_MEMORY_COUNTERS* or PROCESS_MEMORY_COUNTERS_EX*, presumably
- // using the redundant size info to distinguish. But its prototype
- // specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a
- // classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the
- // pointer.
- GetProcessMemoryInfo(GetCurrentProcess(), PPROCESS_MEMORY_COUNTERS(&pmem), sizeof(pmem));
-
- stats.add("Page Fault Count", pmem.PageFaultCount);
- stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div);
- stats.add("WorkingSetSize KB", pmem.WorkingSetSize/div);
- stats.add("QutaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage/div);
- stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage/div);
- stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/div);
- stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage/div);
- stats.add("PagefileUsage KB", pmem.PagefileUsage/div);
- stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage/div);
- stats.add("PrivateUsage KB", pmem.PrivateUsage/div);
-
-#elif LL_DARWIN
-
- const vm_size_t pagekb(vm_page_size / 1024);
-
- //
- // Collect the vm_stat's
- //
-
- {
- vm_statistics64_data_t vmstat;
- mach_msg_type_number_t vmstatCount = HOST_VM_INFO64_COUNT;
-
- if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) &vmstat, &vmstatCount) != KERN_SUCCESS)
- {
- LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL;
- }
- else
- {
- stats.add("Pages free KB", pagekb * vmstat.free_count);
- stats.add("Pages active KB", pagekb * vmstat.active_count);
- stats.add("Pages inactive KB", pagekb * vmstat.inactive_count);
- stats.add("Pages wired KB", pagekb * vmstat.wire_count);
-
- stats.add("Pages zero fill", vmstat.zero_fill_count);
- stats.add("Page reactivations", vmstat.reactivations);
- stats.add("Page-ins", vmstat.pageins);
- stats.add("Page-outs", vmstat.pageouts);
-
- stats.add("Faults", vmstat.faults);
- stats.add("Faults copy-on-write", vmstat.cow_faults);
-
- stats.add("Cache lookups", vmstat.lookups);
- stats.add("Cache hits", vmstat.hits);
-
- stats.add("Page purgeable count", vmstat.purgeable_count);
- stats.add("Page purges", vmstat.purges);
-
- stats.add("Page speculative reads", vmstat.speculative_count);
- }
- }
-
- //
- // Collect the misc task info
- //
-
- {
- task_events_info_data_t taskinfo;
- unsigned taskinfoSize = sizeof(taskinfo);
-
- if (task_info(mach_task_self(), TASK_EVENTS_INFO, (task_info_t) &taskinfo, &taskinfoSize) != KERN_SUCCESS)
- {
- LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL;
- }
- else
- {
- stats.add("Task page-ins", taskinfo.pageins);
- stats.add("Task copy-on-write faults", taskinfo.cow_faults);
- stats.add("Task messages sent", taskinfo.messages_sent);
- stats.add("Task messages received", taskinfo.messages_received);
- stats.add("Task mach system call count", taskinfo.syscalls_mach);
- stats.add("Task unix system call count", taskinfo.syscalls_unix);
- stats.add("Task context switch count", taskinfo.csw);
- }
- }
-
- //
- // Collect the basic task info
- //
-
- {
- mach_task_basic_info_data_t taskinfo;
- mach_msg_type_number_t task_count = MACH_TASK_BASIC_INFO_COUNT;
- if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t) &taskinfo, &task_count) != KERN_SUCCESS)
- {
- LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL;
- }
- else
- {
- stats.add("Basic virtual memory KB", taskinfo.virtual_size / 1024);
- stats.add("Basic resident memory KB", taskinfo.resident_size / 1024);
- stats.add("Basic max resident memory KB", taskinfo.resident_size_max / 1024);
- stats.add("Basic new thread policy", taskinfo.policy);
- stats.add("Basic suspend count", taskinfo.suspend_count);
- }
- }
-
-#elif LL_LINUX
- std::ifstream meminfo(MEMINFO_FILE);
- if (meminfo.is_open())
- {
- // MemTotal: 4108424 kB
- // MemFree: 1244064 kB
- // Buffers: 85164 kB
- // Cached: 1990264 kB
- // SwapCached: 0 kB
- // Active: 1176648 kB
- // Inactive: 1427532 kB
- // ...
- // VmallocTotal: 122880 kB
- // VmallocUsed: 65252 kB
- // VmallocChunk: 52356 kB
- // HardwareCorrupted: 0 kB
- // HugePages_Total: 0
- // HugePages_Free: 0
- // HugePages_Rsvd: 0
- // HugePages_Surp: 0
- // Hugepagesize: 2048 kB
- // DirectMap4k: 434168 kB
- // DirectMap2M: 477184 kB
-
- // Intentionally don't pass the boost::no_except flag. This
- // boost::regex object is constructed with a string literal, so it
- // should be valid every time. If it becomes invalid, we WANT an
- // exception, hopefully even before the dev checks in.
- boost::regex stat_rx("(.+): +([0-9]+)( kB)?");
- boost::smatch matched;
-
- std::string line;
- while (std::getline(meminfo, line))
- {
- LL_DEBUGS("LLMemoryInfo") << line << LL_ENDL;
- if (ll_regex_match(line, matched, stat_rx))
- {
- // e.g. "MemTotal: 4108424 kB"
- LLSD::String key(matched[1].first, matched[1].second);
- LLSD::String value_str(matched[2].first, matched[2].second);
- LLSD::Integer value(0);
- try
- {
- value = boost::lexical_cast<LLSD::Integer>(value_str);
- }
- catch (const boost::bad_lexical_cast&)
- {
- LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str
- << "' in " << MEMINFO_FILE << " line: "
- << line << LL_ENDL;
- continue;
- }
- // Store this statistic.
- stats.add(key, value);
- }
- else
- {
- LL_WARNS("LLMemoryInfo") << "unrecognized " << MEMINFO_FILE << " line: "
- << line << LL_ENDL;
- }
- }
- }
- else
- {
- LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL;
- }
-
-#else
- LL_WARNS("LLMemoryInfo") << "Unknown system; unable to collect memory information" << LL_ENDL;
-
-#endif
-
- return stats.get();
-}
-
-std::ostream& operator<<(std::ostream& s, const LLOSInfo& info)
-{
- info.stream(s);
- return s;
-}
-
-std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info)
-{
- info.stream(s);
- return s;
-}
-
-std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info)
-{
- info.stream(s);
- return s;
-}
-
-class FrameWatcher
-{
-public:
- FrameWatcher():
- // Hooking onto the "mainloop" event pump gets us one call per frame.
- mConnection(LLEventPumps::instance()
- .obtain("mainloop")
- .listen("FrameWatcher", boost::bind(&FrameWatcher::tick, this, _1))),
- // Initializing mSampleStart to an invalid timestamp alerts us to skip
- // trying to compute framerate on the first call.
- mSampleStart(-1),
- // Initializing mSampleEnd to 0 ensures that we treat the first call
- // as the completion of a sample window.
- mSampleEnd(0),
- mFrames(0),
- // Both MEM_INFO_WINDOW and MEM_INFO_THROTTLE are in seconds. We need
- // the number of integer MEM_INFO_THROTTLE sample slots that will fit
- // in MEM_INFO_WINDOW. Round up.
- mSamples(int((MEM_INFO_WINDOW / MEM_INFO_THROTTLE) + 0.7)),
- // Initializing to F32_MAX means that the first real frame will become
- // the slowest ever, which sounds like a good idea.
- mSlowest(F32_MAX)
- {}
-
- bool tick(const LLSD&)
- {
- F32 timestamp(mTimer.getElapsedTimeF32());
-
- // Count this frame in the interval just completed.
- ++mFrames;
-
- // Have we finished a sample window yet?
- if (timestamp < mSampleEnd)
- {
- // no, just keep waiting
- return false;
- }
-
- // Set up for next sample window. Capture values for previous frame in
- // local variables and reset data members.
- U32 frames(mFrames);
- F32 sampleStart(mSampleStart);
- // No frames yet in next window
- mFrames = 0;
- // which starts right now
- mSampleStart = timestamp;
- // and ends MEM_INFO_THROTTLE seconds in the future
- mSampleEnd = mSampleStart + MEM_INFO_THROTTLE;
-
- // On the very first call, that's all we can do, no framerate
- // computation is possible.
- if (sampleStart < 0)
- {
- return false;
- }
-
- // How long did this actually take? As framerate slows, the duration
- // of the frame we just finished could push us WELL beyond our desired
- // sample window size.
- F32 elapsed(timestamp - sampleStart);
- F32 framerate(frames/elapsed);
-
- // Remember previous slowest framerate because we're just about to
- // update it.
- F32 slowest(mSlowest);
- // Remember previous number of samples.
- boost::circular_buffer<F32>::size_type prevSize(mSamples.size());
-
- // Capture new framerate in our samples buffer. Once the buffer is
- // full (after MEM_INFO_WINDOW seconds), this will displace the oldest
- // sample. ("So they all rolled over, and one fell out...")
- mSamples.push_back(framerate);
-
- // Calculate the new minimum framerate. I know of no way to update a
- // rolling minimum without ever rescanning the buffer. But since there
- // are only a few tens of items in this buffer, rescanning it is
- // probably cheaper (and certainly easier to reason about) than
- // attempting to optimize away some of the scans.
- mSlowest = framerate; // pick an arbitrary entry to start
- for (boost::circular_buffer<F32>::const_iterator si(mSamples.begin()), send(mSamples.end());
- si != send; ++si)
- {
- if (*si < mSlowest)
- {
- mSlowest = *si;
- }
- }
-
- // We're especially interested in memory as framerate drops. Only log
- // when framerate drops below the slowest framerate we remember.
- // (Should always be true for the end of the very first sample
- // window.)
- if (framerate >= slowest)
- {
- return false;
- }
- // Congratulations, we've hit a new low. :-P
-
- LL_INFOS("FrameWatcher") << ' ';
- if (! prevSize)
- {
- LL_CONT << "initial framerate ";
- }
- else
- {
- LL_CONT << "slowest framerate for last " << int(prevSize * MEM_INFO_THROTTLE)
- << " seconds ";
- }
-
- auto precision = LL_CONT.precision();
-
- LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n'
- << LLMemoryInfo();
-
- LL_CONT.precision(precision);
- LL_CONT << LL_ENDL;
- return false;
- }
-
-private:
- // Storing the connection in an LLTempBoundListener ensures it will be
- // disconnected when we're destroyed.
- LLTempBoundListener mConnection;
- // Track elapsed time
- LLTimer mTimer;
- // Some of what you see here is in fact redundant with functionality you
- // can get from LLTimer. Unfortunately the LLTimer API is missing the
- // feature we need: has at least the stated interval elapsed, and if so,
- // exactly how long has passed? So we have to do it by hand, sigh.
- // Time at start, end of sample window
- F32 mSampleStart, mSampleEnd;
- // Frames this sample window
- U32 mFrames;
- // Sliding window of framerate samples
- boost::circular_buffer<F32> mSamples;
- // Slowest framerate in mSamples
- F32 mSlowest;
-};
-
-// Need an instance of FrameWatcher before it does any good
-static FrameWatcher sFrameWatcher;
-
-bool gunzip_file(const std::string& srcfile, const std::string& dstfile)
-{
- std::string tmpfile;
- const S32 UNCOMPRESS_BUFFER_SIZE = 32768;
- bool retval = false;
- gzFile src = NULL;
- U8 buffer[UNCOMPRESS_BUFFER_SIZE];
- LLFILE *dst = NULL;
- S32 bytes = 0;
- tmpfile = dstfile + ".t";
-#ifdef LL_WINDOWS
- llutf16string utf16filename = utf8str_to_utf16str(srcfile);
- src = gzopen_w(utf16filename.c_str(), "rb");
-#else
- src = gzopen(srcfile.c_str(), "rb");
-#endif
- if (! src) goto err;
- dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */
- if (! dst) goto err;
- do
- {
- bytes = gzread(src, buffer, UNCOMPRESS_BUFFER_SIZE);
- size_t nwrit = fwrite(buffer, sizeof(U8), bytes, dst);
- if (nwrit < (size_t) bytes)
- {
- LL_WARNS() << "Short write on " << tmpfile << ": Wrote " << nwrit << " of " << bytes << " bytes." << LL_ENDL;
- goto err;
- }
- } while(gzeof(src) == 0);
- fclose(dst);
- dst = NULL;
-#if LL_WINDOWS
- // Rename in windows needs the dstfile to not exist.
- LLFile::remove(dstfile, ENOENT);
-#endif
- if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */
- retval = true;
-err:
- if (src != NULL) gzclose(src);
- if (dst != NULL) fclose(dst);
- return retval;
-}
-
-bool gzip_file(const std::string& srcfile, const std::string& dstfile)
-{
- const S32 COMPRESS_BUFFER_SIZE = 32768;
- std::string tmpfile;
- bool retval = false;
- U8 buffer[COMPRESS_BUFFER_SIZE];
- gzFile dst = NULL;
- LLFILE *src = NULL;
- S32 bytes = 0;
- tmpfile = dstfile + ".t";
-
-#ifdef LL_WINDOWS
- llutf16string utf16filename = utf8str_to_utf16str(tmpfile);
- dst = gzopen_w(utf16filename.c_str(), "wb");
-#else
- dst = gzopen(tmpfile.c_str(), "wb");
-#endif
-
- if (! dst) goto err;
- src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */
- if (! src) goto err;
-
- while ((bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE, src)) > 0)
- {
- if (gzwrite(dst, buffer, bytes) <= 0)
- {
- LL_WARNS() << "gzwrite failed: " << gzerror(dst, NULL) << LL_ENDL;
- goto err;
- }
- }
-
- if (ferror(src))
- {
- LL_WARNS() << "Error reading " << srcfile << LL_ENDL;
- goto err;
- }
-
- gzclose(dst);
- dst = NULL;
-#if LL_WINDOWS
- // Rename in windows needs the dstfile to not exist.
- LLFile::remove(dstfile);
-#endif
- if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */
- retval = true;
- err:
- if (src != NULL) fclose(src);
- if (dst != NULL) gzclose(dst);
- return retval;
-}
+/** + * @file llsys.cpp + * @brief Implementation of the basic system query functions. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +#include "linden_common.h" + +#include "llsys.h" + +#include <iostream> +#ifdef LL_USESYSTEMLIBS +# include <zlib.h> +#else +# include "zlib-ng/zlib.h" +#endif + +#include "llprocessor.h" +#include "llerrorcontrol.h" +#include "llevents.h" +#include "llformat.h" +#include "llregex.h" +#include "lltimer.h" +#include "llsdserialize.h" +#include "llsdutil.h" +#include <boost/bind.hpp> +#include <boost/circular_buffer.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/range.hpp> +#include <boost/utility/enable_if.hpp> +#include <boost/type_traits/is_integral.hpp> +#include <boost/type_traits/is_float.hpp> +#include "llfasttimer.h" + +using namespace llsd; + +#if LL_WINDOWS +# include "llwin32headerslean.h" +# include <psapi.h> // GetPerformanceInfo() et al. +# include <VersionHelpers.h> +#elif LL_DARWIN +# include "llsys_objc.h" +# include <errno.h> +# include <sys/sysctl.h> +# include <sys/utsname.h> +# include <stdint.h> +# include <CoreServices/CoreServices.h> +# include <stdexcept> +# include <mach/host_info.h> +# include <mach/mach_host.h> +# include <mach/task.h> +# include <mach/task_info.h> +#elif LL_LINUX +# include <errno.h> +# include <sys/utsname.h> +# include <unistd.h> +# include <sys/sysinfo.h> +# include <stdexcept> +const char MEMINFO_FILE[] = "/proc/meminfo"; +# include <gnu/libc-version.h> +#endif + +LLCPUInfo gSysCPU; + +// Don't log memory info any more often than this. It also serves as our +// framerate sample size. +static const F32 MEM_INFO_THROTTLE = 20; +// Sliding window of samples. We intentionally limit the length of time we +// remember "the slowest" framerate because framerate is very slow at login. +// If we only triggered FrameWatcher logging when the session framerate +// dropped below the login framerate, we'd have very little additional data. +static const F32 MEM_INFO_WINDOW = 10*60; + +LLOSInfo::LLOSInfo() : + mMajorVer(0), mMinorVer(0), mBuild(0), mOSVersionString("") +{ + +#if LL_WINDOWS + + if (IsWindows10OrGreater()) + { + mMajorVer = 10; + mMinorVer = 0; + mOSStringSimple = "Microsoft Windows 10 "; + } + else if (IsWindows8Point1OrGreater()) + { + mMajorVer = 6; + mMinorVer = 3; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2012 R2 "; + } + else + { + mOSStringSimple = "Microsoft Windows 8.1 "; + } + } + else if (IsWindows8OrGreater()) + { + mMajorVer = 6; + mMinorVer = 2; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2012 "; + } + else + { + mOSStringSimple = "Microsoft Windows 8 "; + } + } + else if (IsWindows7SP1OrGreater()) + { + mMajorVer = 6; + mMinorVer = 1; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2008 R2 SP1 "; + } + else + { + mOSStringSimple = "Microsoft Windows 7 SP1 "; + } + } + else if (IsWindows7OrGreater()) + { + mMajorVer = 6; + mMinorVer = 1; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2008 R2 "; + } + else + { + mOSStringSimple = "Microsoft Windows 7 "; + } + } + else if (IsWindowsVistaSP2OrGreater()) + { + mMajorVer = 6; + mMinorVer = 0; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2008 SP2 "; + } + else + { + mOSStringSimple = "Microsoft Windows Vista SP2 "; + } + } + else + { + mOSStringSimple = "Unsupported Windows version "; + } + + ///get native system info if available.. + typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); ///function pointer for loading GetNativeSystemInfo + SYSTEM_INFO si; //System Info object file contains architecture info + PGNSI pGNSI; //pointer object + ZeroMemory(&si, sizeof(SYSTEM_INFO)); //zero out the memory in information + pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); //load kernel32 get function + if (NULL != pGNSI) //check if it has failed + pGNSI(&si); //success + else + GetSystemInfo(&si); //if it fails get regular system info + //(Warning: If GetSystemInfo it may result in incorrect information in a WOW64 machine, if the kernel fails to load) + + // Try calling GetVersionEx using the OSVERSIONINFOEX structure. + OSVERSIONINFOEX osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if (GetVersionEx((OSVERSIONINFO *)&osvi)) + { + mBuild = osvi.dwBuildNumber & 0xffff; + } + else + { + // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if (GetVersionEx((OSVERSIONINFO *)&osvi)) + { + mBuild = osvi.dwBuildNumber & 0xffff; + } + } + + S32 ubr = 0; // Windows 10 Update Build Revision, can be retrieved from a registry + if (mMajorVer == 10) + { + DWORD cbData(sizeof(DWORD)); + DWORD data(0); + HKEY key; + LSTATUS ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key); + if (ERROR_SUCCESS == ret_code) + { + ret_code = RegQueryValueExW(key, L"UBR", 0, NULL, reinterpret_cast<LPBYTE>(&data), &cbData); + if (ERROR_SUCCESS == ret_code) + { + ubr = data; + } + } + + if (mBuild >= 22000) + { + // At release Windows 11 version was 10.0.22000.194 + // Windows 10 version was 10.0.19043.1266 + // There is no warranty that Win10 build won't increase, + // so until better solution is found or Microsoft updates + // SDK with IsWindows11OrGreater(), indicate "10/11" + // + // Current alternatives: + // Query WMI's Win32_OperatingSystem for OS string. Slow + // and likely to return 'compatibility' string. + // Check presence of dlls/libs or may be their version. + mOSStringSimple = "Microsoft Windows 10/11 "; + } + } + + //msdn microsoft finds 32 bit and 64 bit flavors this way.. + //http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx (example code that contains quite a few more flavors + //of windows than this code does (in case it is needed for the future) + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) //check for 64 bit + { + mOSStringSimple += "64-bit "; + } + else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) + { + mOSStringSimple += "32-bit "; + } + + mOSString = mOSStringSimple; + if (mBuild > 0) + { + mOSString += llformat("(Build %d", mBuild); + if (ubr > 0) + { + mOSString += llformat(".%d", ubr); + } + mOSString += ")"; + } + + LLStringUtil::trim(mOSStringSimple); + LLStringUtil::trim(mOSString); + +#elif LL_DARWIN + + // Initialize mOSStringSimple to something like: + // "macOS 10.13.1" + { + const char * DARWIN_PRODUCT_NAME = "macOS"; + + int64_t major_version, minor_version, bugfix_version = 0; + + if (LLGetDarwinOSInfo(major_version, minor_version, bugfix_version)) + { + mMajorVer = major_version; + mMinorVer = minor_version; + mBuild = bugfix_version; + + std::stringstream os_version_string; + os_version_string << DARWIN_PRODUCT_NAME << " " << mMajorVer << "." << mMinorVer << "." << mBuild; + + // Put it in the OS string we are compiling + mOSStringSimple.append(os_version_string.str()); + } + else + { + mOSStringSimple.append("Unable to collect OS info"); + } + } + + // Initialize mOSString to something like: + // "macOS 10.13.1 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386" + struct utsname un; + if(uname(&un) != -1) + { + mOSString = mOSStringSimple; + mOSString.append(" "); + mOSString.append(un.sysname); + mOSString.append(" "); + mOSString.append(un.release); + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + } + else + { + mOSString = mOSStringSimple; + } + +#elif LL_LINUX + + struct utsname un; + if(uname(&un) != -1) + { + mOSStringSimple.append(un.sysname); + mOSStringSimple.append(" "); + mOSStringSimple.append(un.release); + + mOSString = mOSStringSimple; + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + + // Simplify 'Simple' + std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); + if (ostype == "Linux") + { + // Only care about major and minor Linux versions, truncate at second '.' + std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); + std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; + std::string simple = mOSStringSimple.substr(0, idx2); + if (simple.length() > 0) + mOSStringSimple = simple; + } + } + else + { + mOSStringSimple.append("Unable to collect OS info"); + mOSString = mOSStringSimple; + } + + const char OS_VERSION_MATCH_EXPRESSION[] = "([0-9]+)\\.([0-9]+)(\\.([0-9]+))?"; + boost::regex os_version_parse(OS_VERSION_MATCH_EXPRESSION); + boost::smatch matched; + + std::string glibc_version(gnu_get_libc_version()); + if ( ll_regex_match(glibc_version, matched, os_version_parse) ) + { + LL_INFOS("AppInit") << "Using glibc version '" << glibc_version << "' as OS version" << LL_ENDL; + + std::string version_value; + + if ( matched[1].matched ) // Major version + { + version_value.assign(matched[1].first, matched[1].second); + if (sscanf(version_value.c_str(), "%d", &mMajorVer) != 1) + { + LL_WARNS("AppInit") << "failed to parse major version '" << version_value << "' as a number" << LL_ENDL; + } + } + else + { + LL_ERRS("AppInit") + << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION + << "' returned true, but major version [1] did not match" + << LL_ENDL; + } + + if ( matched[2].matched ) // Minor version + { + version_value.assign(matched[2].first, matched[2].second); + if (sscanf(version_value.c_str(), "%d", &mMinorVer) != 1) + { + LL_ERRS("AppInit") << "failed to parse minor version '" << version_value << "' as a number" << LL_ENDL; + } + } + else + { + LL_ERRS("AppInit") + << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION + << "' returned true, but minor version [1] did not match" + << LL_ENDL; + } + + if ( matched[4].matched ) // Build version (optional) - note that [3] includes the '.' + { + version_value.assign(matched[4].first, matched[4].second); + if (sscanf(version_value.c_str(), "%d", &mBuild) != 1) + { + LL_ERRS("AppInit") << "failed to parse build version '" << version_value << "' as a number" << LL_ENDL; + } + } + else + { + LL_INFOS("AppInit") + << "OS build version not provided; using zero" + << LL_ENDL; + } + } + else + { + LL_WARNS("AppInit") << "glibc version '" << glibc_version << "' cannot be parsed to three numbers; using all zeros" << LL_ENDL; + } + +#else + + struct utsname un; + if(uname(&un) != -1) + { + mOSStringSimple.append(un.sysname); + mOSStringSimple.append(" "); + mOSStringSimple.append(un.release); + + mOSString = mOSStringSimple; + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + + // Simplify 'Simple' + std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); + if (ostype == "Linux") + { + // Only care about major and minor Linux versions, truncate at second '.' + std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); + std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; + std::string simple = mOSStringSimple.substr(0, idx2); + if (simple.length() > 0) + mOSStringSimple = simple; + } + } + else + { + mOSStringSimple.append("Unable to collect OS info"); + mOSString = mOSStringSimple; + } + +#endif + + std::stringstream dotted_version_string; + dotted_version_string << mMajorVer << "." << mMinorVer << "." << mBuild; + mOSVersionString.append(dotted_version_string.str()); + + mOSBitness = is64Bit() ? 64 : 32; + LL_INFOS("LLOSInfo") << "OS bitness: " << mOSBitness << LL_ENDL; +} + +#ifndef LL_WINDOWS +// static +long LLOSInfo::getMaxOpenFiles() +{ + const long OPEN_MAX_GUESS = 256; + +#ifdef OPEN_MAX + static long open_max = OPEN_MAX; +#else + static long open_max = 0; +#endif + + if (0 == open_max) + { + // First time through. + errno = 0; + if ( (open_max = sysconf(_SC_OPEN_MAX)) < 0) + { + if (0 == errno) + { + // Indeterminate. + open_max = OPEN_MAX_GUESS; + } + else + { + LL_ERRS() << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << LL_ENDL; + } + } + } + return open_max; +} +#endif + +void LLOSInfo::stream(std::ostream& s) const +{ + s << mOSString; +} + +const std::string& LLOSInfo::getOSString() const +{ + return mOSString; +} + +const std::string& LLOSInfo::getOSStringSimple() const +{ + return mOSStringSimple; +} + +const std::string& LLOSInfo::getOSVersionString() const +{ + return mOSVersionString; +} + +const S32 LLOSInfo::getOSBitness() const +{ + return mOSBitness; +} + +//static +U32 LLOSInfo::getProcessVirtualSizeKB() +{ + U32 virtual_size = 0; +#if LL_LINUX +# define STATUS_SIZE 2048 + LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); + if (status_filep) + { + S32 numRead = 0; + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + + size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); + buff[nbytes] = '\0'; + + // All these guys return numbers in KB + char *memp = strstr(buff, "VmSize:"); + if (memp) + { + numRead += sscanf(memp, "%*s %u", &virtual_size); + } + fclose(status_filep); + } +#endif + return virtual_size; +} + +//static +U32 LLOSInfo::getProcessResidentSizeKB() +{ + U32 resident_size = 0; +#if LL_LINUX + LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); + if (status_filep != NULL) + { + S32 numRead = 0; + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + + size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); + buff[nbytes] = '\0'; + + // All these guys return numbers in KB + char *memp = strstr(buff, "VmRSS:"); + if (memp) + { + numRead += sscanf(memp, "%*s %u", &resident_size); + } + fclose(status_filep); + } +#endif + return resident_size; +} + +//static +bool LLOSInfo::is64Bit() +{ +#if LL_WINDOWS +#if defined(_WIN64) + return true; +#elif defined(_WIN32) + // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate + bool f64 = false; + return IsWow64Process(GetCurrentProcess(), &f64) && f64; +#else + return false; +#endif +#else // ! LL_WINDOWS + // we only build a 64-bit mac viewer and currently we don't build for linux at all + return true; +#endif +} + +LLCPUInfo::LLCPUInfo() +{ + std::ostringstream out; + LLProcessorInfo proc; + // proc.WriteInfoTextFile("procInfo.txt"); + mHasSSE = proc.hasSSE(); + mHasSSE2 = proc.hasSSE2(); + mHasSSE3 = proc.hasSSE3(); + mHasSSE3S = proc.hasSSE3S(); + mHasSSE41 = proc.hasSSE41(); + mHasSSE42 = proc.hasSSE42(); + mHasSSE4a = proc.hasSSE4a(); + mHasAltivec = proc.hasAltivec(); + mCPUMHz = (F64)proc.getCPUFrequency(); + mFamily = proc.getCPUFamilyName(); + mCPUString = "Unknown"; + + out << proc.getCPUBrandName(); + if (200 < mCPUMHz && mCPUMHz < 10000) // *NOTE: cpu speed is often way wrong, do a sanity check + { + out << " (" << mCPUMHz << " MHz)"; + } + mCPUString = out.str(); + LLStringUtil::trim(mCPUString); + + if (mHasSSE) + { + mSSEVersions.append("1"); + } + if (mHasSSE2) + { + mSSEVersions.append("2"); + } + if (mHasSSE3) + { + mSSEVersions.append("3"); + } + if (mHasSSE3S) + { + mSSEVersions.append("3S"); + } + if (mHasSSE41) + { + mSSEVersions.append("4.1"); + } + if (mHasSSE42) + { + mSSEVersions.append("4.2"); + } + if (mHasSSE4a) + { + mSSEVersions.append("4a"); + } +} + +bool LLCPUInfo::hasAltivec() const +{ + return mHasAltivec; +} + +bool LLCPUInfo::hasSSE() const +{ + return mHasSSE; +} + +bool LLCPUInfo::hasSSE2() const +{ + return mHasSSE2; +} + +bool LLCPUInfo::hasSSE3() const +{ + return mHasSSE3; +} + +bool LLCPUInfo::hasSSE3S() const +{ + return mHasSSE3S; +} + +bool LLCPUInfo::hasSSE41() const +{ + return mHasSSE41; +} + +bool LLCPUInfo::hasSSE42() const +{ + return mHasSSE42; +} + +bool LLCPUInfo::hasSSE4a() const +{ + return mHasSSE4a; +} + +F64 LLCPUInfo::getMHz() const +{ + return mCPUMHz; +} + +std::string LLCPUInfo::getCPUString() const +{ + return mCPUString; +} + +const LLSD& LLCPUInfo::getSSEVersions() const +{ + return mSSEVersions; +} + +void LLCPUInfo::stream(std::ostream& s) const +{ + // gather machine information. + s << LLProcessorInfo().getCPUFeatureDescription(); + + // These are interesting as they reflect our internal view of the + // CPU's attributes regardless of platform + s << "->mHasSSE: " << (U32)mHasSSE << std::endl; + s << "->mHasSSE2: " << (U32)mHasSSE2 << std::endl; + s << "->mHasSSE3: " << (U32)mHasSSE3 << std::endl; + s << "->mHasSSE3S: " << (U32)mHasSSE3S << std::endl; + s << "->mHasSSE41: " << (U32)mHasSSE41 << std::endl; + s << "->mHasSSE42: " << (U32)mHasSSE42 << std::endl; + s << "->mHasSSE4a: " << (U32)mHasSSE4a << std::endl; + s << "->mHasAltivec: " << (U32)mHasAltivec << std::endl; + s << "->mCPUMHz: " << mCPUMHz << std::endl; + s << "->mCPUString: " << mCPUString << std::endl; +} + +// Helper class for LLMemoryInfo: accumulate stats in the form we store for +// LLMemoryInfo::getStatsMap(). +class Stats +{ +public: + Stats(): + mStats(LLSD::emptyMap()) + {} + + // Store every integer type as LLSD::Integer. + template <class T> + void add(const LLSD::String& name, const T& value, + typename boost::enable_if<boost::is_integral<T> >::type* = 0) + { + mStats[name] = LLSD::Integer(value); + } + + // Store every floating-point type as LLSD::Real. + template <class T> + void add(const LLSD::String& name, const T& value, + typename boost::enable_if<boost::is_float<T> >::type* = 0) + { + mStats[name] = LLSD::Real(value); + } + + // Hope that LLSD::Date values are sufficiently unambiguous. + void add(const LLSD::String& name, const LLSD::Date& value) + { + mStats[name] = value; + } + + LLSD get() const { return mStats; } + +private: + LLSD mStats; +}; + +LLMemoryInfo::LLMemoryInfo() +{ + refresh(); +} + +#if LL_WINDOWS +static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB) +{ + // Moved this here from llfloaterabout.cpp + + //! \bug + // For some reason, the reported amount of memory is always wrong. + // The original adjustment assumes it's always off by one meg, however + // errors of as much as 2520 KB have been observed in the value + // returned from the GetMemoryStatusEx function. Here we keep the + // original adjustment from llfoaterabout.cpp until this can be + // fixed somehow. + inKB += U32Megabytes(1); + + return inKB; +} +#endif + +#if LL_DARWIN +// static +U32Kilobytes LLMemoryInfo::getHardwareMemSize() +{ + // This might work on Linux as well. Someone check... + uint64_t phys = 0; + int mib[2] = { CTL_HW, HW_MEMSIZE }; + + size_t len = sizeof(phys); + sysctl(mib, 2, &phys, &len, NULL, 0); + + return U64Bytes(phys); +} +#endif + +U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const +{ +#if LL_WINDOWS + return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger())); + +#elif LL_DARWIN + return getHardwareMemSize(); + +#elif LL_LINUX + U64 phys = 0; + phys = (U64)(getpagesize()) * (U64)(get_phys_pages()); + return U64Bytes(phys); + +#else + return 0; + +#endif +} + +//static +void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb) +{ +#if LL_WINDOWS + // Sigh, this shouldn't be a static method, then we wouldn't have to + // reload this data separately from refresh() + LLSD statsMap(loadStatsMap()); + + avail_physical_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger(); + avail_virtual_mem_kb = (U32Kilobytes)statsMap["Avail Virtual KB"].asInteger(); + +#elif LL_DARWIN + // mStatsMap is derived from vm_stat, look for (e.g.) "kb free": + // $ vm_stat + // Mach Virtual Memory Statistics: (page size of 4096 bytes) + // Pages free: 462078. + // Pages active: 142010. + // Pages inactive: 220007. + // Pages wired down: 159552. + // "Translation faults": 220825184. + // Pages copy-on-write: 2104153. + // Pages zero filled: 167034876. + // Pages reactivated: 65153. + // Pageins: 2097212. + // Pageouts: 41759. + // Object cache: 841598 hits of 7629869 lookups (11% hit rate) + avail_physical_mem_kb = (U32Kilobytes)-1 ; + avail_virtual_mem_kb = (U32Kilobytes)-1 ; + +#elif LL_LINUX + // mStatsMap is derived from MEMINFO_FILE: + // $ cat /proc/meminfo + // MemTotal: 4108424 kB + // MemFree: 1244064 kB + // Buffers: 85164 kB + // Cached: 1990264 kB + // SwapCached: 0 kB + // Active: 1176648 kB + // Inactive: 1427532 kB + // Active(anon): 529152 kB + // Inactive(anon): 15924 kB + // Active(file): 647496 kB + // Inactive(file): 1411608 kB + // Unevictable: 16 kB + // Mlocked: 16 kB + // HighTotal: 3266316 kB + // HighFree: 721308 kB + // LowTotal: 842108 kB + // LowFree: 522756 kB + // SwapTotal: 6384632 kB + // SwapFree: 6384632 kB + // Dirty: 28 kB + // Writeback: 0 kB + // AnonPages: 528820 kB + // Mapped: 89472 kB + // Shmem: 16324 kB + // Slab: 159624 kB + // SReclaimable: 145168 kB + // SUnreclaim: 14456 kB + // KernelStack: 2560 kB + // PageTables: 5560 kB + // NFS_Unstable: 0 kB + // Bounce: 0 kB + // WritebackTmp: 0 kB + // CommitLimit: 8438844 kB + // Committed_AS: 1271596 kB + // VmallocTotal: 122880 kB + // VmallocUsed: 65252 kB + // VmallocChunk: 52356 kB + // HardwareCorrupted: 0 kB + // HugePages_Total: 0 + // HugePages_Free: 0 + // HugePages_Rsvd: 0 + // HugePages_Surp: 0 + // Hugepagesize: 2048 kB + // DirectMap4k: 434168 kB + // DirectMap2M: 477184 kB + // (could also run 'free', but easier to read a file than run a program) + avail_physical_mem_kb = (U32Kilobytes)-1 ; + avail_virtual_mem_kb = (U32Kilobytes)-1 ; + +#else + //do not know how to collect available memory info for other systems. + //leave it blank here for now. + + avail_physical_mem_kb = (U32Kilobytes)-1 ; + avail_virtual_mem_kb = (U32Kilobytes)-1 ; +#endif +} + +void LLMemoryInfo::stream(std::ostream& s) const +{ + // We want these memory stats to be easy to grep from the log, along with + // the timestamp. So preface each line with the timestamp and a + // distinctive marker. Without that, we'd have to search the log for the + // introducer line, then read subsequent lines, etc... + std::string pfx(LLError::utcTime() + " <mem> "); + + // Max key length + size_t key_width(0); + for (const auto& [key, value] : inMap(mStatsMap)) + { + size_t len(key.length()); + if (len > key_width) + { + key_width = len; + } + } + + // Now stream stats + for (const auto& [key, value] : inMap(mStatsMap)) + { + s << pfx << std::setw(narrow<size_t>(key_width+1)) << (key + ':') << ' '; + if (value.isInteger()) + s << std::setw(12) << value.asInteger(); + else if (value.isReal()) + s << std::fixed << std::setprecision(1) << value.asReal(); + else if (value.isDate()) + value.asDate().toStream(s); + else + s << value; // just use default LLSD formatting + s << std::endl; + } +} + +LLSD LLMemoryInfo::getStatsMap() const +{ + return mStatsMap; +} + +LLMemoryInfo& LLMemoryInfo::refresh() +{ + LL_PROFILE_ZONE_SCOPED + mStatsMap = loadStatsMap(); + + LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n"; + LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT); + LL_ENDL; + + return *this; +} + +LLSD LLMemoryInfo::loadStatsMap() +{ + LL_PROFILE_ZONE_SCOPED; + + // This implementation is derived from stream() code (as of 2011-06-29). + Stats stats; + + // associate timestamp for analysis over time + stats.add("timestamp", LLDate::now()); + +#if LL_WINDOWS + MEMORYSTATUSEX state; + state.dwLength = sizeof(state); + GlobalMemoryStatusEx(&state); + + DWORDLONG div = 1024; + + stats.add("Percent Memory use", state.dwMemoryLoad/div); + stats.add("Total Physical KB", state.ullTotalPhys/div); + stats.add("Avail Physical KB", state.ullAvailPhys/div); + stats.add("Total page KB", state.ullTotalPageFile/div); + stats.add("Avail page KB", state.ullAvailPageFile/div); + stats.add("Total Virtual KB", state.ullTotalVirtual/div); + stats.add("Avail Virtual KB", state.ullAvailVirtual/div); + + // SL-12122 - Call to GetPerformanceInfo() was removed here. Took + // on order of 10 ms, causing unacceptable frame time spike every + // second, and results were never used. If this is needed in the + // future, must find a way to avoid frame time impact (e.g. move + // to another thread, call much less often). + + PROCESS_MEMORY_COUNTERS_EX pmem; + pmem.cb = sizeof(pmem); + // GetProcessMemoryInfo() is documented to accept either + // PROCESS_MEMORY_COUNTERS* or PROCESS_MEMORY_COUNTERS_EX*, presumably + // using the redundant size info to distinguish. But its prototype + // specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a + // classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the + // pointer. + GetProcessMemoryInfo(GetCurrentProcess(), PPROCESS_MEMORY_COUNTERS(&pmem), sizeof(pmem)); + + stats.add("Page Fault Count", pmem.PageFaultCount); + stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div); + stats.add("WorkingSetSize KB", pmem.WorkingSetSize/div); + stats.add("QutaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage/div); + stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage/div); + stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/div); + stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage/div); + stats.add("PagefileUsage KB", pmem.PagefileUsage/div); + stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage/div); + stats.add("PrivateUsage KB", pmem.PrivateUsage/div); + +#elif LL_DARWIN + + const vm_size_t pagekb(vm_page_size / 1024); + + // + // Collect the vm_stat's + // + + { + vm_statistics64_data_t vmstat; + mach_msg_type_number_t vmstatCount = HOST_VM_INFO64_COUNT; + + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) &vmstat, &vmstatCount) != KERN_SUCCESS) + { + LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL; + } + else + { + stats.add("Pages free KB", pagekb * vmstat.free_count); + stats.add("Pages active KB", pagekb * vmstat.active_count); + stats.add("Pages inactive KB", pagekb * vmstat.inactive_count); + stats.add("Pages wired KB", pagekb * vmstat.wire_count); + + stats.add("Pages zero fill", vmstat.zero_fill_count); + stats.add("Page reactivations", vmstat.reactivations); + stats.add("Page-ins", vmstat.pageins); + stats.add("Page-outs", vmstat.pageouts); + + stats.add("Faults", vmstat.faults); + stats.add("Faults copy-on-write", vmstat.cow_faults); + + stats.add("Cache lookups", vmstat.lookups); + stats.add("Cache hits", vmstat.hits); + + stats.add("Page purgeable count", vmstat.purgeable_count); + stats.add("Page purges", vmstat.purges); + + stats.add("Page speculative reads", vmstat.speculative_count); + } + } + + // + // Collect the misc task info + // + + { + task_events_info_data_t taskinfo; + unsigned taskinfoSize = sizeof(taskinfo); + + if (task_info(mach_task_self(), TASK_EVENTS_INFO, (task_info_t) &taskinfo, &taskinfoSize) != KERN_SUCCESS) + { + LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL; + } + else + { + stats.add("Task page-ins", taskinfo.pageins); + stats.add("Task copy-on-write faults", taskinfo.cow_faults); + stats.add("Task messages sent", taskinfo.messages_sent); + stats.add("Task messages received", taskinfo.messages_received); + stats.add("Task mach system call count", taskinfo.syscalls_mach); + stats.add("Task unix system call count", taskinfo.syscalls_unix); + stats.add("Task context switch count", taskinfo.csw); + } + } + + // + // Collect the basic task info + // + + { + mach_task_basic_info_data_t taskinfo; + mach_msg_type_number_t task_count = MACH_TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t) &taskinfo, &task_count) != KERN_SUCCESS) + { + LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL; + } + else + { + stats.add("Basic virtual memory KB", taskinfo.virtual_size / 1024); + stats.add("Basic resident memory KB", taskinfo.resident_size / 1024); + stats.add("Basic max resident memory KB", taskinfo.resident_size_max / 1024); + stats.add("Basic new thread policy", taskinfo.policy); + stats.add("Basic suspend count", taskinfo.suspend_count); + } + } + +#elif LL_LINUX + std::ifstream meminfo(MEMINFO_FILE); + if (meminfo.is_open()) + { + // MemTotal: 4108424 kB + // MemFree: 1244064 kB + // Buffers: 85164 kB + // Cached: 1990264 kB + // SwapCached: 0 kB + // Active: 1176648 kB + // Inactive: 1427532 kB + // ... + // VmallocTotal: 122880 kB + // VmallocUsed: 65252 kB + // VmallocChunk: 52356 kB + // HardwareCorrupted: 0 kB + // HugePages_Total: 0 + // HugePages_Free: 0 + // HugePages_Rsvd: 0 + // HugePages_Surp: 0 + // Hugepagesize: 2048 kB + // DirectMap4k: 434168 kB + // DirectMap2M: 477184 kB + + // Intentionally don't pass the boost::no_except flag. This + // boost::regex object is constructed with a string literal, so it + // should be valid every time. If it becomes invalid, we WANT an + // exception, hopefully even before the dev checks in. + boost::regex stat_rx("(.+): +([0-9]+)( kB)?"); + boost::smatch matched; + + std::string line; + while (std::getline(meminfo, line)) + { + LL_DEBUGS("LLMemoryInfo") << line << LL_ENDL; + if (ll_regex_match(line, matched, stat_rx)) + { + // e.g. "MemTotal: 4108424 kB" + LLSD::String key(matched[1].first, matched[1].second); + LLSD::String value_str(matched[2].first, matched[2].second); + LLSD::Integer value(0); + try + { + value = boost::lexical_cast<LLSD::Integer>(value_str); + } + catch (const boost::bad_lexical_cast&) + { + LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str + << "' in " << MEMINFO_FILE << " line: " + << line << LL_ENDL; + continue; + } + // Store this statistic. + stats.add(key, value); + } + else + { + LL_WARNS("LLMemoryInfo") << "unrecognized " << MEMINFO_FILE << " line: " + << line << LL_ENDL; + } + } + } + else + { + LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL; + } + +#else + LL_WARNS("LLMemoryInfo") << "Unknown system; unable to collect memory information" << LL_ENDL; + +#endif + + return stats.get(); +} + +std::ostream& operator<<(std::ostream& s, const LLOSInfo& info) +{ + info.stream(s); + return s; +} + +std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info) +{ + info.stream(s); + return s; +} + +std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info) +{ + info.stream(s); + return s; +} + +class FrameWatcher +{ +public: + FrameWatcher(): + // Hooking onto the "mainloop" event pump gets us one call per frame. + mConnection(LLEventPumps::instance() + .obtain("mainloop") + .listen("FrameWatcher", boost::bind(&FrameWatcher::tick, this, _1))), + // Initializing mSampleStart to an invalid timestamp alerts us to skip + // trying to compute framerate on the first call. + mSampleStart(-1), + // Initializing mSampleEnd to 0 ensures that we treat the first call + // as the completion of a sample window. + mSampleEnd(0), + mFrames(0), + // Both MEM_INFO_WINDOW and MEM_INFO_THROTTLE are in seconds. We need + // the number of integer MEM_INFO_THROTTLE sample slots that will fit + // in MEM_INFO_WINDOW. Round up. + mSamples(int((MEM_INFO_WINDOW / MEM_INFO_THROTTLE) + 0.7)), + // Initializing to F32_MAX means that the first real frame will become + // the slowest ever, which sounds like a good idea. + mSlowest(F32_MAX) + {} + + bool tick(const LLSD&) + { + F32 timestamp(mTimer.getElapsedTimeF32()); + + // Count this frame in the interval just completed. + ++mFrames; + + // Have we finished a sample window yet? + if (timestamp < mSampleEnd) + { + // no, just keep waiting + return false; + } + + // Set up for next sample window. Capture values for previous frame in + // local variables and reset data members. + U32 frames(mFrames); + F32 sampleStart(mSampleStart); + // No frames yet in next window + mFrames = 0; + // which starts right now + mSampleStart = timestamp; + // and ends MEM_INFO_THROTTLE seconds in the future + mSampleEnd = mSampleStart + MEM_INFO_THROTTLE; + + // On the very first call, that's all we can do, no framerate + // computation is possible. + if (sampleStart < 0) + { + return false; + } + + // How long did this actually take? As framerate slows, the duration + // of the frame we just finished could push us WELL beyond our desired + // sample window size. + F32 elapsed(timestamp - sampleStart); + F32 framerate(frames/elapsed); + + // Remember previous slowest framerate because we're just about to + // update it. + F32 slowest(mSlowest); + // Remember previous number of samples. + boost::circular_buffer<F32>::size_type prevSize(mSamples.size()); + + // Capture new framerate in our samples buffer. Once the buffer is + // full (after MEM_INFO_WINDOW seconds), this will displace the oldest + // sample. ("So they all rolled over, and one fell out...") + mSamples.push_back(framerate); + + // Calculate the new minimum framerate. I know of no way to update a + // rolling minimum without ever rescanning the buffer. But since there + // are only a few tens of items in this buffer, rescanning it is + // probably cheaper (and certainly easier to reason about) than + // attempting to optimize away some of the scans. + mSlowest = framerate; // pick an arbitrary entry to start + for (boost::circular_buffer<F32>::const_iterator si(mSamples.begin()), send(mSamples.end()); + si != send; ++si) + { + if (*si < mSlowest) + { + mSlowest = *si; + } + } + + // We're especially interested in memory as framerate drops. Only log + // when framerate drops below the slowest framerate we remember. + // (Should always be true for the end of the very first sample + // window.) + if (framerate >= slowest) + { + return false; + } + // Congratulations, we've hit a new low. :-P + + LL_INFOS("FrameWatcher") << ' '; + if (! prevSize) + { + LL_CONT << "initial framerate "; + } + else + { + LL_CONT << "slowest framerate for last " << int(prevSize * MEM_INFO_THROTTLE) + << " seconds "; + } + + auto precision = LL_CONT.precision(); + + LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n' + << LLMemoryInfo(); + + LL_CONT.precision(precision); + LL_CONT << LL_ENDL; + return false; + } + +private: + // Storing the connection in an LLTempBoundListener ensures it will be + // disconnected when we're destroyed. + LLTempBoundListener mConnection; + // Track elapsed time + LLTimer mTimer; + // Some of what you see here is in fact redundant with functionality you + // can get from LLTimer. Unfortunately the LLTimer API is missing the + // feature we need: has at least the stated interval elapsed, and if so, + // exactly how long has passed? So we have to do it by hand, sigh. + // Time at start, end of sample window + F32 mSampleStart, mSampleEnd; + // Frames this sample window + U32 mFrames; + // Sliding window of framerate samples + boost::circular_buffer<F32> mSamples; + // Slowest framerate in mSamples + F32 mSlowest; +}; + +// Need an instance of FrameWatcher before it does any good +static FrameWatcher sFrameWatcher; + +bool gunzip_file(const std::string& srcfile, const std::string& dstfile) +{ + std::string tmpfile; + const S32 UNCOMPRESS_BUFFER_SIZE = 32768; + bool retval = false; + gzFile src = NULL; + U8 buffer[UNCOMPRESS_BUFFER_SIZE]; + LLFILE *dst = NULL; + S32 bytes = 0; + tmpfile = dstfile + ".t"; +#ifdef LL_WINDOWS + llutf16string utf16filename = utf8str_to_utf16str(srcfile); + src = gzopen_w(utf16filename.c_str(), "rb"); +#else + src = gzopen(srcfile.c_str(), "rb"); +#endif + if (! src) goto err; + dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */ + if (! dst) goto err; + do + { + bytes = gzread(src, buffer, UNCOMPRESS_BUFFER_SIZE); + size_t nwrit = fwrite(buffer, sizeof(U8), bytes, dst); + if (nwrit < (size_t) bytes) + { + LL_WARNS() << "Short write on " << tmpfile << ": Wrote " << nwrit << " of " << bytes << " bytes." << LL_ENDL; + goto err; + } + } while(gzeof(src) == 0); + fclose(dst); + dst = NULL; +#if LL_WINDOWS + // Rename in windows needs the dstfile to not exist. + LLFile::remove(dstfile, ENOENT); +#endif + if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ + retval = true; +err: + if (src != NULL) gzclose(src); + if (dst != NULL) fclose(dst); + return retval; +} + +bool gzip_file(const std::string& srcfile, const std::string& dstfile) +{ + const S32 COMPRESS_BUFFER_SIZE = 32768; + std::string tmpfile; + bool retval = false; + U8 buffer[COMPRESS_BUFFER_SIZE]; + gzFile dst = NULL; + LLFILE *src = NULL; + S32 bytes = 0; + tmpfile = dstfile + ".t"; + +#ifdef LL_WINDOWS + llutf16string utf16filename = utf8str_to_utf16str(tmpfile); + dst = gzopen_w(utf16filename.c_str(), "wb"); +#else + dst = gzopen(tmpfile.c_str(), "wb"); +#endif + + if (! dst) goto err; + src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */ + if (! src) goto err; + + while ((bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE, src)) > 0) + { + if (gzwrite(dst, buffer, bytes) <= 0) + { + LL_WARNS() << "gzwrite failed: " << gzerror(dst, NULL) << LL_ENDL; + goto err; + } + } + + if (ferror(src)) + { + LL_WARNS() << "Error reading " << srcfile << LL_ENDL; + goto err; + } + + gzclose(dst); + dst = NULL; +#if LL_WINDOWS + // Rename in windows needs the dstfile to not exist. + LLFile::remove(dstfile); +#endif + if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ + retval = true; + err: + if (src != NULL) fclose(src); + if (dst != NULL) gzclose(dst); + return retval; +} diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index 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 <iosfwd>
-#include <string>
-
-class LL_COMMON_API LLOSInfo : public LLSingleton<LLOSInfo>
-{
- LLSINGLETON(LLOSInfo);
-public:
- void stream(std::ostream& s) const;
-
- const std::string& getOSString() const;
- const std::string& getOSStringSimple() const;
-
- const std::string& getOSVersionString() const;
-
- const S32 getOSBitness() const;
-
- S32 mMajorVer;
- S32 mMinorVer;
- S32 mBuild;
-
-#ifndef LL_WINDOWS
- static long getMaxOpenFiles();
-#endif
- static bool is64Bit();
-
- static U32 getProcessVirtualSizeKB();
- static U32 getProcessResidentSizeKB();
-private:
- std::string mOSString;
- std::string mOSStringSimple;
- std::string mOSVersionString;
- S32 mOSBitness;
-};
-
-
-class LL_COMMON_API LLCPUInfo
-{
-public:
- LLCPUInfo();
- void stream(std::ostream& s) const;
-
- std::string getCPUString() const;
- const LLSD& getSSEVersions() const;
-
- bool hasAltivec() const;
- bool hasSSE() const;
- bool hasSSE2() const;
- bool hasSSE3() const;
- bool hasSSE3S() const;
- bool hasSSE41() const;
- bool hasSSE42() const;
- bool hasSSE4a() const;
- F64 getMHz() const;
-
- // Family is "AMD Duron" or "Intel Pentium Pro"
- const std::string& getFamily() const { return mFamily; }
-
-private:
- bool mHasSSE;
- bool mHasSSE2;
- bool mHasSSE3;
- bool mHasSSE3S;
- bool mHasSSE41;
- bool mHasSSE42;
- bool mHasSSE4a;
- bool mHasAltivec;
- F64 mCPUMHz;
- std::string mFamily;
- std::string mCPUString;
- LLSD mSSEVersions;
-};
-
-//=============================================================================
-//
-// CLASS LLMemoryInfo
-
-class LL_COMMON_API LLMemoryInfo
-
-/*! @brief Class to query the memory subsystem
-
- @details
- Here's how you use an LLMemoryInfo:
-
- LLMemoryInfo info;
-<br> LL_INFOS() << info << LL_ENDL;
-*/
-{
-public:
- LLMemoryInfo(); ///< Default constructor
- void stream(std::ostream& s) const; ///< output text info to s
-
- U32Kilobytes getPhysicalMemoryKB() const;
-#if LL_DARWIN
- static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib.
-#endif
-
- //get the available memory infomation in KiloBytes.
- static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb);
-
- // Retrieve a map of memory statistics. The keys of the map are platform-
- // dependent. The values are in kilobytes to try to avoid integer overflow.
- LLSD getStatsMap() const;
-
- // Re-fetch memory data (as reported by stream() and getStatsMap()) from the
- // system. Normally this is fetched at construction time. Return (*this)
- // to permit usage of the form:
- // @code
- // LLMemoryInfo info;
- // ...
- // info.refresh().getStatsMap();
- // @endcode
- LLMemoryInfo& refresh();
-
-private:
- // set mStatsMap
- static LLSD loadStatsMap();
-
- // Memory stats for getStatsMap().
- LLSD mStatsMap;
-};
-
-
-LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLOSInfo& info);
-LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info);
-LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info);
-
-// gunzip srcfile into dstfile. Returns false on error.
-bool LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile);
-// gzip srcfile into dstfile. Returns false on error.
-bool LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile);
-
-extern LL_COMMON_API LLCPUInfo gSysCPU;
-
-#endif // LL_LLSYS_H
+/** + * @file llsys.h + * @brief System information debugging classes. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_SYS_H +#define LL_SYS_H + +// +// The LLOSInfo, LLCPUInfo, and LLMemoryInfo classes are essentially +// the same, but query different machine subsystems. Here's how you +// use an LLCPUInfo object: +// +// LLCPUInfo info; +// LL_INFOS() << info << LL_ENDL; +// + +#include "llsd.h" +#include "llsingleton.h" +#include <iosfwd> +#include <string> + +class LL_COMMON_API LLOSInfo : public LLSingleton<LLOSInfo> +{ + LLSINGLETON(LLOSInfo); +public: + void stream(std::ostream& s) const; + + const std::string& getOSString() const; + const std::string& getOSStringSimple() const; + + const std::string& getOSVersionString() const; + + const S32 getOSBitness() const; + + S32 mMajorVer; + S32 mMinorVer; + S32 mBuild; + +#ifndef LL_WINDOWS + static long getMaxOpenFiles(); +#endif + static bool is64Bit(); + + static U32 getProcessVirtualSizeKB(); + static U32 getProcessResidentSizeKB(); +private: + std::string mOSString; + std::string mOSStringSimple; + std::string mOSVersionString; + S32 mOSBitness; +}; + + +class LL_COMMON_API LLCPUInfo +{ +public: + LLCPUInfo(); + void stream(std::ostream& s) const; + + std::string getCPUString() const; + const LLSD& getSSEVersions() const; + + bool hasAltivec() const; + bool hasSSE() const; + bool hasSSE2() const; + bool hasSSE3() const; + bool hasSSE3S() const; + bool hasSSE41() const; + bool hasSSE42() const; + bool hasSSE4a() const; + F64 getMHz() const; + + // Family is "AMD Duron" or "Intel Pentium Pro" + const std::string& getFamily() const { return mFamily; } + +private: + bool mHasSSE; + bool mHasSSE2; + bool mHasSSE3; + bool mHasSSE3S; + bool mHasSSE41; + bool mHasSSE42; + bool mHasSSE4a; + bool mHasAltivec; + F64 mCPUMHz; + std::string mFamily; + std::string mCPUString; + LLSD mSSEVersions; +}; + +//============================================================================= +// +// CLASS LLMemoryInfo + +class LL_COMMON_API LLMemoryInfo + +/*! @brief Class to query the memory subsystem + + @details + Here's how you use an LLMemoryInfo: + + LLMemoryInfo info; +<br> LL_INFOS() << info << LL_ENDL; +*/ +{ +public: + LLMemoryInfo(); ///< Default constructor + void stream(std::ostream& s) const; ///< output text info to s + + U32Kilobytes getPhysicalMemoryKB() const; +#if LL_DARWIN + static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib. +#endif + + //get the available memory infomation in KiloBytes. + static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb); + + // Retrieve a map of memory statistics. The keys of the map are platform- + // dependent. The values are in kilobytes to try to avoid integer overflow. + LLSD getStatsMap() const; + + // Re-fetch memory data (as reported by stream() and getStatsMap()) from the + // system. Normally this is fetched at construction time. Return (*this) + // to permit usage of the form: + // @code + // LLMemoryInfo info; + // ... + // info.refresh().getStatsMap(); + // @endcode + LLMemoryInfo& refresh(); + +private: + // set mStatsMap + static LLSD loadStatsMap(); + + // Memory stats for getStatsMap(). + LLSD mStatsMap; +}; + + +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLOSInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info); +LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info); + +// gunzip srcfile into dstfile. Returns false on error. +bool LL_COMMON_API gunzip_file(const std::string& srcfile, const std::string& dstfile); +// gzip srcfile into dstfile. Returns false on error. +bool LL_COMMON_API gzip_file(const std::string& srcfile, const std::string& dstfile); + +extern LL_COMMON_API LLCPUInfo gSysCPU; + +#endif // LL_LLSYS_H diff --git a/indra/llcommon/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 <sched.h>
-#endif
-
-
-#ifdef LL_WINDOWS
-
-const DWORD MS_VC_EXCEPTION=0x406D1388;
-
-#pragma pack(push,8)
-typedef struct tagTHREADNAME_INFO
-{
- DWORD dwType; // Must be 0x1000.
- LPCSTR szName; // Pointer to name (in user addr space).
- DWORD dwThreadID; // Thread ID (-1=caller thread).
- DWORD dwFlags; // Reserved for future use, must be zero.
-} THREADNAME_INFO;
-#pragma pack(pop)
-
-void set_thread_name( DWORD dwThreadID, const char* threadName)
-{
- THREADNAME_INFO info;
- info.dwType = 0x1000;
- info.szName = threadName;
- info.dwThreadID = dwThreadID;
- info.dwFlags = 0;
-
- __try
- {
- ::RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info );
- }
- __except(EXCEPTION_CONTINUE_EXECUTION)
- {
- }
-}
-#endif
-
-
-//----------------------------------------------------------------------------
-// Usage:
-// void run_func(LLThread* thread)
-// {
-// }
-// LLThread* thread = new LLThread();
-// thread->run(run_func);
-// ...
-// thread->setQuitting();
-// while(!timeout)
-// {
-// if (thread->isStopped())
-// {
-// delete thread;
-// break;
-// }
-// }
-//
-//----------------------------------------------------------------------------
-namespace
-{
-
- LLThread::id_t main_thread()
- {
- // Using a function-static variable to identify the main thread
- // requires that control reach here from the main thread before it
- // reaches here from any other thread. We simply trust that whichever
- // thread gets here first is the main thread.
- static LLThread::id_t s_thread_id = LLThread::currentID();
- return s_thread_id;
- }
-
-} // anonymous namespace
-
-LL_COMMON_API bool on_main_thread()
-{
- return (LLThread::currentID() == main_thread());
-}
-
-LL_COMMON_API bool assert_main_thread()
-{
- auto curr = LLThread::currentID();
- auto main = main_thread();
- if (curr == main)
- return true;
-
- LL_WARNS() << "Illegal execution from thread id " << curr
- << " outside main thread " << main << LL_ENDL;
- return false;
-}
-
-// this function has become moot
-void LLThread::registerThreadID() {}
-
-//
-// Handed to the APR thread creation function
-//
-void LLThread::threadRun()
-{
-#ifdef LL_WINDOWS
- set_thread_name(-1, mName.c_str());
-
-#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32)
- HANDLE hThread = GetCurrentThread();
- if (hThread)
- {
- SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE);
- }
-#endif
-
-#endif
-
- LL_PROFILER_SET_THREAD_NAME( mName.c_str() );
-
- // this is the first point at which we're actually running in the new thread
- mID = currentID();
-
- // for now, hard code all LLThreads to report to single master thread recorder, which is known to be running on main thread
- mRecorder = new LLTrace::ThreadRecorder(*LLTrace::get_master_thread_recorder());
-
- // Run the user supplied function
- do
- {
- try
- {
- run();
- }
- catch (const LLContinueError &e)
- {
- LL_WARNS("THREAD") << "ContinueException on thread '" << mName <<
- "' reentering run(). Error what is: '" << e.what() << "'" << LL_ENDL;
- //output possible call stacks to log file.
- LLError::LLCallStacks::print();
-
- LOG_UNHANDLED_EXCEPTION("LLThread");
- continue;
- }
- break;
-
- } while (true);
-
- //LL_INFOS() << "LLThread::staticRun() Exiting: " << threadp->mName << LL_ENDL;
-
-
- delete mRecorder;
- mRecorder = NULL;
-
- // We're done with the run function, this thread is done executing now.
- //NB: we are using this flag to sync across threads...we really need memory barriers here
- // Todo: add LLMutex per thread instead of flag?
- // We are using "while (mStatus != STOPPED) {ms_sleep();}" everywhere.
- mStatus = STOPPED;
-}
-
-LLThread::LLThread(const std::string& name, apr_pool_t *poolp) :
- mPaused(false),
- mName(name),
- mThreadp(NULL),
- mStatus(STOPPED),
- mRecorder(NULL)
-{
- mRunCondition = new LLCondition();
- mDataLock = new LLMutex();
- mLocalAPRFilePoolp = NULL ;
-}
-
-
-LLThread::~LLThread()
-{
- shutdown();
-
- if (isCrashed())
- {
- LL_WARNS("THREAD") << "Destroying crashed thread named '" << mName << "'" << LL_ENDL;
- }
-
- if(mLocalAPRFilePoolp)
- {
- delete mLocalAPRFilePoolp ;
- mLocalAPRFilePoolp = NULL ;
- }
-}
-
-void LLThread::shutdown()
-{
- if (isCrashed())
- {
- LL_WARNS("THREAD") << "Shutting down crashed thread named '" << mName << "'" << LL_ENDL;
- }
-
- // Warning! If you somehow call the thread destructor from itself,
- // the thread will die in an unclean fashion!
- if (mThreadp)
- {
- if (!isStopped())
- {
- // The thread isn't already stopped
- // First, set the flag that indicates that we're ready to die
- setQuitting();
-
- //LL_INFOS() << "LLThread::~LLThread() Killing thread " << mName << " Status: " << mStatus << LL_ENDL;
- // Now wait a bit for the thread to exit
- // It's unclear whether I should even bother doing this - this destructor
- // should never get called unless we're already stopped, really...
- S32 counter = 0;
- const S32 MAX_WAIT = 600;
- while (counter < MAX_WAIT)
- {
- if (isStopped())
- {
- break;
- }
- // Sleep for a tenth of a second
- ms_sleep(100);
- yield();
- counter++;
- }
- }
-
- if (!isStopped())
- {
- // This thread just wouldn't stop, even though we gave it time
- //LL_WARNS() << "LLThread::~LLThread() exiting thread before clean exit!" << LL_ENDL;
- // Put a stake in its heart. (A very hostile method to force a thread to quit)
-#if LL_WINDOWS
- TerminateThread(mNativeHandle, 0);
-#else
- pthread_cancel(mNativeHandle);
-#endif
-
- delete mRecorder;
- mRecorder = NULL;
- mStatus = STOPPED;
- return;
- }
- mThreadp = NULL;
- }
-
- delete mRunCondition;
- mRunCondition = NULL;
-
- delete mDataLock;
- mDataLock = NULL;
-
- if (mRecorder)
- {
- // missed chance to properly shut down recorder (needs to be done in thread context)
- // probably due to abnormal thread termination
- // so just leak it and remove it from parent
- LLTrace::get_master_thread_recorder()->removeChildRecorder(mRecorder);
- }
-}
-
-
-void LLThread::start()
-{
- llassert(isStopped());
-
- // Set thread state to running
- mStatus = RUNNING;
-
- try
- {
- mThreadp = new std::thread(std::bind(&LLThread::threadRun, this));
- mNativeHandle = mThreadp->native_handle();
- }
- catch (std::system_error& ex)
- {
- mStatus = STOPPED;
- LL_WARNS() << "failed to start thread " << mName << " " << ex.what() << LL_ENDL;
- }
-
-}
-
-//============================================================================
-// Called from MAIN THREAD.
-
-// Request that the thread pause/resume.
-// The thread will pause when (and if) it calls checkPause()
-void LLThread::pause()
-{
- if (!mPaused)
- {
- // this will cause the thread to stop execution as soon as checkPause() is called
- mPaused = 1; // Does not need to be atomic since this is only set/unset from the main thread
- }
-}
-
-void LLThread::unpause()
-{
- if (mPaused)
- {
- mPaused = 0;
- }
-
- wake(); // wake up the thread if necessary
-}
-
-// virtual predicate function -- returns true if the thread should wake up, false if it should sleep.
-bool LLThread::runCondition(void)
-{
- // by default, always run. Handling of pause/unpause is done regardless of this function's result.
- return true;
-}
-
-//============================================================================
-// Called from run() (CHILD THREAD).
-// Stop thread execution if requested until unpaused.
-void LLThread::checkPause()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- mDataLock->lock();
-
- // This is in a while loop because the pthread API allows for spurious wakeups.
- while(shouldSleep())
- {
- mDataLock->unlock();
- mRunCondition->wait(); // unlocks mRunCondition
- mDataLock->lock();
- // mRunCondition is locked when the thread wakes up
- }
-
- mDataLock->unlock();
-}
-
-//============================================================================
-
-void LLThread::setQuitting()
-{
- mDataLock->lock();
- if (mStatus == RUNNING)
- {
- mStatus = QUITTING;
- }
- // It's only safe to remove mRunCondition if all locked threads were notified
- mRunCondition->broadcast();
- mDataLock->unlock();
-}
-
-// static
-LLThread::id_t LLThread::currentID()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- return std::this_thread::get_id();
-}
-
-// static
-void LLThread::yield()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- std::this_thread::yield();
-}
-
-void LLThread::wake()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- mDataLock->lock();
- if(!shouldSleep())
- {
- mRunCondition->signal();
- }
- mDataLock->unlock();
-}
-
-void LLThread::wakeLocked()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- if(!shouldSleep())
- {
- mRunCondition->signal();
- }
-}
-
-void LLThread::lockData()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- mDataLock->lock();
-}
-
-void LLThread::unlockData()
-{
- LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD
- mDataLock->unlock();
-}
-
-//============================================================================
-
-//----------------------------------------------------------------------------
-
-//static
-LLMutex* LLThreadSafeRefCount::sMutex = 0;
-
-//static
-void LLThreadSafeRefCount::initThreadSafeRefCount()
-{
- if (!sMutex)
- {
- sMutex = new LLMutex();
- }
-}
-
-//static
-void LLThreadSafeRefCount::cleanupThreadSafeRefCount()
-{
- delete sMutex;
- sMutex = NULL;
-}
-
-
-//----------------------------------------------------------------------------
-
-LLThreadSafeRefCount::LLThreadSafeRefCount() :
- mRef(0)
-{
-}
-
-LLThreadSafeRefCount::LLThreadSafeRefCount(const LLThreadSafeRefCount& src)
-{
- mRef = 0;
-}
-
-LLThreadSafeRefCount::~LLThreadSafeRefCount()
-{
- if (mRef != 0)
- {
- LL_ERRS() << "deleting referenced object mRef = " << mRef << LL_ENDL;
- }
-}
-
-//============================================================================
-
-LLResponder::~LLResponder()
-{
-}
-
-//============================================================================
+/** + * @file llthread.cpp + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010-2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llapr.h" + +#include "apr_portable.h" + +#include "llthread.h" +#include "llmutex.h" + +#include "lltimer.h" +#include "lltrace.h" +#include "lltracethreadrecorder.h" +#include "llexception.h" + +#if LL_LINUX +#include <sched.h> +#endif + + +#ifdef LL_WINDOWS + +const DWORD MS_VC_EXCEPTION=0x406D1388; + +#pragma pack(push,8) +typedef struct tagTHREADNAME_INFO +{ + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. +} THREADNAME_INFO; +#pragma pack(pop) + +void set_thread_name( DWORD dwThreadID, const char* threadName) +{ + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = threadName; + info.dwThreadID = dwThreadID; + info.dwFlags = 0; + + __try + { + ::RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(DWORD), (ULONG_PTR*)&info ); + } + __except(EXCEPTION_CONTINUE_EXECUTION) + { + } +} +#endif + + +//---------------------------------------------------------------------------- +// Usage: +// void run_func(LLThread* thread) +// { +// } +// LLThread* thread = new LLThread(); +// thread->run(run_func); +// ... +// thread->setQuitting(); +// while(!timeout) +// { +// if (thread->isStopped()) +// { +// delete thread; +// break; +// } +// } +// +//---------------------------------------------------------------------------- +namespace +{ + + LLThread::id_t main_thread() + { + // Using a function-static variable to identify the main thread + // requires that control reach here from the main thread before it + // reaches here from any other thread. We simply trust that whichever + // thread gets here first is the main thread. + static LLThread::id_t s_thread_id = LLThread::currentID(); + return s_thread_id; + } + +} // anonymous namespace + +LL_COMMON_API bool on_main_thread() +{ + return (LLThread::currentID() == main_thread()); +} + +LL_COMMON_API bool assert_main_thread() +{ + auto curr = LLThread::currentID(); + auto main = main_thread(); + if (curr == main) + return true; + + LL_WARNS() << "Illegal execution from thread id " << curr + << " outside main thread " << main << LL_ENDL; + return false; +} + +// this function has become moot +void LLThread::registerThreadID() {} + +// +// Handed to the APR thread creation function +// +void LLThread::threadRun() +{ +#ifdef LL_WINDOWS + set_thread_name(-1, mName.c_str()); + +#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32) + HANDLE hThread = GetCurrentThread(); + if (hThread) + { + SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE); + } +#endif + +#endif + + LL_PROFILER_SET_THREAD_NAME( mName.c_str() ); + + // this is the first point at which we're actually running in the new thread + mID = currentID(); + + // for now, hard code all LLThreads to report to single master thread recorder, which is known to be running on main thread + mRecorder = new LLTrace::ThreadRecorder(*LLTrace::get_master_thread_recorder()); + + // Run the user supplied function + do + { + try + { + run(); + } + catch (const LLContinueError &e) + { + LL_WARNS("THREAD") << "ContinueException on thread '" << mName << + "' reentering run(). Error what is: '" << e.what() << "'" << LL_ENDL; + //output possible call stacks to log file. + LLError::LLCallStacks::print(); + + LOG_UNHANDLED_EXCEPTION("LLThread"); + continue; + } + break; + + } while (true); + + //LL_INFOS() << "LLThread::staticRun() Exiting: " << threadp->mName << LL_ENDL; + + + delete mRecorder; + mRecorder = NULL; + + // We're done with the run function, this thread is done executing now. + //NB: we are using this flag to sync across threads...we really need memory barriers here + // Todo: add LLMutex per thread instead of flag? + // We are using "while (mStatus != STOPPED) {ms_sleep();}" everywhere. + mStatus = STOPPED; +} + +LLThread::LLThread(const std::string& name, apr_pool_t *poolp) : + mPaused(false), + mName(name), + mThreadp(NULL), + mStatus(STOPPED), + mRecorder(NULL) +{ + mRunCondition = new LLCondition(); + mDataLock = new LLMutex(); + mLocalAPRFilePoolp = NULL ; +} + + +LLThread::~LLThread() +{ + shutdown(); + + if (isCrashed()) + { + LL_WARNS("THREAD") << "Destroying crashed thread named '" << mName << "'" << LL_ENDL; + } + + if(mLocalAPRFilePoolp) + { + delete mLocalAPRFilePoolp ; + mLocalAPRFilePoolp = NULL ; + } +} + +void LLThread::shutdown() +{ + if (isCrashed()) + { + LL_WARNS("THREAD") << "Shutting down crashed thread named '" << mName << "'" << LL_ENDL; + } + + // Warning! If you somehow call the thread destructor from itself, + // the thread will die in an unclean fashion! + if (mThreadp) + { + if (!isStopped()) + { + // The thread isn't already stopped + // First, set the flag that indicates that we're ready to die + setQuitting(); + + //LL_INFOS() << "LLThread::~LLThread() Killing thread " << mName << " Status: " << mStatus << LL_ENDL; + // Now wait a bit for the thread to exit + // It's unclear whether I should even bother doing this - this destructor + // should never get called unless we're already stopped, really... + S32 counter = 0; + const S32 MAX_WAIT = 600; + while (counter < MAX_WAIT) + { + if (isStopped()) + { + break; + } + // Sleep for a tenth of a second + ms_sleep(100); + yield(); + counter++; + } + } + + if (!isStopped()) + { + // This thread just wouldn't stop, even though we gave it time + //LL_WARNS() << "LLThread::~LLThread() exiting thread before clean exit!" << LL_ENDL; + // Put a stake in its heart. (A very hostile method to force a thread to quit) +#if LL_WINDOWS + TerminateThread(mNativeHandle, 0); +#else + pthread_cancel(mNativeHandle); +#endif + + delete mRecorder; + mRecorder = NULL; + mStatus = STOPPED; + return; + } + mThreadp = NULL; + } + + delete mRunCondition; + mRunCondition = NULL; + + delete mDataLock; + mDataLock = NULL; + + if (mRecorder) + { + // missed chance to properly shut down recorder (needs to be done in thread context) + // probably due to abnormal thread termination + // so just leak it and remove it from parent + LLTrace::get_master_thread_recorder()->removeChildRecorder(mRecorder); + } +} + + +void LLThread::start() +{ + llassert(isStopped()); + + // Set thread state to running + mStatus = RUNNING; + + try + { + mThreadp = new std::thread(std::bind(&LLThread::threadRun, this)); + mNativeHandle = mThreadp->native_handle(); + } + catch (std::system_error& ex) + { + mStatus = STOPPED; + LL_WARNS() << "failed to start thread " << mName << " " << ex.what() << LL_ENDL; + } + +} + +//============================================================================ +// Called from MAIN THREAD. + +// Request that the thread pause/resume. +// The thread will pause when (and if) it calls checkPause() +void LLThread::pause() +{ + if (!mPaused) + { + // this will cause the thread to stop execution as soon as checkPause() is called + mPaused = 1; // Does not need to be atomic since this is only set/unset from the main thread + } +} + +void LLThread::unpause() +{ + if (mPaused) + { + mPaused = 0; + } + + wake(); // wake up the thread if necessary +} + +// virtual predicate function -- returns true if the thread should wake up, false if it should sleep. +bool LLThread::runCondition(void) +{ + // by default, always run. Handling of pause/unpause is done regardless of this function's result. + return true; +} + +//============================================================================ +// Called from run() (CHILD THREAD). +// Stop thread execution if requested until unpaused. +void LLThread::checkPause() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mDataLock->lock(); + + // This is in a while loop because the pthread API allows for spurious wakeups. + while(shouldSleep()) + { + mDataLock->unlock(); + mRunCondition->wait(); // unlocks mRunCondition + mDataLock->lock(); + // mRunCondition is locked when the thread wakes up + } + + mDataLock->unlock(); +} + +//============================================================================ + +void LLThread::setQuitting() +{ + mDataLock->lock(); + if (mStatus == RUNNING) + { + mStatus = QUITTING; + } + // It's only safe to remove mRunCondition if all locked threads were notified + mRunCondition->broadcast(); + mDataLock->unlock(); +} + +// static +LLThread::id_t LLThread::currentID() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + return std::this_thread::get_id(); +} + +// static +void LLThread::yield() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + std::this_thread::yield(); +} + +void LLThread::wake() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mDataLock->lock(); + if(!shouldSleep()) + { + mRunCondition->signal(); + } + mDataLock->unlock(); +} + +void LLThread::wakeLocked() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + if(!shouldSleep()) + { + mRunCondition->signal(); + } +} + +void LLThread::lockData() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mDataLock->lock(); +} + +void LLThread::unlockData() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD + mDataLock->unlock(); +} + +//============================================================================ + +//---------------------------------------------------------------------------- + +//static +LLMutex* LLThreadSafeRefCount::sMutex = 0; + +//static +void LLThreadSafeRefCount::initThreadSafeRefCount() +{ + if (!sMutex) + { + sMutex = new LLMutex(); + } +} + +//static +void LLThreadSafeRefCount::cleanupThreadSafeRefCount() +{ + delete sMutex; + sMutex = NULL; +} + + +//---------------------------------------------------------------------------- + +LLThreadSafeRefCount::LLThreadSafeRefCount() : + mRef(0) +{ +} + +LLThreadSafeRefCount::LLThreadSafeRefCount(const LLThreadSafeRefCount& src) +{ + mRef = 0; +} + +LLThreadSafeRefCount::~LLThreadSafeRefCount() +{ + if (mRef != 0) + { + LL_ERRS() << "deleting referenced object mRef = " << mRef << LL_ENDL; + } +} + +//============================================================================ + +LLResponder::~LLResponder() +{ +} + +//============================================================================ diff --git a/indra/llcommon/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 <chrono>
-#include <thread>
-
-#if LL_WINDOWS
-# include "llwin32headerslean.h"
-#elif LL_LINUX || LL_DARWIN
-# include <errno.h>
-# include <sys/time.h>
-#else
-# error "architecture not supported"
-#endif
-
-//
-// Locally used constants
-//
-const U64 SEC_TO_MICROSEC_U64 = 1000000;
-
-//---------------------------------------------------------------------------
-// Globals and statics
-//---------------------------------------------------------------------------
-
-S32 gUTCOffset = 0; // viewer's offset from server UTC, in seconds
-LLTimer* LLTimer::sTimer = NULL;
-
-
-//
-// Forward declarations
-//
-
-
-//---------------------------------------------------------------------------
-// Implementation
-//---------------------------------------------------------------------------
-
-#if LL_WINDOWS
-
-
-#if 0
-void ms_sleep(U32 ms)
-{
- LL_PROFILE_ZONE_SCOPED;
- using TimePoint = std::chrono::steady_clock::time_point;
- auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms);
- while (TimePoint::clock::now() < resume_time)
- {
- std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long
- }
-}
-
-U32 micro_sleep(U64 us, U32 max_yields)
-{
- // max_yields is unused; just fiddle with it to avoid warnings.
- max_yields = 0;
- ms_sleep((U32)(us / 1000));
- return 0;
-}
-
-#else
-
-U32 micro_sleep(U64 us, U32 max_yields)
-{
- LL_PROFILE_ZONE_SCOPED
-#if 0
- LARGE_INTEGER ft;
- ft.QuadPart = -static_cast<S64>(us * 10); // '-' using relative time
-
- HANDLE timer = CreateWaitableTimer(NULL, true, NULL);
- SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
- WaitForSingleObject(timer, INFINITE);
- CloseHandle(timer);
-#else
- Sleep(us / 1000);
-#endif
-
- return 0;
-}
-
-void ms_sleep(U32 ms)
-{
- LL_PROFILE_ZONE_SCOPED
- micro_sleep(ms * 1000, 0);
-}
-
-#endif
-
-#elif LL_LINUX || LL_DARWIN
-static void _sleep_loop(struct timespec& thiswait)
-{
- struct timespec nextwait;
- bool sleep_more = false;
-
- do {
- int result = nanosleep(&thiswait, &nextwait);
-
- // check if sleep was interrupted by a signal; unslept
- // remainder was written back into 't' and we just nanosleep
- // again.
- sleep_more = (result == -1 && EINTR == errno);
-
- if (sleep_more)
- {
- if ( nextwait.tv_sec > thiswait.tv_sec ||
- (nextwait.tv_sec == thiswait.tv_sec &&
- nextwait.tv_nsec >= thiswait.tv_nsec) )
- {
- // if the remaining time isn't actually going
- // down then we're being shafted by low clock
- // resolution - manually massage the sleep time
- // downward.
- if (nextwait.tv_nsec > 1000000) {
- // lose 1ms
- nextwait.tv_nsec -= 1000000;
- } else {
- if (nextwait.tv_sec == 0) {
- // already so close to finished
- sleep_more = false;
- } else {
- // lose up to 1ms
- nextwait.tv_nsec = 0;
- }
- }
- }
- thiswait = nextwait;
- }
- } while (sleep_more);
-}
-
-U32 micro_sleep(U64 us, U32 max_yields)
-{
- U64 start = get_clock_count();
- // This is kernel dependent. Currently, our kernel generates software clock
- // interrupts at 250 Hz (every 4,000 microseconds).
- const S64 KERNEL_SLEEP_INTERVAL_US = 4000;
-
- // Use signed arithmetic to discover whether a sleep is even necessary. If
- // either 'us' or KERNEL_SLEEP_INTERVAL_US is unsigned, the compiler
- // promotes the difference to unsigned. If 'us' is less than half
- // KERNEL_SLEEP_INTERVAL_US, the unsigned difference will be hugely
- // positive, resulting in a crazy long wait.
- auto num_sleep_intervals = (S64(us) - (KERNEL_SLEEP_INTERVAL_US >> 1)) / KERNEL_SLEEP_INTERVAL_US;
- if (num_sleep_intervals > 0)
- {
- U64 sleep_time = (num_sleep_intervals * KERNEL_SLEEP_INTERVAL_US) - (KERNEL_SLEEP_INTERVAL_US >> 1);
- struct timespec thiswait;
- thiswait.tv_sec = sleep_time / 1000000;
- thiswait.tv_nsec = (sleep_time % 1000000) * 1000l;
- _sleep_loop(thiswait);
- }
-
- U64 current_clock = get_clock_count();
- U32 yields = 0;
- while ( (yields < max_yields)
- && (current_clock - start < us) )
- {
- sched_yield();
- ++yields;
- current_clock = get_clock_count();
- }
- return yields;
-}
-
-void ms_sleep(U32 ms)
-{
- long mslong = ms; // tv_nsec is a long
- struct timespec thiswait;
- thiswait.tv_sec = ms / 1000;
- thiswait.tv_nsec = (mslong % 1000) * 1000000l;
- _sleep_loop(thiswait);
-}
-#else
-# error "architecture not supported"
-#endif
-
-//
-// CPU clock/other clock frequency and count functions
-//
-
-#if LL_WINDOWS
-U64 get_clock_count()
-{
- static bool firstTime = true;
- static U64 offset;
- // ensures that callers to this function never have to deal with wrap
-
- // QueryPerformanceCounter implementation
- LARGE_INTEGER clock_count;
- QueryPerformanceCounter(&clock_count);
- if (firstTime) {
- offset = clock_count.QuadPart;
- firstTime = false;
- }
- return clock_count.QuadPart - offset;
-}
-
-F64 calc_clock_frequency()
-{
- __int64 freq;
- QueryPerformanceFrequency((LARGE_INTEGER *) &freq);
- return (F64)freq;
-}
-#endif // LL_WINDOWS
-
-
-#if LL_LINUX || LL_DARWIN
-// Both Linux and Mac use gettimeofday for accurate time
-F64 calc_clock_frequency()
-{
- return 1000000.0; // microseconds, so 1 MHz.
-}
-
-U64 get_clock_count()
-{
- // Linux clocks are in microseconds
- struct timeval tv;
- gettimeofday(&tv, NULL);
- return tv.tv_sec*SEC_TO_MICROSEC_U64 + tv.tv_usec;
-}
-#endif
-
-
-TimerInfo::TimerInfo()
-: mClockFrequency(0.0),
- mTotalTimeClockCount(0),
- mLastTotalTimeClockCount(0)
-{}
-
-void TimerInfo::update()
-{
- mClockFrequency = calc_clock_frequency();
- mClockFrequencyInv = 1.0/mClockFrequency;
- mClocksToMicroseconds = mClockFrequencyInv;
-}
-
-TimerInfo& get_timer_info()
-{
- static TimerInfo sTimerInfo;
- return sTimerInfo;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-// returns a U64 number that represents the number of
-// microseconds since the Unix epoch - Jan 1, 1970
-U64MicrosecondsImplicit totalTime()
-{
- U64 current_clock_count = get_clock_count();
- if (!get_timer_info().mTotalTimeClockCount || get_timer_info().mClocksToMicroseconds.value() == 0)
- {
- get_timer_info().update();
- get_timer_info().mTotalTimeClockCount = current_clock_count;
-
-#if LL_WINDOWS
- // Sync us up with local time (even though we PROBABLY don't need to, this is how it was implemented)
- // Unix platforms use gettimeofday so they are synced, although this probably isn't a good assumption to
- // make in the future.
-
- get_timer_info().mTotalTimeClockCount = (U64)(time(NULL) * get_timer_info().mClockFrequency);
-#endif
-
- // Update the last clock count
- get_timer_info().mLastTotalTimeClockCount = current_clock_count;
- }
- else
- {
- if (current_clock_count >= get_timer_info().mLastTotalTimeClockCount)
- {
- // No wrapping, we're all okay.
- get_timer_info().mTotalTimeClockCount += current_clock_count - get_timer_info().mLastTotalTimeClockCount;
- }
- else
- {
- // We've wrapped. Compensate correctly
- get_timer_info().mTotalTimeClockCount += (0xFFFFFFFFFFFFFFFFULL - get_timer_info().mLastTotalTimeClockCount) + current_clock_count;
- }
-
- // Update the last clock count
- get_timer_info().mLastTotalTimeClockCount = current_clock_count;
- }
-
- // Return the total clock tick count in microseconds.
- U64Microseconds time(get_timer_info().mTotalTimeClockCount*get_timer_info().mClocksToMicroseconds);
- return time;
-}
-
-
-///////////////////////////////////////////////////////////////////////////////
-
-LLTimer::LLTimer()
-{
- if (!get_timer_info().mClockFrequency)
- {
- get_timer_info().update();
- }
-
- mStarted = true;
- reset();
-}
-
-LLTimer::~LLTimer()
-{}
-
-// static
-void LLTimer::initClass()
-{
- if (!sTimer) sTimer = new LLTimer;
-}
-
-// static
-void LLTimer::cleanupClass()
-{
- delete sTimer; sTimer = NULL;
-}
-
-// static
-U64MicrosecondsImplicit LLTimer::getTotalTime()
-{
- // simply call into the implementation function.
- U64MicrosecondsImplicit total_time = totalTime();
- return total_time;
-}
-
-// static
-F64SecondsImplicit LLTimer::getTotalSeconds()
-{
- return F64Microseconds(U64_to_F64(getTotalTime()));
-}
-
-void LLTimer::reset()
-{
- mLastClockCount = get_clock_count();
- mExpirationTicks = 0;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-U64 LLTimer::getCurrentClockCount()
-{
- return get_clock_count();
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-void LLTimer::setLastClockCount(U64 current_count)
-{
- mLastClockCount = current_count;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-static
-U64 getElapsedTimeAndUpdate(U64& lastClockCount)
-{
- U64 current_clock_count = get_clock_count();
- U64 result;
-
- if (current_clock_count >= lastClockCount)
- {
- result = current_clock_count - lastClockCount;
- }
- else
- {
- // time has gone backward
- result = 0;
- }
-
- lastClockCount = current_clock_count;
-
- return result;
-}
-
-
-F64SecondsImplicit LLTimer::getElapsedTimeF64() const
-{
- U64 last = mLastClockCount;
- return (F64)getElapsedTimeAndUpdate(last) * get_timer_info().mClockFrequencyInv;
-}
-
-F32SecondsImplicit LLTimer::getElapsedTimeF32() const
-{
- return (F32)getElapsedTimeF64();
-}
-
-F64SecondsImplicit LLTimer::getElapsedTimeAndResetF64()
-{
- return (F64)getElapsedTimeAndUpdate(mLastClockCount) * get_timer_info().mClockFrequencyInv;
-}
-
-F32SecondsImplicit LLTimer::getElapsedTimeAndResetF32()
-{
- return (F32)getElapsedTimeAndResetF64();
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-void LLTimer::setTimerExpirySec(F32SecondsImplicit expiration)
-{
- mExpirationTicks = get_clock_count()
- + (U64)((F32)(expiration * get_timer_info().mClockFrequency.value()));
-}
-
-F32SecondsImplicit LLTimer::getRemainingTimeF32() const
-{
- U64 cur_ticks = get_clock_count();
- if (cur_ticks > mExpirationTicks)
- {
- return 0.0f;
- }
- return F32((mExpirationTicks - cur_ticks) * get_timer_info().mClockFrequencyInv);
-}
-
-
-bool LLTimer::checkExpirationAndReset(F32 expiration)
-{
- U64 cur_ticks = get_clock_count();
- if (cur_ticks < mExpirationTicks)
- {
- return false;
- }
-
- mExpirationTicks = cur_ticks
- + (U64)((F32)(expiration * get_timer_info().mClockFrequency));
- return true;
-}
-
-
-bool LLTimer::hasExpired() const
-{
- return get_clock_count() >= mExpirationTicks;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-bool LLTimer::knownBadTimer()
-{
- bool failed = false;
-
-#if LL_WINDOWS
- WCHAR bad_pci_list[][10] = {L"1039:0530",
- L"1039:0620",
- L"10B9:0533",
- L"10B9:1533",
- L"1106:0596",
- L"1106:0686",
- L"1166:004F",
- L"1166:0050",
- L"8086:7110",
- L"\0"
- };
-
- HKEY hKey = NULL;
- LONG nResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SYSTEM\\CurrentControlSet\\Enum\\PCI", 0,
- KEY_EXECUTE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey);
-
- WCHAR name[1024];
- DWORD name_len = 1024;
- FILETIME scrap;
-
- S32 key_num = 0;
- WCHAR pci_id[10];
-
- wcscpy(pci_id, L"0000:0000"); /*Flawfinder: ignore*/
-
- while (nResult == ERROR_SUCCESS)
- {
- nResult = ::RegEnumKeyEx(hKey, key_num++, name, &name_len, NULL, NULL, NULL, &scrap);
-
- if (nResult == ERROR_SUCCESS)
- {
- memcpy(&pci_id[0],&name[4],4); /* Flawfinder: ignore */
- memcpy(&pci_id[5],&name[13],4); /* Flawfinder: ignore */
-
- for (S32 check = 0; bad_pci_list[check][0]; check++)
- {
- if (!wcscmp(pci_id, bad_pci_list[check]))
- {
-// LL_WARNS() << "unreliable PCI chipset found!! " << pci_id << endl;
- failed = true;
- break;
- }
- }
-// llinfo << "PCI chipset found: " << pci_id << endl;
- name_len = 1024;
- }
- }
-#endif
- return(failed);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-//
-// NON-MEMBER FUNCTIONS
-//
-///////////////////////////////////////////////////////////////////////////////
-
-time_t time_corrected()
-{
- return time(NULL) + gUTCOffset;
-}
-
-
-// Is the current computer (in its current time zone)
-// observing daylight savings time?
-bool is_daylight_savings()
-{
- time_t now = time(NULL);
-
- // Internal buffer to local server time
- struct tm* internal_time = localtime(&now);
-
- // tm_isdst > 0 => daylight savings
- // tm_isdst = 0 => not daylight savings
- // tm_isdst < 0 => can't tell
- return (internal_time->tm_isdst > 0);
-}
-
-
-struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time)
-{
- S32Hours pacific_offset_hours;
- if (pacific_daylight_time)
- {
- pacific_offset_hours = S32Hours(7);
- }
- else
- {
- pacific_offset_hours = S32Hours(8);
- }
-
- // We subtract off the PST/PDT offset _before_ getting
- // "UTC" time, because this will handle wrapping around
- // for 5 AM UTC -> 10 PM PDT of the previous day.
- utc_time -= S32SecondsImplicit(pacific_offset_hours);
-
- // Internal buffer to PST/PDT (see above)
- struct tm* internal_time = gmtime(&utc_time);
-
- /*
- // Don't do this, this won't correctly tell you if daylight savings is active in CA or not.
- if (pacific_daylight_time)
- {
- internal_time->tm_isdst = 1;
- }
- */
-
- return internal_time;
-}
-
-
-void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring)
-{
- U64 hours;
- U64 minutes;
- U64 seconds;
- U64 frames;
- U64 subframes;
-
- hours = current_time / (U64)3600000000ul;
- minutes = current_time / (U64)60000000;
- minutes %= 60;
- seconds = current_time / (U64)1000000;
- seconds %= 60;
- frames = current_time / (U64)41667;
- frames %= 24;
- subframes = current_time / (U64)42;
- subframes %= 100;
-
- tcstring = llformat("%3.3d:%2.2d:%2.2d:%2.2d.%2.2d",(int)hours,(int)minutes,(int)seconds,(int)frames,(int)subframes);
-}
-
-
-void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring)
-{
- microsecondsToTimecodeString(current_time, tcstring);
-}
-
-
+/** + * @file lltimer.cpp + * @brief Cross-platform objects for doing timing + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lltimer.h" + +#include "u64.h" + +#include <chrono> +#include <thread> + +#if LL_WINDOWS +# include "llwin32headerslean.h" +#elif LL_LINUX || LL_DARWIN +# include <errno.h> +# include <sys/time.h> +#else +# error "architecture not supported" +#endif + +// +// Locally used constants +// +const U64 SEC_TO_MICROSEC_U64 = 1000000; + +//--------------------------------------------------------------------------- +// Globals and statics +//--------------------------------------------------------------------------- + +S32 gUTCOffset = 0; // viewer's offset from server UTC, in seconds +LLTimer* LLTimer::sTimer = NULL; + + +// +// Forward declarations +// + + +//--------------------------------------------------------------------------- +// Implementation +//--------------------------------------------------------------------------- + +#if LL_WINDOWS + + +#if 0 +void ms_sleep(U32 ms) +{ + LL_PROFILE_ZONE_SCOPED; + using TimePoint = std::chrono::steady_clock::time_point; + auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms); + while (TimePoint::clock::now() < resume_time) + { + std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long + } +} + +U32 micro_sleep(U64 us, U32 max_yields) +{ + // max_yields is unused; just fiddle with it to avoid warnings. + max_yields = 0; + ms_sleep((U32)(us / 1000)); + return 0; +} + +#else + +U32 micro_sleep(U64 us, U32 max_yields) +{ + LL_PROFILE_ZONE_SCOPED +#if 0 + LARGE_INTEGER ft; + ft.QuadPart = -static_cast<S64>(us * 10); // '-' using relative time + + HANDLE timer = CreateWaitableTimer(NULL, true, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); +#else + Sleep(us / 1000); +#endif + + return 0; +} + +void ms_sleep(U32 ms) +{ + LL_PROFILE_ZONE_SCOPED + micro_sleep(ms * 1000, 0); +} + +#endif + +#elif LL_LINUX || LL_DARWIN +static void _sleep_loop(struct timespec& thiswait) +{ + struct timespec nextwait; + bool sleep_more = false; + + do { + int result = nanosleep(&thiswait, &nextwait); + + // check if sleep was interrupted by a signal; unslept + // remainder was written back into 't' and we just nanosleep + // again. + sleep_more = (result == -1 && EINTR == errno); + + if (sleep_more) + { + if ( nextwait.tv_sec > thiswait.tv_sec || + (nextwait.tv_sec == thiswait.tv_sec && + nextwait.tv_nsec >= thiswait.tv_nsec) ) + { + // if the remaining time isn't actually going + // down then we're being shafted by low clock + // resolution - manually massage the sleep time + // downward. + if (nextwait.tv_nsec > 1000000) { + // lose 1ms + nextwait.tv_nsec -= 1000000; + } else { + if (nextwait.tv_sec == 0) { + // already so close to finished + sleep_more = false; + } else { + // lose up to 1ms + nextwait.tv_nsec = 0; + } + } + } + thiswait = nextwait; + } + } while (sleep_more); +} + +U32 micro_sleep(U64 us, U32 max_yields) +{ + U64 start = get_clock_count(); + // This is kernel dependent. Currently, our kernel generates software clock + // interrupts at 250 Hz (every 4,000 microseconds). + const S64 KERNEL_SLEEP_INTERVAL_US = 4000; + + // Use signed arithmetic to discover whether a sleep is even necessary. If + // either 'us' or KERNEL_SLEEP_INTERVAL_US is unsigned, the compiler + // promotes the difference to unsigned. If 'us' is less than half + // KERNEL_SLEEP_INTERVAL_US, the unsigned difference will be hugely + // positive, resulting in a crazy long wait. + auto num_sleep_intervals = (S64(us) - (KERNEL_SLEEP_INTERVAL_US >> 1)) / KERNEL_SLEEP_INTERVAL_US; + if (num_sleep_intervals > 0) + { + U64 sleep_time = (num_sleep_intervals * KERNEL_SLEEP_INTERVAL_US) - (KERNEL_SLEEP_INTERVAL_US >> 1); + struct timespec thiswait; + thiswait.tv_sec = sleep_time / 1000000; + thiswait.tv_nsec = (sleep_time % 1000000) * 1000l; + _sleep_loop(thiswait); + } + + U64 current_clock = get_clock_count(); + U32 yields = 0; + while ( (yields < max_yields) + && (current_clock - start < us) ) + { + sched_yield(); + ++yields; + current_clock = get_clock_count(); + } + return yields; +} + +void ms_sleep(U32 ms) +{ + long mslong = ms; // tv_nsec is a long + struct timespec thiswait; + thiswait.tv_sec = ms / 1000; + thiswait.tv_nsec = (mslong % 1000) * 1000000l; + _sleep_loop(thiswait); +} +#else +# error "architecture not supported" +#endif + +// +// CPU clock/other clock frequency and count functions +// + +#if LL_WINDOWS +U64 get_clock_count() +{ + static bool firstTime = true; + static U64 offset; + // ensures that callers to this function never have to deal with wrap + + // QueryPerformanceCounter implementation + LARGE_INTEGER clock_count; + QueryPerformanceCounter(&clock_count); + if (firstTime) { + offset = clock_count.QuadPart; + firstTime = false; + } + return clock_count.QuadPart - offset; +} + +F64 calc_clock_frequency() +{ + __int64 freq; + QueryPerformanceFrequency((LARGE_INTEGER *) &freq); + return (F64)freq; +} +#endif // LL_WINDOWS + + +#if LL_LINUX || LL_DARWIN +// Both Linux and Mac use gettimeofday for accurate time +F64 calc_clock_frequency() +{ + return 1000000.0; // microseconds, so 1 MHz. +} + +U64 get_clock_count() +{ + // Linux clocks are in microseconds + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec*SEC_TO_MICROSEC_U64 + tv.tv_usec; +} +#endif + + +TimerInfo::TimerInfo() +: mClockFrequency(0.0), + mTotalTimeClockCount(0), + mLastTotalTimeClockCount(0) +{} + +void TimerInfo::update() +{ + mClockFrequency = calc_clock_frequency(); + mClockFrequencyInv = 1.0/mClockFrequency; + mClocksToMicroseconds = mClockFrequencyInv; +} + +TimerInfo& get_timer_info() +{ + static TimerInfo sTimerInfo; + return sTimerInfo; +} + +/////////////////////////////////////////////////////////////////////////////// + +// returns a U64 number that represents the number of +// microseconds since the Unix epoch - Jan 1, 1970 +U64MicrosecondsImplicit totalTime() +{ + U64 current_clock_count = get_clock_count(); + if (!get_timer_info().mTotalTimeClockCount || get_timer_info().mClocksToMicroseconds.value() == 0) + { + get_timer_info().update(); + get_timer_info().mTotalTimeClockCount = current_clock_count; + +#if LL_WINDOWS + // Sync us up with local time (even though we PROBABLY don't need to, this is how it was implemented) + // Unix platforms use gettimeofday so they are synced, although this probably isn't a good assumption to + // make in the future. + + get_timer_info().mTotalTimeClockCount = (U64)(time(NULL) * get_timer_info().mClockFrequency); +#endif + + // Update the last clock count + get_timer_info().mLastTotalTimeClockCount = current_clock_count; + } + else + { + if (current_clock_count >= get_timer_info().mLastTotalTimeClockCount) + { + // No wrapping, we're all okay. + get_timer_info().mTotalTimeClockCount += current_clock_count - get_timer_info().mLastTotalTimeClockCount; + } + else + { + // We've wrapped. Compensate correctly + get_timer_info().mTotalTimeClockCount += (0xFFFFFFFFFFFFFFFFULL - get_timer_info().mLastTotalTimeClockCount) + current_clock_count; + } + + // Update the last clock count + get_timer_info().mLastTotalTimeClockCount = current_clock_count; + } + + // Return the total clock tick count in microseconds. + U64Microseconds time(get_timer_info().mTotalTimeClockCount*get_timer_info().mClocksToMicroseconds); + return time; +} + + +/////////////////////////////////////////////////////////////////////////////// + +LLTimer::LLTimer() +{ + if (!get_timer_info().mClockFrequency) + { + get_timer_info().update(); + } + + mStarted = true; + reset(); +} + +LLTimer::~LLTimer() +{} + +// static +void LLTimer::initClass() +{ + if (!sTimer) sTimer = new LLTimer; +} + +// static +void LLTimer::cleanupClass() +{ + delete sTimer; sTimer = NULL; +} + +// static +U64MicrosecondsImplicit LLTimer::getTotalTime() +{ + // simply call into the implementation function. + U64MicrosecondsImplicit total_time = totalTime(); + return total_time; +} + +// static +F64SecondsImplicit LLTimer::getTotalSeconds() +{ + return F64Microseconds(U64_to_F64(getTotalTime())); +} + +void LLTimer::reset() +{ + mLastClockCount = get_clock_count(); + mExpirationTicks = 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +U64 LLTimer::getCurrentClockCount() +{ + return get_clock_count(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void LLTimer::setLastClockCount(U64 current_count) +{ + mLastClockCount = current_count; +} + +/////////////////////////////////////////////////////////////////////////////// + +static +U64 getElapsedTimeAndUpdate(U64& lastClockCount) +{ + U64 current_clock_count = get_clock_count(); + U64 result; + + if (current_clock_count >= lastClockCount) + { + result = current_clock_count - lastClockCount; + } + else + { + // time has gone backward + result = 0; + } + + lastClockCount = current_clock_count; + + return result; +} + + +F64SecondsImplicit LLTimer::getElapsedTimeF64() const +{ + U64 last = mLastClockCount; + return (F64)getElapsedTimeAndUpdate(last) * get_timer_info().mClockFrequencyInv; +} + +F32SecondsImplicit LLTimer::getElapsedTimeF32() const +{ + return (F32)getElapsedTimeF64(); +} + +F64SecondsImplicit LLTimer::getElapsedTimeAndResetF64() +{ + return (F64)getElapsedTimeAndUpdate(mLastClockCount) * get_timer_info().mClockFrequencyInv; +} + +F32SecondsImplicit LLTimer::getElapsedTimeAndResetF32() +{ + return (F32)getElapsedTimeAndResetF64(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void LLTimer::setTimerExpirySec(F32SecondsImplicit expiration) +{ + mExpirationTicks = get_clock_count() + + (U64)((F32)(expiration * get_timer_info().mClockFrequency.value())); +} + +F32SecondsImplicit LLTimer::getRemainingTimeF32() const +{ + U64 cur_ticks = get_clock_count(); + if (cur_ticks > mExpirationTicks) + { + return 0.0f; + } + return F32((mExpirationTicks - cur_ticks) * get_timer_info().mClockFrequencyInv); +} + + +bool LLTimer::checkExpirationAndReset(F32 expiration) +{ + U64 cur_ticks = get_clock_count(); + if (cur_ticks < mExpirationTicks) + { + return false; + } + + mExpirationTicks = cur_ticks + + (U64)((F32)(expiration * get_timer_info().mClockFrequency)); + return true; +} + + +bool LLTimer::hasExpired() const +{ + return get_clock_count() >= mExpirationTicks; +} + +/////////////////////////////////////////////////////////////////////////////// + +bool LLTimer::knownBadTimer() +{ + bool failed = false; + +#if LL_WINDOWS + WCHAR bad_pci_list[][10] = {L"1039:0530", + L"1039:0620", + L"10B9:0533", + L"10B9:1533", + L"1106:0596", + L"1106:0686", + L"1166:004F", + L"1166:0050", + L"8086:7110", + L"\0" + }; + + HKEY hKey = NULL; + LONG nResult = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE,L"SYSTEM\\CurrentControlSet\\Enum\\PCI", 0, + KEY_EXECUTE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &hKey); + + WCHAR name[1024]; + DWORD name_len = 1024; + FILETIME scrap; + + S32 key_num = 0; + WCHAR pci_id[10]; + + wcscpy(pci_id, L"0000:0000"); /*Flawfinder: ignore*/ + + while (nResult == ERROR_SUCCESS) + { + nResult = ::RegEnumKeyEx(hKey, key_num++, name, &name_len, NULL, NULL, NULL, &scrap); + + if (nResult == ERROR_SUCCESS) + { + memcpy(&pci_id[0],&name[4],4); /* Flawfinder: ignore */ + memcpy(&pci_id[5],&name[13],4); /* Flawfinder: ignore */ + + for (S32 check = 0; bad_pci_list[check][0]; check++) + { + if (!wcscmp(pci_id, bad_pci_list[check])) + { +// LL_WARNS() << "unreliable PCI chipset found!! " << pci_id << endl; + failed = true; + break; + } + } +// llinfo << "PCI chipset found: " << pci_id << endl; + name_len = 1024; + } + } +#endif + return(failed); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// NON-MEMBER FUNCTIONS +// +/////////////////////////////////////////////////////////////////////////////// + +time_t time_corrected() +{ + return time(NULL) + gUTCOffset; +} + + +// Is the current computer (in its current time zone) +// observing daylight savings time? +bool is_daylight_savings() +{ + time_t now = time(NULL); + + // Internal buffer to local server time + struct tm* internal_time = localtime(&now); + + // tm_isdst > 0 => daylight savings + // tm_isdst = 0 => not daylight savings + // tm_isdst < 0 => can't tell + return (internal_time->tm_isdst > 0); +} + + +struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time) +{ + S32Hours pacific_offset_hours; + if (pacific_daylight_time) + { + pacific_offset_hours = S32Hours(7); + } + else + { + pacific_offset_hours = S32Hours(8); + } + + // We subtract off the PST/PDT offset _before_ getting + // "UTC" time, because this will handle wrapping around + // for 5 AM UTC -> 10 PM PDT of the previous day. + utc_time -= S32SecondsImplicit(pacific_offset_hours); + + // Internal buffer to PST/PDT (see above) + struct tm* internal_time = gmtime(&utc_time); + + /* + // Don't do this, this won't correctly tell you if daylight savings is active in CA or not. + if (pacific_daylight_time) + { + internal_time->tm_isdst = 1; + } + */ + + return internal_time; +} + + +void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring) +{ + U64 hours; + U64 minutes; + U64 seconds; + U64 frames; + U64 subframes; + + hours = current_time / (U64)3600000000ul; + minutes = current_time / (U64)60000000; + minutes %= 60; + seconds = current_time / (U64)1000000; + seconds %= 60; + frames = current_time / (U64)41667; + frames %= 24; + subframes = current_time / (U64)42; + subframes %= 100; + + tcstring = llformat("%3.3d:%2.2d:%2.2d:%2.2d.%2.2d",(int)hours,(int)minutes,(int)seconds,(int)frames,(int)subframes); +} + + +void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring) +{ + microsecondsToTimecodeString(current_time, tcstring); +} + + diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index 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 <sys/time.h>
-#endif
-#include <limits.h>
-
-#include "stdtypes.h"
-
-#include <string>
-#include <list>
-// units conversions
-#include "llunits.h"
-#ifndef USEC_PER_SEC
- const U32 USEC_PER_SEC = 1000000;
-#endif
-const U32 SEC_PER_MIN = 60;
-const U32 MIN_PER_HOUR = 60;
-const U32 USEC_PER_MIN = USEC_PER_SEC * SEC_PER_MIN;
-const U32 USEC_PER_HOUR = USEC_PER_MIN * MIN_PER_HOUR;
-const U32 SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR;
-const F64 SEC_PER_USEC = 1.0 / (F64) USEC_PER_SEC;
-
-class LL_COMMON_API LLTimer
-{
-public:
- static LLTimer *sTimer; // global timer
-
-protected:
- U64 mLastClockCount;
- U64 mExpirationTicks;
- bool mStarted;
-
-public:
- LLTimer();
- ~LLTimer();
-
- static void initClass();
- static void cleanupClass();
-
- // Return a high precision number of seconds since the start of
- // this application instance.
- static F64SecondsImplicit getElapsedSeconds()
- {
- if (sTimer)
- {
- return sTimer->getElapsedTimeF64();
- }
- else
- {
- return 0;
- }
- }
-
- // Return a high precision usec since epoch
- static U64MicrosecondsImplicit getTotalTime();
-
- // Return a high precision seconds since epoch
- static F64SecondsImplicit getTotalSeconds();
-
-
- // MANIPULATORS
- void start() { reset(); mStarted = true; }
- void stop() { mStarted = false; }
- void reset(); // Resets the timer
- void setLastClockCount(U64 current_count); // Sets the timer so that the next elapsed call will be relative to this time
- void setTimerExpirySec(F32SecondsImplicit expiration);
- bool checkExpirationAndReset(F32 expiration);
- bool hasExpired() const;
- F32SecondsImplicit getElapsedTimeAndResetF32(); // Returns elapsed time in seconds with reset
- F64SecondsImplicit getElapsedTimeAndResetF64();
-
- F32SecondsImplicit getRemainingTimeF32() const;
-
- static bool knownBadTimer();
-
- // ACCESSORS
- F32SecondsImplicit getElapsedTimeF32() const; // Returns elapsed time in seconds
- F64SecondsImplicit getElapsedTimeF64() const; // Returns elapsed time in seconds
-
- bool getStarted() const { return mStarted; }
-
-
- static U64 getCurrentClockCount(); // Returns the raw clockticks
-};
-
-//
-// Various functions for initializing/accessing clock and timing stuff. Don't use these without REALLY knowing how they work.
-//
-struct TimerInfo
-{
- TimerInfo();
- void update();
-
- F64HertzImplicit mClockFrequency;
- F64SecondsImplicit mClockFrequencyInv;
- F64MicrosecondsImplicit mClocksToMicroseconds;
- U64 mTotalTimeClockCount;
- U64 mLastTotalTimeClockCount;
-};
-
-TimerInfo& get_timer_info();
-
-LL_COMMON_API U64 get_clock_count();
-
-// Sleep for milliseconds
-LL_COMMON_API void ms_sleep(U32 ms);
-LL_COMMON_API U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF);
-
-// Returns the correct UTC time in seconds, like time(NULL).
-// Useful on the viewer, which may have its local clock set wrong.
-LL_COMMON_API time_t time_corrected();
-
-static inline time_t time_min()
-{
- if (sizeof(time_t) == 4)
- {
- return (time_t) INT_MIN;
- } else {
-#ifdef LLONG_MIN
- return (time_t) LLONG_MIN;
-#else
- return (time_t) LONG_MIN;
-#endif
- }
-}
-
-static inline time_t time_max()
-{
- if (sizeof(time_t) == 4)
- {
- return (time_t) INT_MAX;
- } else {
-#ifdef LLONG_MAX
- return (time_t) LLONG_MAX;
-#else
- return (time_t) LONG_MAX;
-#endif
- }
-}
-
-// Correction factor used by time_corrected() above.
-extern LL_COMMON_API S32 gUTCOffset;
-
-// Is the current computer (in its current time zone)
-// observing daylight savings time?
-LL_COMMON_API bool is_daylight_savings();
-
-// Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time
-// Usage:
-// S32 utc_time;
-// utc_time = time_corrected();
-// struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight);
-LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time);
-
-LL_COMMON_API void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring);
-LL_COMMON_API void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring);
-
-U64MicrosecondsImplicit LL_COMMON_API totalTime(); // Returns current system time in microseconds
-
-#endif
+/** + * @file lltimer.h + * @brief Cross-platform objects for doing timing + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TIMER_H +#define LL_TIMER_H + +#if LL_LINUX || LL_DARWIN +#include <sys/time.h> +#endif +#include <limits.h> + +#include "stdtypes.h" + +#include <string> +#include <list> +// units conversions +#include "llunits.h" +#ifndef USEC_PER_SEC + const U32 USEC_PER_SEC = 1000000; +#endif +const U32 SEC_PER_MIN = 60; +const U32 MIN_PER_HOUR = 60; +const U32 USEC_PER_MIN = USEC_PER_SEC * SEC_PER_MIN; +const U32 USEC_PER_HOUR = USEC_PER_MIN * MIN_PER_HOUR; +const U32 SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR; +const F64 SEC_PER_USEC = 1.0 / (F64) USEC_PER_SEC; + +class LL_COMMON_API LLTimer +{ +public: + static LLTimer *sTimer; // global timer + +protected: + U64 mLastClockCount; + U64 mExpirationTicks; + bool mStarted; + +public: + LLTimer(); + ~LLTimer(); + + static void initClass(); + static void cleanupClass(); + + // Return a high precision number of seconds since the start of + // this application instance. + static F64SecondsImplicit getElapsedSeconds() + { + if (sTimer) + { + return sTimer->getElapsedTimeF64(); + } + else + { + return 0; + } + } + + // Return a high precision usec since epoch + static U64MicrosecondsImplicit getTotalTime(); + + // Return a high precision seconds since epoch + static F64SecondsImplicit getTotalSeconds(); + + + // MANIPULATORS + void start() { reset(); mStarted = true; } + void stop() { mStarted = false; } + void reset(); // Resets the timer + void setLastClockCount(U64 current_count); // Sets the timer so that the next elapsed call will be relative to this time + void setTimerExpirySec(F32SecondsImplicit expiration); + bool checkExpirationAndReset(F32 expiration); + bool hasExpired() const; + F32SecondsImplicit getElapsedTimeAndResetF32(); // Returns elapsed time in seconds with reset + F64SecondsImplicit getElapsedTimeAndResetF64(); + + F32SecondsImplicit getRemainingTimeF32() const; + + static bool knownBadTimer(); + + // ACCESSORS + F32SecondsImplicit getElapsedTimeF32() const; // Returns elapsed time in seconds + F64SecondsImplicit getElapsedTimeF64() const; // Returns elapsed time in seconds + + bool getStarted() const { return mStarted; } + + + static U64 getCurrentClockCount(); // Returns the raw clockticks +}; + +// +// Various functions for initializing/accessing clock and timing stuff. Don't use these without REALLY knowing how they work. +// +struct TimerInfo +{ + TimerInfo(); + void update(); + + F64HertzImplicit mClockFrequency; + F64SecondsImplicit mClockFrequencyInv; + F64MicrosecondsImplicit mClocksToMicroseconds; + U64 mTotalTimeClockCount; + U64 mLastTotalTimeClockCount; +}; + +TimerInfo& get_timer_info(); + +LL_COMMON_API U64 get_clock_count(); + +// Sleep for milliseconds +LL_COMMON_API void ms_sleep(U32 ms); +LL_COMMON_API U32 micro_sleep(U64 us, U32 max_yields = 0xFFFFFFFF); + +// Returns the correct UTC time in seconds, like time(NULL). +// Useful on the viewer, which may have its local clock set wrong. +LL_COMMON_API time_t time_corrected(); + +static inline time_t time_min() +{ + if (sizeof(time_t) == 4) + { + return (time_t) INT_MIN; + } else { +#ifdef LLONG_MIN + return (time_t) LLONG_MIN; +#else + return (time_t) LONG_MIN; +#endif + } +} + +static inline time_t time_max() +{ + if (sizeof(time_t) == 4) + { + return (time_t) INT_MAX; + } else { +#ifdef LLONG_MAX + return (time_t) LLONG_MAX; +#else + return (time_t) LONG_MAX; +#endif + } +} + +// Correction factor used by time_corrected() above. +extern LL_COMMON_API S32 gUTCOffset; + +// Is the current computer (in its current time zone) +// observing daylight savings time? +LL_COMMON_API bool is_daylight_savings(); + +// Converts internal "struct tm" time buffer to Pacific Standard/Daylight Time +// Usage: +// S32 utc_time; +// utc_time = time_corrected(); +// struct tm* internal_time = utc_to_pacific_time(utc_time, gDaylight); +LL_COMMON_API struct tm* utc_to_pacific_time(time_t utc_time, bool pacific_daylight_time); + +LL_COMMON_API void microsecondsToTimecodeString(U64MicrosecondsImplicit current_time, std::string& tcstring); +LL_COMMON_API void secondsToTimecodeString(F32SecondsImplicit current_time, std::string& tcstring); + +U64MicrosecondsImplicit LL_COMMON_API totalTime(); // Returns current system time in microseconds + +#endif diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp index 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 <iomanip>
-
-#include "lluuid.h"
-
-// system includes
-#include <boost/tokenizer.hpp>
-#include <boost/algorithm/string/find_iterator.hpp>
-#include <boost/algorithm/string/finder.hpp>
-
-// static
-void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val)
-{
- ostr << "%"
-
- << std::uppercase
- << std::hex
- << std::setw(2)
- << std::setfill('0')
-
- // VWR-4010 Cannot cast to U32 because sign-extension on
- // chars > 128 will result in FFFFFFC3 instead of F3.
- << static_cast<S32>(static_cast<U8>(val))
-
- // reset stream state
- << std::nouppercase
- << std::dec
- << std::setfill(' ');
-}
-
-// static
-std::string LLURI::escape(
- const std::string& str,
- const std::string& allowed,
- bool is_allowed_sorted)
-{
- // *NOTE: This size determination feels like a good value to
- // me. If someone wante to come up with a more precise heuristic
- // with some data to back up the assertion that 'sort is good'
- // then feel free to change this test a bit.
- if(!is_allowed_sorted && (str.size() > 2 * allowed.size()))
- {
- // if it's already sorted, or if the url is quite long, we
- // want to optimize this process.
- std::string sorted_allowed(allowed);
- std::sort(sorted_allowed.begin(), sorted_allowed.end());
- return escape(str, sorted_allowed, true);
- }
-
- std::ostringstream ostr;
- std::string::const_iterator it = str.begin();
- std::string::const_iterator end = str.end();
- std::string::value_type c;
- if(is_allowed_sorted)
- {
- std::string::const_iterator allowed_begin(allowed.begin());
- std::string::const_iterator allowed_end(allowed.end());
- for(; it != end; ++it)
- {
- c = *it;
- if(std::binary_search(allowed_begin, allowed_end, c))
- {
- ostr << c;
- }
- else
- {
- encodeCharacter(ostr, c);
- }
- }
- }
- else
- {
- for(; it != end; ++it)
- {
- c = *it;
- if(allowed.find(c) == std::string::npos)
- {
- encodeCharacter(ostr, c);
- }
- else
- {
- ostr << c;
- }
- }
- }
- return ostr.str();
-}
-
-// static
-std::string LLURI::unescape(const std::string& str)
-{
- std::ostringstream ostr;
- std::string::const_iterator it = str.begin();
- std::string::const_iterator end = str.end();
- for(; it != end; ++it)
- {
- if((*it) == '%')
- {
- ++it;
- if(it == end) break;
-
- if(is_char_hex(*it))
- {
- U8 c = hex_as_nybble(*it++);
-
- c = c << 4;
- if (it == end) break;
-
- if(is_char_hex(*it))
- {
- c |= hex_as_nybble(*it);
- ostr.put((char)c);
- }
- else
- {
- ostr.put((char)c);
- ostr.put(*it);
- }
- }
- else
- {
- ostr.put('%');
- ostr.put(*it);
- }
- }
- else
- {
- ostr.put(*it);
- }
- }
- return ostr.str();
-}
-
-namespace
-{
- const std::string unreserved()
- {
- static const std::string s =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- "0123456789"
- "-._~";
- return s;
- }
- const std::string path()
- {
- static const std::string s =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz"
- "0123456789"
- "$-_.+"
- "!*'(),"
- "{}|\\^~[]`"
- "<>#%"
- ";/?:@&=";
- return s;
- }
- const std::string sub_delims()
- {
- static const std::string s = "!$&'()*+,;=";
- return s;
- }
-
- std::string escapeHostAndPort(const std::string& s)
- { return LLURI::escape(s, unreserved() + sub_delims() +":"); }
- std::string escapePathComponent(const std::string& s)
- { return LLURI::escape(s, unreserved() + sub_delims() + ":@"); }
- std::string escapeQueryVariable(const std::string& s)
- { return LLURI::escape(s, unreserved() + ":@!$'()*+,"); } // sub_delims - "&;=" + ":@"
- std::string escapeQueryValue(const std::string& s)
- { return LLURI::escape(s, unreserved() + ":@!$'()*+,="); } // sub_delims - "&;" + ":@"
- std::string escapeUriQuery(const std::string& s)
- { return LLURI::escape(s, unreserved() + ":@?&$;*+=%/"); }
- std::string escapeUriData(const std::string& s)
- { return LLURI::escape(s, unreserved() + "%"); }
- std::string escapeUriPath(const std::string& s)
- { return LLURI::escape(s, path()); }
-}
-
-//static
-std::string LLURI::escape(const std::string& str)
-{
- static std::string default_allowed = unreserved();
- static bool initialized = false;
- if(!initialized)
- {
- std::sort(default_allowed.begin(), default_allowed.end());
- initialized = true;
- }
- return escape(str, default_allowed, true);
-}
-
-//static
-std::string LLURI::escapePathAndData(const std::string &str)
-{
- std::string result;
-
- const std::string data_marker = "data:";
- if (str.compare(0, data_marker.length(), data_marker) == 0)
- {
- // This is not url, but data, data part needs to be properly escaped
- // data part is separated by ',' from header. Minimal data uri is "data:,"
- // See "data URI scheme"
- size_t separator = str.find(',');
- if (separator != std::string::npos)
- {
- size_t header_size = separator + 1;
- std::string header = str.substr(0, header_size);
- // base64 is url-safe
- if (header.find("base64") != std::string::npos)
- {
- // assume url-safe data
- result = str;
- }
- else
- {
- std::string data = str.substr(header_size, str.length() - header_size);
-
- // Notes: File can be partially pre-escaped, that's why escaping ignores '%'
- // It somewhat limits user from displaying strings like "%20" in text
- // but that's how viewer worked for a while and user can double-escape it
-
-
- // Header doesn't need escaping
- result = header + escapeUriData(data);
- }
- }
- }
- else
- {
- // try processing it as path with query separator
- // The query component is indicated by the first question
- // mark("?") character and terminated by a number sign("#")
- size_t delim_pos = str.find('?');
- if (delim_pos == std::string::npos)
- {
- // alternate separator
- delim_pos = str.find(';');
- }
-
- if (delim_pos != std::string::npos)
- {
- size_t path_size = delim_pos + 1;
- std::string query;
- std::string fragment;
-
- size_t fragment_pos = str.find('#');
- if ((fragment_pos != std::string::npos) && (fragment_pos > delim_pos))
- {
- query = str.substr(path_size, fragment_pos - path_size);
- fragment = str.substr(fragment_pos);
- }
- else
- {
- query = str.substr(path_size);
- }
-
- std::string path = str.substr(0, path_size);
-
- result = escapeUriPath(path) + escapeUriQuery(query) + escapeUriPath(fragment);
- }
- }
-
- if (result.empty())
- {
- // Not a known scheme or no data part, try just escaping as Uri path
- result = escapeUriPath(str);
- }
- return result;
-}
-
-LLURI::LLURI()
-{
-}
-
-LLURI::LLURI(const std::string& escaped_str)
-{
- std::string::size_type delim_pos;
- delim_pos = escaped_str.find(':');
- std::string temp;
- if (delim_pos == std::string::npos)
- {
- mScheme = "";
- mEscapedOpaque = escaped_str;
- }
- else
- {
- mScheme = escaped_str.substr(0, delim_pos);
- mEscapedOpaque = escaped_str.substr(delim_pos+1);
- }
-
- parseAuthorityAndPathUsingOpaque();
-
- delim_pos = mEscapedPath.find('?');
- if (delim_pos != std::string::npos)
- {
- mEscapedQuery = mEscapedPath.substr(delim_pos+1);
- mEscapedPath = mEscapedPath.substr(0,delim_pos);
- }
-}
-
-static bool isDefault(const std::string& scheme, U16 port)
-{
- if (scheme == "http")
- return port == 80;
- if (scheme == "https")
- return port == 443;
- if (scheme == "ftp")
- return port == 21;
-
- return false;
-}
-
-void LLURI::parseAuthorityAndPathUsingOpaque()
-{
- if (mScheme == "http" || mScheme == "https" ||
- mScheme == "ftp" || mScheme == "secondlife" ||
- mScheme == "x-grid-location-info")
- {
- if (mEscapedOpaque.substr(0,2) != "//")
- {
- return;
- }
-
- std::string::size_type delim_pos, delim_pos2;
- delim_pos = mEscapedOpaque.find('/', 2);
- delim_pos2 = mEscapedOpaque.find('?', 2);
- // no path, no query
- if (delim_pos == std::string::npos &&
- delim_pos2 == std::string::npos)
- {
- mEscapedAuthority = mEscapedOpaque.substr(2);
- mEscapedPath = "";
- }
- // path exist, no query
- else if (delim_pos2 == std::string::npos)
- {
- mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2);
- mEscapedPath = mEscapedOpaque.substr(delim_pos);
- }
- // no path, only query
- else if (delim_pos == std::string::npos ||
- delim_pos2 < delim_pos)
- {
- mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2);
- // query part will be broken out later
- mEscapedPath = mEscapedOpaque.substr(delim_pos2);
- }
- // path and query
- else
- {
- mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2);
- // query part will be broken out later
- mEscapedPath = mEscapedOpaque.substr(delim_pos);
- }
- }
- else if (mScheme == "about")
- {
- mEscapedPath = mEscapedOpaque;
- }
-}
-
-LLURI::LLURI(const std::string& scheme,
- const std::string& userName,
- const std::string& password,
- const std::string& hostName,
- U16 port,
- const std::string& escapedPath,
- const std::string& escapedQuery)
- : mScheme(scheme),
- mEscapedPath(escapedPath),
- mEscapedQuery(escapedQuery)
-{
- std::ostringstream auth;
- std::ostringstream opaque;
-
- opaque << "//";
-
- if (!userName.empty())
- {
- auth << escape(userName);
- if (!password.empty())
- {
- auth << ':' << escape(password);
- }
- auth << '@';
- }
- auth << hostName;
- if (!isDefault(scheme, port))
- {
- auth << ':' << port;
- }
- mEscapedAuthority = auth.str();
-
- opaque << mEscapedAuthority << escapedPath << escapedQuery;
-
- mEscapedOpaque = opaque.str();
-}
-
-LLURI::~LLURI()
-{
-}
-
-// static
-LLURI LLURI::buildHTTP(const std::string& prefix,
- const LLSD& path)
-{
- LLURI result;
-
- // TODO: deal with '/' '?' '#' in host_port
- if (prefix.find("://") != prefix.npos)
- {
- // it is a prefix
- result = LLURI(prefix);
- }
- else
- {
- // it is just a host and optional port
- result.mScheme = "http";
- result.mEscapedAuthority = escapeHostAndPort(prefix);
- }
-
- if (path.isArray())
- {
- // break out and escape each path component
- for (LLSD::array_const_iterator it = path.beginArray();
- it != path.endArray();
- ++it)
- {
- LL_DEBUGS() << "PATH: inserting " << it->asString() << LL_ENDL;
- result.mEscapedPath += "/" + escapePathComponent(it->asString());
- }
- }
- else if (path.isString())
- {
- std::string pathstr(path);
- // Trailing slash is significant in HTTP land. If caller specified,
- // make a point of preserving.
- std::string last_slash;
- std::string::size_type len(pathstr.length());
- if (len && pathstr[len-1] == '/')
- {
- last_slash = "/";
- }
-
- // Escape every individual path component, recombining with slashes.
- for (boost::split_iterator<std::string::const_iterator>
- ti(pathstr, boost::first_finder("/")), tend;
- ti != tend; ++ti)
- {
- // Eliminate a leading slash or duplicate slashes anywhere. (Extra
- // slashes show up here as empty components.) This test also
- // eliminates a trailing slash, hence last_slash above.
- if (! ti->empty())
- {
- result.mEscapedPath
- += "/" + escapePathComponent(std::string(ti->begin(), ti->end()));
- }
- }
-
- // Reinstate trailing slash, if any.
- result.mEscapedPath += last_slash;
- }
- else if(path.isUndefined())
- {
- // do nothing
- }
- else
- {
- LL_WARNS() << "Valid path arguments to buildHTTP are array, string, or undef, you passed type"
- << path.type() << LL_ENDL;
- }
- result.mEscapedOpaque = "//" + result.mEscapedAuthority +
- result.mEscapedPath;
- return result;
-}
-
-// static
-LLURI LLURI::buildHTTP(const std::string& prefix,
- const LLSD& path,
- const LLSD& query)
-{
- LLURI uri = buildHTTP(prefix, path);
- // break out and escape each query component
- uri.mEscapedQuery = mapToQueryString(query);
- uri.mEscapedOpaque += uri.mEscapedQuery ;
- uri.mEscapedQuery.erase(0,1); // trim the leading '?'
- return uri;
-}
-
-// static
-LLURI LLURI::buildHTTP(const std::string& host,
- const U32& port,
- const LLSD& path)
-{
- return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path);
-}
-
-// static
-LLURI LLURI::buildHTTP(const std::string& host,
- const U32& port,
- const LLSD& path,
- const LLSD& query)
-{
- return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path, query);
-}
-
-std::string LLURI::asString() const
-{
- if (mScheme.empty())
- {
- return mEscapedOpaque;
- }
- else
- {
- return mScheme + ":" + mEscapedOpaque;
- }
-}
-
-std::string LLURI::scheme() const
-{
- return mScheme;
-}
-
-std::string LLURI::opaque() const
-{
- return unescape(mEscapedOpaque);
-}
-
-std::string LLURI::authority() const
-{
- return unescape(mEscapedAuthority);
-}
-
-
-namespace {
- void findAuthorityParts(const std::string& authority,
- std::string& user,
- std::string& host,
- std::string& port)
- {
- std::string::size_type start_pos = authority.find('@');
- if (start_pos == std::string::npos)
- {
- user = "";
- start_pos = 0;
- }
- else
- {
- user = authority.substr(0, start_pos);
- start_pos += 1;
- }
-
- std::string::size_type end_pos = authority.find(':', start_pos);
- if (end_pos == std::string::npos)
- {
- host = authority.substr(start_pos);
- port = "";
- }
- else
- {
- host = authority.substr(start_pos, end_pos - start_pos);
- port = authority.substr(end_pos + 1);
- }
- }
-}
-
-std::string LLURI::hostName() const
-{
- std::string user, host, port;
- findAuthorityParts(mEscapedAuthority, user, host, port);
- return unescape(host);
-}
-
-std::string LLURI::userName() const
-{
- std::string user, userPass, host, port;
- findAuthorityParts(mEscapedAuthority, userPass, host, port);
- std::string::size_type pos = userPass.find(':');
- if (pos != std::string::npos)
- {
- user = userPass.substr(0, pos);
- }
- return unescape(user);
-}
-
-std::string LLURI::password() const
-{
- std::string pass, userPass, host, port;
- findAuthorityParts(mEscapedAuthority, userPass, host, port);
- std::string::size_type pos = userPass.find(':');
- if (pos != std::string::npos)
- {
- pass = userPass.substr(pos + 1);
- }
- return unescape(pass);
-}
-
-bool LLURI::defaultPort() const
-{
- return isDefault(mScheme, hostPort());
-}
-
-U16 LLURI::hostPort() const
-{
- std::string user, host, port;
- findAuthorityParts(mEscapedAuthority, user, host, port);
- if (port.empty())
- {
- if (mScheme == "http")
- return 80;
- if (mScheme == "https")
- return 443;
- if (mScheme == "ftp")
- return 21;
- return 0;
- }
- return atoi(port.c_str());
-}
-
-std::string LLURI::path() const
-{
- return unescape(mEscapedPath);
-}
-
-LLSD LLURI::pathArray() const
-{
- typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
- boost::char_separator<char> sep("/", "", boost::drop_empty_tokens);
- tokenizer tokens(mEscapedPath, sep);
- tokenizer::iterator it = tokens.begin();
- tokenizer::iterator end = tokens.end();
-
- LLSD params;
- for (const std::string& str : tokens)
- {
- params.append(str);
- }
- return params;
-}
-
-std::string LLURI::query() const
-{
- return unescape(mEscapedQuery);
-}
-
-LLSD LLURI::queryMap() const
-{
- return queryMap(mEscapedQuery);
-}
-
-// static
-LLSD LLURI::queryMap(std::string escaped_query_string)
-{
- LL_DEBUGS() << "LLURI::queryMap query params: " << escaped_query_string << LL_ENDL;
-
- LLSD result = LLSD::emptyArray();
- while(!escaped_query_string.empty())
- {
- // get tuple first
- std::string tuple;
- std::string::size_type tuple_begin = escaped_query_string.find('&');
- if (tuple_begin != std::string::npos)
- {
- tuple = escaped_query_string.substr(0, tuple_begin);
- escaped_query_string = escaped_query_string.substr(tuple_begin+1);
- }
- else
- {
- tuple = escaped_query_string;
- escaped_query_string = "";
- }
- if (tuple.empty()) continue;
-
- // parse tuple
- std::string::size_type key_end = tuple.find('=');
- if (key_end != std::string::npos)
- {
- std::string key = unescape(tuple.substr(0,key_end));
- std::string value = unescape(tuple.substr(key_end+1));
- LL_DEBUGS() << "inserting key " << key << " value " << value << LL_ENDL;
- result[key] = value;
- }
- else
- {
- LL_DEBUGS() << "inserting key " << unescape(tuple) << " value true" << LL_ENDL;
- result[unescape(tuple)] = true;
- }
- }
- return result;
-}
-
-std::string LLURI::mapToQueryString(const LLSD& queryMap)
-{
- std::string query_string;
- if (queryMap.isMap())
- {
- bool first_element = true;
- LLSD::map_const_iterator iter = queryMap.beginMap();
- LLSD::map_const_iterator end = queryMap.endMap();
- std::ostringstream ostr;
- for (; iter != end; ++iter)
- {
- if(first_element)
- {
- ostr << "?";
- first_element = false;
- }
- else
- {
- ostr << "&";
- }
- ostr << escapeQueryVariable(iter->first);
- if(iter->second.isDefined())
- {
- ostr << "=" << escapeQueryValue(iter->second.asString());
- }
- }
- query_string = ostr.str();
- }
- return query_string;
-}
-
-bool operator!=(const LLURI& first, const LLURI& second)
-{
- return (first.asString() != second.asString());
-}
+/** + * @file lluri.cpp + * @author Phoenix + * @date 2006-02-08 + * @brief Implementation of the LLURI class. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llapp.h" +#include "lluri.h" +#include "llsd.h" +#include <iomanip> + +#include "lluuid.h" + +// system includes +#include <boost/tokenizer.hpp> +#include <boost/algorithm/string/find_iterator.hpp> +#include <boost/algorithm/string/finder.hpp> + +// static +void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val) +{ + ostr << "%" + + << std::uppercase + << std::hex + << std::setw(2) + << std::setfill('0') + + // VWR-4010 Cannot cast to U32 because sign-extension on + // chars > 128 will result in FFFFFFC3 instead of F3. + << static_cast<S32>(static_cast<U8>(val)) + + // reset stream state + << std::nouppercase + << std::dec + << std::setfill(' '); +} + +// static +std::string LLURI::escape( + const std::string& str, + const std::string& allowed, + bool is_allowed_sorted) +{ + // *NOTE: This size determination feels like a good value to + // me. If someone wante to come up with a more precise heuristic + // with some data to back up the assertion that 'sort is good' + // then feel free to change this test a bit. + if(!is_allowed_sorted && (str.size() > 2 * allowed.size())) + { + // if it's already sorted, or if the url is quite long, we + // want to optimize this process. + std::string sorted_allowed(allowed); + std::sort(sorted_allowed.begin(), sorted_allowed.end()); + return escape(str, sorted_allowed, true); + } + + std::ostringstream ostr; + std::string::const_iterator it = str.begin(); + std::string::const_iterator end = str.end(); + std::string::value_type c; + if(is_allowed_sorted) + { + std::string::const_iterator allowed_begin(allowed.begin()); + std::string::const_iterator allowed_end(allowed.end()); + for(; it != end; ++it) + { + c = *it; + if(std::binary_search(allowed_begin, allowed_end, c)) + { + ostr << c; + } + else + { + encodeCharacter(ostr, c); + } + } + } + else + { + for(; it != end; ++it) + { + c = *it; + if(allowed.find(c) == std::string::npos) + { + encodeCharacter(ostr, c); + } + else + { + ostr << c; + } + } + } + return ostr.str(); +} + +// static +std::string LLURI::unescape(const std::string& str) +{ + std::ostringstream ostr; + std::string::const_iterator it = str.begin(); + std::string::const_iterator end = str.end(); + for(; it != end; ++it) + { + if((*it) == '%') + { + ++it; + if(it == end) break; + + if(is_char_hex(*it)) + { + U8 c = hex_as_nybble(*it++); + + c = c << 4; + if (it == end) break; + + if(is_char_hex(*it)) + { + c |= hex_as_nybble(*it); + ostr.put((char)c); + } + else + { + ostr.put((char)c); + ostr.put(*it); + } + } + else + { + ostr.put('%'); + ostr.put(*it); + } + } + else + { + ostr.put(*it); + } + } + return ostr.str(); +} + +namespace +{ + const std::string unreserved() + { + static const std::string s = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~"; + return s; + } + const std::string path() + { + static const std::string s = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "$-_.+" + "!*'()," + "{}|\\^~[]`" + "<>#%" + ";/?:@&="; + return s; + } + const std::string sub_delims() + { + static const std::string s = "!$&'()*+,;="; + return s; + } + + std::string escapeHostAndPort(const std::string& s) + { return LLURI::escape(s, unreserved() + sub_delims() +":"); } + std::string escapePathComponent(const std::string& s) + { return LLURI::escape(s, unreserved() + sub_delims() + ":@"); } + std::string escapeQueryVariable(const std::string& s) + { return LLURI::escape(s, unreserved() + ":@!$'()*+,"); } // sub_delims - "&;=" + ":@" + std::string escapeQueryValue(const std::string& s) + { return LLURI::escape(s, unreserved() + ":@!$'()*+,="); } // sub_delims - "&;" + ":@" + std::string escapeUriQuery(const std::string& s) + { return LLURI::escape(s, unreserved() + ":@?&$;*+=%/"); } + std::string escapeUriData(const std::string& s) + { return LLURI::escape(s, unreserved() + "%"); } + std::string escapeUriPath(const std::string& s) + { return LLURI::escape(s, path()); } +} + +//static +std::string LLURI::escape(const std::string& str) +{ + static std::string default_allowed = unreserved(); + static bool initialized = false; + if(!initialized) + { + std::sort(default_allowed.begin(), default_allowed.end()); + initialized = true; + } + return escape(str, default_allowed, true); +} + +//static +std::string LLURI::escapePathAndData(const std::string &str) +{ + std::string result; + + const std::string data_marker = "data:"; + if (str.compare(0, data_marker.length(), data_marker) == 0) + { + // This is not url, but data, data part needs to be properly escaped + // data part is separated by ',' from header. Minimal data uri is "data:," + // See "data URI scheme" + size_t separator = str.find(','); + if (separator != std::string::npos) + { + size_t header_size = separator + 1; + std::string header = str.substr(0, header_size); + // base64 is url-safe + if (header.find("base64") != std::string::npos) + { + // assume url-safe data + result = str; + } + else + { + std::string data = str.substr(header_size, str.length() - header_size); + + // Notes: File can be partially pre-escaped, that's why escaping ignores '%' + // It somewhat limits user from displaying strings like "%20" in text + // but that's how viewer worked for a while and user can double-escape it + + + // Header doesn't need escaping + result = header + escapeUriData(data); + } + } + } + else + { + // try processing it as path with query separator + // The query component is indicated by the first question + // mark("?") character and terminated by a number sign("#") + size_t delim_pos = str.find('?'); + if (delim_pos == std::string::npos) + { + // alternate separator + delim_pos = str.find(';'); + } + + if (delim_pos != std::string::npos) + { + size_t path_size = delim_pos + 1; + std::string query; + std::string fragment; + + size_t fragment_pos = str.find('#'); + if ((fragment_pos != std::string::npos) && (fragment_pos > delim_pos)) + { + query = str.substr(path_size, fragment_pos - path_size); + fragment = str.substr(fragment_pos); + } + else + { + query = str.substr(path_size); + } + + std::string path = str.substr(0, path_size); + + result = escapeUriPath(path) + escapeUriQuery(query) + escapeUriPath(fragment); + } + } + + if (result.empty()) + { + // Not a known scheme or no data part, try just escaping as Uri path + result = escapeUriPath(str); + } + return result; +} + +LLURI::LLURI() +{ +} + +LLURI::LLURI(const std::string& escaped_str) +{ + std::string::size_type delim_pos; + delim_pos = escaped_str.find(':'); + std::string temp; + if (delim_pos == std::string::npos) + { + mScheme = ""; + mEscapedOpaque = escaped_str; + } + else + { + mScheme = escaped_str.substr(0, delim_pos); + mEscapedOpaque = escaped_str.substr(delim_pos+1); + } + + parseAuthorityAndPathUsingOpaque(); + + delim_pos = mEscapedPath.find('?'); + if (delim_pos != std::string::npos) + { + mEscapedQuery = mEscapedPath.substr(delim_pos+1); + mEscapedPath = mEscapedPath.substr(0,delim_pos); + } +} + +static bool isDefault(const std::string& scheme, U16 port) +{ + if (scheme == "http") + return port == 80; + if (scheme == "https") + return port == 443; + if (scheme == "ftp") + return port == 21; + + return false; +} + +void LLURI::parseAuthorityAndPathUsingOpaque() +{ + if (mScheme == "http" || mScheme == "https" || + mScheme == "ftp" || mScheme == "secondlife" || + mScheme == "x-grid-location-info") + { + if (mEscapedOpaque.substr(0,2) != "//") + { + return; + } + + std::string::size_type delim_pos, delim_pos2; + delim_pos = mEscapedOpaque.find('/', 2); + delim_pos2 = mEscapedOpaque.find('?', 2); + // no path, no query + if (delim_pos == std::string::npos && + delim_pos2 == std::string::npos) + { + mEscapedAuthority = mEscapedOpaque.substr(2); + mEscapedPath = ""; + } + // path exist, no query + else if (delim_pos2 == std::string::npos) + { + mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); + mEscapedPath = mEscapedOpaque.substr(delim_pos); + } + // no path, only query + else if (delim_pos == std::string::npos || + delim_pos2 < delim_pos) + { + mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2); + // query part will be broken out later + mEscapedPath = mEscapedOpaque.substr(delim_pos2); + } + // path and query + else + { + mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); + // query part will be broken out later + mEscapedPath = mEscapedOpaque.substr(delim_pos); + } + } + else if (mScheme == "about") + { + mEscapedPath = mEscapedOpaque; + } +} + +LLURI::LLURI(const std::string& scheme, + const std::string& userName, + const std::string& password, + const std::string& hostName, + U16 port, + const std::string& escapedPath, + const std::string& escapedQuery) + : mScheme(scheme), + mEscapedPath(escapedPath), + mEscapedQuery(escapedQuery) +{ + std::ostringstream auth; + std::ostringstream opaque; + + opaque << "//"; + + if (!userName.empty()) + { + auth << escape(userName); + if (!password.empty()) + { + auth << ':' << escape(password); + } + auth << '@'; + } + auth << hostName; + if (!isDefault(scheme, port)) + { + auth << ':' << port; + } + mEscapedAuthority = auth.str(); + + opaque << mEscapedAuthority << escapedPath << escapedQuery; + + mEscapedOpaque = opaque.str(); +} + +LLURI::~LLURI() +{ +} + +// static +LLURI LLURI::buildHTTP(const std::string& prefix, + const LLSD& path) +{ + LLURI result; + + // TODO: deal with '/' '?' '#' in host_port + if (prefix.find("://") != prefix.npos) + { + // it is a prefix + result = LLURI(prefix); + } + else + { + // it is just a host and optional port + result.mScheme = "http"; + result.mEscapedAuthority = escapeHostAndPort(prefix); + } + + if (path.isArray()) + { + // break out and escape each path component + for (LLSD::array_const_iterator it = path.beginArray(); + it != path.endArray(); + ++it) + { + LL_DEBUGS() << "PATH: inserting " << it->asString() << LL_ENDL; + result.mEscapedPath += "/" + escapePathComponent(it->asString()); + } + } + else if (path.isString()) + { + std::string pathstr(path); + // Trailing slash is significant in HTTP land. If caller specified, + // make a point of preserving. + std::string last_slash; + std::string::size_type len(pathstr.length()); + if (len && pathstr[len-1] == '/') + { + last_slash = "/"; + } + + // Escape every individual path component, recombining with slashes. + for (boost::split_iterator<std::string::const_iterator> + ti(pathstr, boost::first_finder("/")), tend; + ti != tend; ++ti) + { + // Eliminate a leading slash or duplicate slashes anywhere. (Extra + // slashes show up here as empty components.) This test also + // eliminates a trailing slash, hence last_slash above. + if (! ti->empty()) + { + result.mEscapedPath + += "/" + escapePathComponent(std::string(ti->begin(), ti->end())); + } + } + + // Reinstate trailing slash, if any. + result.mEscapedPath += last_slash; + } + else if(path.isUndefined()) + { + // do nothing + } + else + { + LL_WARNS() << "Valid path arguments to buildHTTP are array, string, or undef, you passed type" + << path.type() << LL_ENDL; + } + result.mEscapedOpaque = "//" + result.mEscapedAuthority + + result.mEscapedPath; + return result; +} + +// static +LLURI LLURI::buildHTTP(const std::string& prefix, + const LLSD& path, + const LLSD& query) +{ + LLURI uri = buildHTTP(prefix, path); + // break out and escape each query component + uri.mEscapedQuery = mapToQueryString(query); + uri.mEscapedOpaque += uri.mEscapedQuery ; + uri.mEscapedQuery.erase(0,1); // trim the leading '?' + return uri; +} + +// static +LLURI LLURI::buildHTTP(const std::string& host, + const U32& port, + const LLSD& path) +{ + return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path); +} + +// static +LLURI LLURI::buildHTTP(const std::string& host, + const U32& port, + const LLSD& path, + const LLSD& query) +{ + return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path, query); +} + +std::string LLURI::asString() const +{ + if (mScheme.empty()) + { + return mEscapedOpaque; + } + else + { + return mScheme + ":" + mEscapedOpaque; + } +} + +std::string LLURI::scheme() const +{ + return mScheme; +} + +std::string LLURI::opaque() const +{ + return unescape(mEscapedOpaque); +} + +std::string LLURI::authority() const +{ + return unescape(mEscapedAuthority); +} + + +namespace { + void findAuthorityParts(const std::string& authority, + std::string& user, + std::string& host, + std::string& port) + { + std::string::size_type start_pos = authority.find('@'); + if (start_pos == std::string::npos) + { + user = ""; + start_pos = 0; + } + else + { + user = authority.substr(0, start_pos); + start_pos += 1; + } + + std::string::size_type end_pos = authority.find(':', start_pos); + if (end_pos == std::string::npos) + { + host = authority.substr(start_pos); + port = ""; + } + else + { + host = authority.substr(start_pos, end_pos - start_pos); + port = authority.substr(end_pos + 1); + } + } +} + +std::string LLURI::hostName() const +{ + std::string user, host, port; + findAuthorityParts(mEscapedAuthority, user, host, port); + return unescape(host); +} + +std::string LLURI::userName() const +{ + std::string user, userPass, host, port; + findAuthorityParts(mEscapedAuthority, userPass, host, port); + std::string::size_type pos = userPass.find(':'); + if (pos != std::string::npos) + { + user = userPass.substr(0, pos); + } + return unescape(user); +} + +std::string LLURI::password() const +{ + std::string pass, userPass, host, port; + findAuthorityParts(mEscapedAuthority, userPass, host, port); + std::string::size_type pos = userPass.find(':'); + if (pos != std::string::npos) + { + pass = userPass.substr(pos + 1); + } + return unescape(pass); +} + +bool LLURI::defaultPort() const +{ + return isDefault(mScheme, hostPort()); +} + +U16 LLURI::hostPort() const +{ + std::string user, host, port; + findAuthorityParts(mEscapedAuthority, user, host, port); + if (port.empty()) + { + if (mScheme == "http") + return 80; + if (mScheme == "https") + return 443; + if (mScheme == "ftp") + return 21; + return 0; + } + return atoi(port.c_str()); +} + +std::string LLURI::path() const +{ + return unescape(mEscapedPath); +} + +LLSD LLURI::pathArray() const +{ + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep("/", "", boost::drop_empty_tokens); + tokenizer tokens(mEscapedPath, sep); + tokenizer::iterator it = tokens.begin(); + tokenizer::iterator end = tokens.end(); + + LLSD params; + for (const std::string& str : tokens) + { + params.append(str); + } + return params; +} + +std::string LLURI::query() const +{ + return unescape(mEscapedQuery); +} + +LLSD LLURI::queryMap() const +{ + return queryMap(mEscapedQuery); +} + +// static +LLSD LLURI::queryMap(std::string escaped_query_string) +{ + LL_DEBUGS() << "LLURI::queryMap query params: " << escaped_query_string << LL_ENDL; + + LLSD result = LLSD::emptyArray(); + while(!escaped_query_string.empty()) + { + // get tuple first + std::string tuple; + std::string::size_type tuple_begin = escaped_query_string.find('&'); + if (tuple_begin != std::string::npos) + { + tuple = escaped_query_string.substr(0, tuple_begin); + escaped_query_string = escaped_query_string.substr(tuple_begin+1); + } + else + { + tuple = escaped_query_string; + escaped_query_string = ""; + } + if (tuple.empty()) continue; + + // parse tuple + std::string::size_type key_end = tuple.find('='); + if (key_end != std::string::npos) + { + std::string key = unescape(tuple.substr(0,key_end)); + std::string value = unescape(tuple.substr(key_end+1)); + LL_DEBUGS() << "inserting key " << key << " value " << value << LL_ENDL; + result[key] = value; + } + else + { + LL_DEBUGS() << "inserting key " << unescape(tuple) << " value true" << LL_ENDL; + result[unescape(tuple)] = true; + } + } + return result; +} + +std::string LLURI::mapToQueryString(const LLSD& queryMap) +{ + std::string query_string; + if (queryMap.isMap()) + { + bool first_element = true; + LLSD::map_const_iterator iter = queryMap.beginMap(); + LLSD::map_const_iterator end = queryMap.endMap(); + std::ostringstream ostr; + for (; iter != end; ++iter) + { + if(first_element) + { + ostr << "?"; + first_element = false; + } + else + { + ostr << "&"; + } + ostr << escapeQueryVariable(iter->first); + if(iter->second.isDefined()) + { + ostr << "=" << escapeQueryValue(iter->second.asString()); + } + } + query_string = ostr.str(); + } + return query_string; +} + +bool operator!=(const LLURI& first, const LLURI& second) +{ + return (first.asString() != second.asString()); +} diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index 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 <string>
-
-class LLSD;
-class LLUUID;
-class LLApp;
-
-/**
- *
- * LLURI instances are immutable
- * See: http://www.ietf.org/rfc/rfc3986.txt
- *
- */
-class LL_COMMON_API LLURI
-{
-public:
- LLURI();
- LLURI(const std::string& escaped_str);
- LLURI(const std::string& scheme,
- const std::string& userName,
- const std::string& password,
- const std::string& hostName,
- U16 hostPort,
- const std::string& escapedPath,
- const std::string& escapedQuery);
-
- // construct from escaped string, as would be transmitted on the net
-
- ~LLURI();
-
- static LLURI buildHTTP(
- const std::string& prefix,
- const LLSD& path);
-
- static LLURI buildHTTP(
- const std::string& prefix,
- const LLSD& path,
- const LLSD& query);
- ///< prefix is either a full URL prefix of the form
- /// "http://example.com:8080", or it can be simply a host and
- /// optional port like "example.com" or "example.com:8080", in
- /// these cases, the "http://" will be added
-
- static LLURI buildHTTP(
- const std::string& host,
- const U32& port,
- const LLSD& path);
- static LLURI buildHTTP(
- const std::string& host,
- const U32& port,
- const LLSD& path,
- const LLSD& query);
-
- std::string asString() const;
- ///< the whole URI, escaped as needed
-
- /** @name Parts of a URI */
- //@{
- // These functions return parts of the decoded URI. The returned
- // strings are un-escaped as needed
-
- // for all schemes
- std::string scheme() const; ///< ex.: "http", note lack of colon
- std::string opaque() const; ///< everything after the colon
-
- // for schemes that follow path like syntax (http, https, ftp)
- std::string authority() const; // ex.: "host.com:80"
- std::string hostName() const; // ex.: "host.com"
- std::string userName() const;
- std::string password() const;
- U16 hostPort() const; // ex.: 80, will include implicit port
- bool defaultPort() const; // true if port is default for scheme
- const std::string& escapedPath() const { return mEscapedPath; }
- std::string path() const; // ex.: "/abc/def", includes leading slash
- LLSD pathArray() const; // above decoded into an array of strings
- std::string query() const; // ex.: "x=34", section after "?"
- const std::string& escapedQuery() const { return mEscapedQuery; }
- LLSD queryMap() const; // above decoded into a map
- static LLSD queryMap(std::string escaped_query_string);
-
- /**
- * @brief given a name value map, return a serialized query string.
- *
-
- * @param query_map a map of name value. every value must be
- * representable as a string.
- * @return Returns an url query string of '?n1=v1&n2=v2&...'
- */
- static std::string mapToQueryString(const LLSD& query_map);
-
- /** @name Escaping Utilities */
- //@{
- /**
- * @brief 'Escape' symbol into stream
- *
- * @param ostr Output stream.
- * @param val Symbol to encode.
- */
- static void encodeCharacter(std::ostream& ostr, std::string::value_type val);
-
- /**
- * @brief Escape the string passed except for unreserved
- *
- * ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
- * 0123456789
- * -._~
- *
- * @see http://www.ietf.org/rfc/rfc1738.txt
- *
- * @param str The raw URI to escape.
- * @return Returns the rfc 1738 escaped uri or an empty string.
- */
- static std::string escape(const std::string& str);
-
- /**
- * @brief Escape a string with a specified set of allowed characters.
- *
- * Escape a string by urlencoding all the characters that aren't
- * in the allowed string.
- * @param str The raw URI to escape.
- * @param allowed Character array of allowed characters
- * @param is_allowed_sorted Optimization hint if allowed array is sorted.
- * @return Returns the escaped uri or an empty string.
- */
- static std::string escape(
- const std::string& str,
- const std::string& allowed,
- bool is_allowed_sorted = false);
-
- /**
- * @brief Break string into data part and path or sheme
- * and escape path (if present) and data.
- * Data part is not allowed to have path related symbols
- * @param str The raw URI to escape.
- */
- static std::string escapePathAndData(const std::string &str);
-
- /**
- * @brief unescape an escaped URI string.
- *
- * @param str The escped URI to unescape.
- * @return Returns the unescaped uri or an empty string.
- */
- static std::string unescape(const std::string& str);
- //@}
-
-private:
- // only "http", "https", "ftp", and "secondlife" schemes are parsed
- // secondlife scheme parses authority as "" and includes it as part of
- // the path. See lluri_tut.cpp
- // i.e. secondlife://app/login has mAuthority = "" and mPath = "/app/login"
- void parseAuthorityAndPathUsingOpaque();
- std::string mScheme;
- std::string mEscapedOpaque;
- std::string mEscapedAuthority;
- std::string mEscapedPath;
- std::string mEscapedQuery;
-};
-
-// this operator required for tut
-LL_COMMON_API bool operator!=(const LLURI& first, const LLURI& second);
-
-#endif // LL_LLURI_H
+/** + * @file lluri.h + * @author Phoenix + * @date 2006-02-05 + * @brief Declaration of the URI class. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLURI_H +#define LL_LLURI_H + +#include <string> + +class LLSD; +class LLUUID; +class LLApp; + +/** + * + * LLURI instances are immutable + * See: http://www.ietf.org/rfc/rfc3986.txt + * + */ +class LL_COMMON_API LLURI +{ +public: + LLURI(); + LLURI(const std::string& escaped_str); + LLURI(const std::string& scheme, + const std::string& userName, + const std::string& password, + const std::string& hostName, + U16 hostPort, + const std::string& escapedPath, + const std::string& escapedQuery); + + // construct from escaped string, as would be transmitted on the net + + ~LLURI(); + + static LLURI buildHTTP( + const std::string& prefix, + const LLSD& path); + + static LLURI buildHTTP( + const std::string& prefix, + const LLSD& path, + const LLSD& query); + ///< prefix is either a full URL prefix of the form + /// "http://example.com:8080", or it can be simply a host and + /// optional port like "example.com" or "example.com:8080", in + /// these cases, the "http://" will be added + + static LLURI buildHTTP( + const std::string& host, + const U32& port, + const LLSD& path); + static LLURI buildHTTP( + const std::string& host, + const U32& port, + const LLSD& path, + const LLSD& query); + + std::string asString() const; + ///< the whole URI, escaped as needed + + /** @name Parts of a URI */ + //@{ + // These functions return parts of the decoded URI. The returned + // strings are un-escaped as needed + + // for all schemes + std::string scheme() const; ///< ex.: "http", note lack of colon + std::string opaque() const; ///< everything after the colon + + // for schemes that follow path like syntax (http, https, ftp) + std::string authority() const; // ex.: "host.com:80" + std::string hostName() const; // ex.: "host.com" + std::string userName() const; + std::string password() const; + U16 hostPort() const; // ex.: 80, will include implicit port + bool defaultPort() const; // true if port is default for scheme + const std::string& escapedPath() const { return mEscapedPath; } + std::string path() const; // ex.: "/abc/def", includes leading slash + LLSD pathArray() const; // above decoded into an array of strings + std::string query() const; // ex.: "x=34", section after "?" + const std::string& escapedQuery() const { return mEscapedQuery; } + LLSD queryMap() const; // above decoded into a map + static LLSD queryMap(std::string escaped_query_string); + + /** + * @brief given a name value map, return a serialized query string. + * + + * @param query_map a map of name value. every value must be + * representable as a string. + * @return Returns an url query string of '?n1=v1&n2=v2&...' + */ + static std::string mapToQueryString(const LLSD& query_map); + + /** @name Escaping Utilities */ + //@{ + /** + * @brief 'Escape' symbol into stream + * + * @param ostr Output stream. + * @param val Symbol to encode. + */ + static void encodeCharacter(std::ostream& ostr, std::string::value_type val); + + /** + * @brief Escape the string passed except for unreserved + * + * ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz + * 0123456789 + * -._~ + * + * @see http://www.ietf.org/rfc/rfc1738.txt + * + * @param str The raw URI to escape. + * @return Returns the rfc 1738 escaped uri or an empty string. + */ + static std::string escape(const std::string& str); + + /** + * @brief Escape a string with a specified set of allowed characters. + * + * Escape a string by urlencoding all the characters that aren't + * in the allowed string. + * @param str The raw URI to escape. + * @param allowed Character array of allowed characters + * @param is_allowed_sorted Optimization hint if allowed array is sorted. + * @return Returns the escaped uri or an empty string. + */ + static std::string escape( + const std::string& str, + const std::string& allowed, + bool is_allowed_sorted = false); + + /** + * @brief Break string into data part and path or sheme + * and escape path (if present) and data. + * Data part is not allowed to have path related symbols + * @param str The raw URI to escape. + */ + static std::string escapePathAndData(const std::string &str); + + /** + * @brief unescape an escaped URI string. + * + * @param str The escped URI to unescape. + * @return Returns the unescaped uri or an empty string. + */ + static std::string unescape(const std::string& str); + //@} + +private: + // only "http", "https", "ftp", and "secondlife" schemes are parsed + // secondlife scheme parses authority as "" and includes it as part of + // the path. See lluri_tut.cpp + // i.e. secondlife://app/login has mAuthority = "" and mPath = "/app/login" + void parseAuthorityAndPathUsingOpaque(); + std::string mScheme; + std::string mEscapedOpaque; + std::string mEscapedAuthority; + std::string mEscapedPath; + std::string mEscapedQuery; +}; + +// this operator required for tut +LL_COMMON_API bool operator!=(const LLURI& first, const LLURI& second); + +#endif // LL_LLURI_H diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index 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 <iphlpapi.h>
-#endif
-
-#include "llapp.h"
-#include "lldefs.h"
-#include "llerror.h"
-
-#include "lluuid.h"
-#include "llerror.h"
-#include "llrand.h"
-#include "llstring.h"
-#include "lltimer.h"
-#include "llthread.h"
-#include "llmutex.h"
-#include "llmd5.h"
-#include "hbxxh.h"
-
-const LLUUID LLUUID::null;
-const LLTransactionID LLTransactionID::tnull;
-
-// static
-LLMutex* LLUUID::mMutex = NULL;
-
-
-
-/*
-
-NOT DONE YET!!!
-
-static char BASE85_TABLE[] = {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
- 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
- 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
- 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
- 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
- 'y', 'z', '!', '#', '$', '%', '&', '(', ')', '*',
- '+', '-', ';', '[', '=', '>', '?', '@', '^', '_',
- '`', '{', '|', '}', '~', '\0'
-};
-
-
-void encode( char * fiveChars, unsigned int word ) throw( )
-{
-for( int ix = 0; ix < 5; ++ix ) {
-fiveChars[4-ix] = encodeTable[ word % 85];
-word /= 85;
-}
-}
-
-To decode:
-unsigned int decode( char const * fiveChars ) throw( bad_input_data )
-{
-unsigned int ret = 0;
-for( int ix = 0; ix < 5; ++ix ) {
-char * s = strchr( encodeTable, fiveChars[ ix ] );
-if( s == 0 ) LLTHROW(bad_input_data());
-ret = ret * 85 + (s-encodeTable);
-}
-return ret;
-}
-
-void LLUUID::toBase85(char* out)
-{
- U32* me = (U32*)&(mData[0]);
- for(S32 i = 0; i < 4; ++i)
- {
- char* o = &out[i*i];
- for(S32 j = 0; j < 5; ++j)
- {
- o[4-j] = BASE85_TABLE[ me[i] % 85];
- word /= 85;
- }
- }
-}
-
-unsigned int decode( char const * fiveChars ) throw( bad_input_data )
-{
- unsigned int ret = 0;
- for( S32 ix = 0; ix < 5; ++ix )
- {
- char * s = strchr( encodeTable, fiveChars[ ix ] );
- ret = ret * 85 + (s-encodeTable);
- }
- return ret;
-}
-*/
-
-#define LL_USE_JANKY_RANDOM_NUMBER_GENERATOR 0
-#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
-/**
- * @brief a global for
- */
-static U64 sJankyRandomSeed(LLUUID::getRandomSeed());
-
-/**
- * @brief generate a random U32.
- */
-U32 janky_fast_random_bytes()
-{
- sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223);
- return (U32)sJankyRandomSeed;
-}
-
-/**
- * @brief generate a random U32 from [0, val)
- */
-U32 janky_fast_random_byes_range(U32 val)
-{
- sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223);
- return (U32)(sJankyRandomSeed) % val;
-}
-
-/**
- * @brief generate a random U32 from [0, val)
- */
-U32 janky_fast_random_seeded_bytes(U32 seed, U32 val)
-{
- seed = U64L(1664525) * (U64)(seed)+U64L(1013904223);
- return (U32)(seed) % val;
-}
-#endif
-
-// Common to all UUID implementations
-void LLUUID::toString(std::string& out) const
-{
- out = llformat(
- "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
- (U8)(mData[0]),
- (U8)(mData[1]),
- (U8)(mData[2]),
- (U8)(mData[3]),
- (U8)(mData[4]),
- (U8)(mData[5]),
- (U8)(mData[6]),
- (U8)(mData[7]),
- (U8)(mData[8]),
- (U8)(mData[9]),
- (U8)(mData[10]),
- (U8)(mData[11]),
- (U8)(mData[12]),
- (U8)(mData[13]),
- (U8)(mData[14]),
- (U8)(mData[15]));
-}
-
-// *TODO: deprecate
-void LLUUID::toString(char* out) const
-{
- std::string buffer;
- toString(buffer);
- strcpy(out, buffer.c_str()); /* Flawfinder: ignore */
-}
-
-void LLUUID::toCompressedString(std::string& out) const
-{
- char bytes[UUID_BYTES + 1];
- memcpy(bytes, mData, UUID_BYTES); /* Flawfinder: ignore */
- bytes[UUID_BYTES] = '\0';
- out.assign(bytes, UUID_BYTES);
-}
-
-// *TODO: deprecate
-void LLUUID::toCompressedString(char* out) const
-{
- memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */
- out[UUID_BYTES] = '\0';
-}
-
-std::string LLUUID::getString() const
-{
- return asString();
-}
-
-std::string LLUUID::asString() const
-{
- std::string str;
- toString(str);
- return str;
-}
-
-bool LLUUID::set(const char* in_string, bool emit)
-{
- return set(ll_safe_string(in_string), emit);
-}
-
-bool LLUUID::set(const std::string& in_string, bool emit)
-{
- bool broken_format = false;
-
- // empty strings should make NULL uuid
- if (in_string.empty())
- {
- setNull();
- return true;
- }
-
- if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */
- {
- // I'm a moron. First implementation didn't have the right UUID format.
- // Shouldn't see any of these any more
- if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */
- {
- if (emit)
- {
- LL_WARNS() << "Warning! Using broken UUID string format" << LL_ENDL;
- }
- broken_format = true;
- }
- else
- {
- // Bad UUID string. Spam as INFO, as most cases we don't care.
- if (emit)
- {
- //don't spam the logs because a resident can't spell.
- LL_WARNS() << "Bad UUID string: " << in_string << LL_ENDL;
- }
- setNull();
- return false;
- }
- }
-
- U8 cur_pos = 0;
- S32 i;
- for (i = 0; i < UUID_BYTES; i++)
- {
- if ((i == 4) || (i == 6) || (i == 8) || (i == 10))
- {
- cur_pos++;
- if (broken_format && (i == 10))
- {
- // Missing - in the broken format
- cur_pos--;
- }
- }
-
- mData[i] = 0;
-
- if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9'))
- {
- mData[i] += (U8)(in_string[cur_pos] - '0');
- }
- else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f'))
- {
- mData[i] += (U8)(10 + in_string[cur_pos] - 'a');
- }
- else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F'))
- {
- mData[i] += (U8)(10 + in_string[cur_pos] - 'A');
- }
- else
- {
- if (emit)
- {
- LL_WARNS() << "Invalid UUID string character" << LL_ENDL;
- }
- setNull();
- return false;
- }
-
- mData[i] = mData[i] << 4;
- cur_pos++;
-
- if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9'))
- {
- mData[i] += (U8)(in_string[cur_pos] - '0');
- }
- else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f'))
- {
- mData[i] += (U8)(10 + in_string[cur_pos] - 'a');
- }
- else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F'))
- {
- mData[i] += (U8)(10 + in_string[cur_pos] - 'A');
- }
- else
- {
- if (emit)
- {
- LL_WARNS() << "Invalid UUID string character" << LL_ENDL;
- }
- setNull();
- return false;
- }
- cur_pos++;
- }
-
- return true;
-}
-
-bool LLUUID::validate(const std::string& in_string)
-{
- bool broken_format = false;
- if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */
- {
- // I'm a moron. First implementation didn't have the right UUID format.
- if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */
- {
- broken_format = true;
- }
- else
- {
- return false;
- }
- }
-
- U8 cur_pos = 0;
- for (U32 i = 0; i < 16; i++)
- {
- if ((i == 4) || (i == 6) || (i == 8) || (i == 10))
- {
- cur_pos++;
- if (broken_format && (i == 10))
- {
- // Missing - in the broken format
- cur_pos--;
- }
- }
-
- if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9'))
- {
- }
- else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f'))
- {
- }
- else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F'))
- {
- }
- else
- {
- return false;
- }
-
- cur_pos++;
-
- if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9'))
- {
- }
- else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f'))
- {
- }
- else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F'))
- {
- }
- else
- {
- return false;
- }
- cur_pos++;
- }
- return true;
-}
-
-const LLUUID& LLUUID::operator^=(const LLUUID& rhs)
-{
- U32* me = (U32*)&(mData[0]);
- const U32* other = (U32*)&(rhs.mData[0]);
- for (S32 i = 0; i < 4; ++i)
- {
- me[i] = me[i] ^ other[i];
- }
- return *this;
-}
-
-LLUUID LLUUID::operator^(const LLUUID& rhs) const
-{
- LLUUID id(*this);
- id ^= rhs;
- return id;
-}
-
-// WARNING: this algorithm SHALL NOT be changed. It is also used by the server
-// and plays a role in some assets validation (e.g. clothing items). Changing
-// it would cause invalid assets.
-void LLUUID::combine(const LLUUID& other, LLUUID& result) const
-{
- LLMD5 md5_uuid;
- md5_uuid.update((unsigned char*)mData, 16);
- md5_uuid.update((unsigned char*)other.mData, 16);
- md5_uuid.finalize();
- md5_uuid.raw_digest(result.mData);
-}
-
-LLUUID LLUUID::combine(const LLUUID& other) const
-{
- LLUUID combination;
- combine(other, combination);
- return combination;
-}
-
-std::ostream& operator<<(std::ostream& s, const LLUUID& uuid)
-{
- std::string uuid_str;
- uuid.toString(uuid_str);
- s << uuid_str;
- return s;
-}
-
-std::istream& operator>>(std::istream& s, LLUUID& uuid)
-{
- U32 i;
- char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */
- for (i = 0; i < UUID_STR_LENGTH - 1; i++)
- {
- s >> uuid_str[i];
- }
- uuid_str[i] = '\0';
- uuid.set(std::string(uuid_str));
- return s;
-}
-
-static void get_random_bytes(void* buf, int nbytes)
-{
- int i;
- char* cp = (char*)buf;
-
- // *NOTE: If we are not using the janky generator ll_rand()
- // generates at least 3 good bytes of data since it is 0 to
- // RAND_MAX. This could be made more efficient by copying all the
- // bytes.
- for (i = 0; i < nbytes; i++)
-#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
- * cp++ = janky_fast_random_bytes() & 0xFF;
-#else
- * cp++ = ll_rand() & 0xFF;
-#endif
- return;
-}
-
-#if LL_WINDOWS
-
-typedef struct _ASTAT_
-{
- ADAPTER_STATUS adapt;
- NAME_BUFFER NameBuff[30];
-}ASTAT, * PASTAT;
-
-// static
-S32 LLUUID::getNodeID(unsigned char* node_id)
-{
- ASTAT Adapter;
- NCB Ncb;
- UCHAR uRetCode;
- LANA_ENUM lenum;
- int i;
- int retval = 0;
-
- memset(&Ncb, 0, sizeof(Ncb));
- Ncb.ncb_command = NCBENUM;
- Ncb.ncb_buffer = (UCHAR*)&lenum;
- Ncb.ncb_length = sizeof(lenum);
- uRetCode = Netbios(&Ncb);
-
- for (i = 0; i < lenum.length; i++)
- {
- memset(&Ncb, 0, sizeof(Ncb));
- Ncb.ncb_command = NCBRESET;
- Ncb.ncb_lana_num = lenum.lana[i];
-
- uRetCode = Netbios(&Ncb);
-
- memset(&Ncb, 0, sizeof(Ncb));
- Ncb.ncb_command = NCBASTAT;
- Ncb.ncb_lana_num = lenum.lana[i];
-
- strcpy((char*)Ncb.ncb_callname, "* "); /* Flawfinder: ignore */
- Ncb.ncb_buffer = (unsigned char*)&Adapter;
- Ncb.ncb_length = sizeof(Adapter);
-
- uRetCode = Netbios(&Ncb);
- if (uRetCode == 0)
- {
- memcpy(node_id, Adapter.adapt.adapter_address, 6); /* Flawfinder: ignore */
- retval = 1;
- }
- }
- return retval;
-}
-
-#elif LL_DARWIN
-// macOS version of the UUID generation code...
-/*
- * Get an ethernet hardware address, if we can find it...
- */
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
-#include <net/if.h>
-#include <net/if_types.h>
-#include <net/if_dl.h>
-#include <net/route.h>
-#include <ifaddrs.h>
-
- // static
-S32 LLUUID::getNodeID(unsigned char* node_id)
-{
- int i;
- unsigned char* a = NULL;
- struct ifaddrs* ifap, * ifa;
- int rv;
- S32 result = 0;
-
- if ((rv = getifaddrs(&ifap)) == -1)
- {
- return -1;
- }
- if (ifap == NULL)
- {
- return -1;
- }
-
- for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next)
- {
- // printf("Interface %s, address family %d, ", ifa->ifa_name, ifa->ifa_addr->sa_family);
- for (i = 0; i < ifa->ifa_addr->sa_len; i++)
- {
- // printf("%02X ", (unsigned char)ifa->ifa_addr->sa_data[i]);
- }
- // printf("\n");
-
- if (ifa->ifa_addr->sa_family == AF_LINK)
- {
- // This is a link-level address
- struct sockaddr_dl* lla = (struct sockaddr_dl*)ifa->ifa_addr;
-
- // printf("\tLink level address, type %02X\n", lla->sdl_type);
-
- if (lla->sdl_type == IFT_ETHER)
- {
- // Use the first ethernet MAC in the list.
- // For some reason, the macro LLADDR() defined in net/if_dl.h doesn't expand correctly. This is what it would do.
- a = (unsigned char*)&((lla)->sdl_data);
- a += (lla)->sdl_nlen;
-
- if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5])
- {
- continue;
- }
-
- if (node_id)
- {
- memcpy(node_id, a, 6);
- result = 1;
- }
-
- // We found one.
- break;
- }
- }
- }
- freeifaddrs(ifap);
-
- return result;
-}
-
-#else
-
-// Linux version of the UUID generation code...
-/*
- * Get the ethernet hardware address, if we can find it...
- */
-#include <unistd.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/stat.h>
-#include <sys/file.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <net/if.h>
-#define HAVE_NETINET_IN_H
-#ifdef HAVE_NETINET_IN_H
-#include <netinet/in.h>
-#if !LL_DARWIN
-#include <linux/sockios.h>
-#endif
-#endif
-
- // static
-S32 LLUUID::getNodeID(unsigned char* node_id)
-{
- int sd;
- struct ifreq ifr, * ifrp;
- struct ifconf ifc;
- char buf[1024];
- int n, i;
- unsigned char* a;
-
- /*
- * BSD 4.4 defines the size of an ifreq to be
- * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len
- * However, under earlier systems, sa_len isn't present, so the size is
- * just sizeof(struct ifreq)
- */
-#ifdef HAVE_SA_LEN
-#ifndef max
-#define max(a,b) ((a) > (b) ? (a) : (b))
-#endif
-#define ifreq_size(i) max(sizeof(struct ifreq),\
- sizeof((i).ifr_name)+(i).ifr_addr.sa_len)
-#else
-#define ifreq_size(i) sizeof(struct ifreq)
-#endif /* HAVE_SA_LEN*/
-
- sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
- if (sd < 0) {
- return -1;
- }
- memset(buf, 0, sizeof(buf));
- ifc.ifc_len = sizeof(buf);
- ifc.ifc_buf = buf;
- if (ioctl(sd, SIOCGIFCONF, (char*)&ifc) < 0) {
- close(sd);
- return -1;
- }
- n = ifc.ifc_len;
- for (i = 0; i < n; i += ifreq_size(*ifr)) {
- ifrp = (struct ifreq*)((char*)ifc.ifc_buf + i);
- strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); /* Flawfinder: ignore */
-#ifdef SIOCGIFHWADDR
- if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0)
- continue;
- a = (unsigned char*)&ifr.ifr_hwaddr.sa_data;
-#else
-#ifdef SIOCGENADDR
- if (ioctl(sd, SIOCGENADDR, &ifr) < 0)
- continue;
- a = (unsigned char*)ifr.ifr_enaddr;
-#else
- /*
- * XXX we don't have a way of getting the hardware
- * address
- */
- close(sd);
- return 0;
-#endif /* SIOCGENADDR */
-#endif /* SIOCGIFHWADDR */
- if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5])
- continue;
- if (node_id) {
- memcpy(node_id, a, 6); /* Flawfinder: ignore */
- close(sd);
- return 1;
- }
- }
- close(sd);
- return 0;
-}
-
-#endif
-
-S32 LLUUID::cmpTime(uuid_time_t* t1, uuid_time_t* t2)
-{
- // Compare two time values.
-
- if (t1->high < t2->high) return -1;
- if (t1->high > t2->high) return 1;
- if (t1->low < t2->low) return -1;
- if (t1->low > t2->low) return 1;
- return 0;
-}
-
-void LLUUID::getSystemTime(uuid_time_t* timestamp)
-{
- // Get system time with 100ns precision. Time is since Oct 15, 1582.
-#if LL_WINDOWS
- ULARGE_INTEGER time;
- GetSystemTimeAsFileTime((FILETIME*)&time);
- // NT keeps time in FILETIME format which is 100ns ticks since
- // Jan 1, 1601. UUIDs use time in 100ns ticks since Oct 15, 1582.
- // The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec)
- // + 18 years and 5 leap days.
- time.QuadPart +=
- (unsigned __int64)(1000 * 1000 * 10) // seconds
- * (unsigned __int64)(60 * 60 * 24) // days
- * (unsigned __int64)(17 + 30 + 31 + 365 * 18 + 5); // # of days
-
- timestamp->high = time.HighPart;
- timestamp->low = time.LowPart;
-#else
- struct timeval tp;
- gettimeofday(&tp, 0);
-
- // Offset between UUID formatted times and Unix formatted times.
- // UUID UTC base time is October 15, 1582.
- // Unix base time is January 1, 1970.
- U64 uuid_time = ((U64)tp.tv_sec * 10000000) + (tp.tv_usec * 10) +
- U64L(0x01B21DD213814000);
- timestamp->high = (U32)(uuid_time >> 32);
- timestamp->low = (U32)(uuid_time & 0xFFFFFFFF);
-#endif
-}
-
-void LLUUID::getCurrentTime(uuid_time_t* timestamp)
-{
- // Get current time as 60 bit 100ns ticks since whenever.
- // Compensate for the fact that real clock resolution is less
- // than 100ns.
-
- const U32 uuids_per_tick = 1024;
-
- static uuid_time_t time_last;
- static U32 uuids_this_tick;
- static bool init = false;
-
- if (!init) {
- getSystemTime(&time_last);
- uuids_this_tick = uuids_per_tick;
- init = true;
- mMutex = new LLMutex();
- }
-
- uuid_time_t time_now = { 0,0 };
-
- while (1) {
- getSystemTime(&time_now);
-
- // if clock reading changed since last UUID generated
- if (cmpTime(&time_last, &time_now)) {
- // reset count of uuid's generated with this clock reading
- uuids_this_tick = 0;
- break;
- }
- if (uuids_this_tick < uuids_per_tick) {
- uuids_this_tick++;
- break;
- }
- // going too fast for our clock; spin
- }
-
- time_last = time_now;
-
- if (uuids_this_tick != 0) {
- if (time_now.low & 0x80000000) {
- time_now.low += uuids_this_tick;
- if (!(time_now.low & 0x80000000))
- time_now.high++;
- }
- else
- time_now.low += uuids_this_tick;
- }
-
- timestamp->high = time_now.high;
- timestamp->low = time_now.low;
-}
-
-void LLUUID::generate()
-{
- // Create a UUID.
- uuid_time_t timestamp;
-
- static unsigned char node_id[6]; /* Flawfinder: ignore */
- static int has_init = 0;
-
- // Create a UUID.
- static uuid_time_t time_last = { 0,0 };
- static U16 clock_seq = 0;
-#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
- static U32 seed = 0L; // dummy seed. reset it below
-#endif
- if (!has_init)
- {
- has_init = 1;
- if (getNodeID(node_id) <= 0)
- {
- get_random_bytes(node_id, 6);
- /*
- * Set multicast bit, to prevent conflicts
- * with IEEE 802 addresses obtained from
- * network cards
- */
- node_id[0] |= 0x80;
- }
-
- getCurrentTime(&time_last);
-#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
- seed = time_last.low;
-#endif
-
-#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR
- clock_seq = (U16)janky_fast_random_seeded_bytes(seed, 65536);
-#else
- clock_seq = (U16)ll_rand(65536);
-#endif
- }
-
- // get current time
- getCurrentTime(×tamp);
- U16 our_clock_seq = clock_seq;
-
- // if clock hasn't changed or went backward, change clockseq
- if (cmpTime(×tamp, &time_last) != 1)
- {
- LLMutexLock lock(mMutex);
- clock_seq = (clock_seq + 1) & 0x3FFF;
- if (clock_seq == 0)
- clock_seq++;
- our_clock_seq = clock_seq; // Ensure we're using a different clock_seq value from previous time
- }
-
- time_last = timestamp;
-
- memcpy(mData + 10, node_id, 6); /* Flawfinder: ignore */
- U32 tmp;
- tmp = timestamp.low;
- mData[3] = (unsigned char)tmp;
- tmp >>= 8;
- mData[2] = (unsigned char)tmp;
- tmp >>= 8;
- mData[1] = (unsigned char)tmp;
- tmp >>= 8;
- mData[0] = (unsigned char)tmp;
-
- tmp = (U16)timestamp.high;
- mData[5] = (unsigned char)tmp;
- tmp >>= 8;
- mData[4] = (unsigned char)tmp;
-
- tmp = (timestamp.high >> 16) | 0x1000;
- mData[7] = (unsigned char)tmp;
- tmp >>= 8;
- mData[6] = (unsigned char)tmp;
-
- tmp = our_clock_seq;
-
- mData[9] = (unsigned char)tmp;
- tmp >>= 8;
- mData[8] = (unsigned char)tmp;
-
- HBXXH128::digest(*this, (const void*)mData, 16);
-}
-
-void LLUUID::generate(const std::string& hash_string)
-{
- HBXXH128::digest(*this, hash_string);
-}
-
-U32 LLUUID::getRandomSeed()
-{
- static unsigned char seed[16]; /* Flawfinder: ignore */
-
- getNodeID(&seed[0]);
-
- // Incorporate the pid into the seed to prevent
- // processes that start on the same host at the same
- // time from generating the same seed.
- pid_t pid = LLApp::getPid();
-
- seed[6] = (unsigned char)(pid >> 8);
- seed[7] = (unsigned char)(pid);
- getSystemTime((uuid_time_t*)(&seed[8]));
-
- U64 seed64 = HBXXH64::digest((const void*)seed, 16);
- return U32(seed64) ^ U32(seed64 >> 32);
-}
-
-bool LLUUID::parseUUID(const std::string& buf, LLUUID* value)
-{
- if (buf.empty() || value == NULL)
- {
- return false;
- }
-
- std::string temp(buf);
- LLStringUtil::trim(temp);
- if (LLUUID::validate(temp))
- {
- value->set(temp);
- return true;
- }
- return false;
-}
-
-//static
-LLUUID LLUUID::generateNewID(std::string hash_string)
-{
- LLUUID new_id;
- if (hash_string.empty())
- {
- new_id.generate();
- }
- else
- {
- new_id.generate(hash_string);
- }
- return new_id;
-}
-
-LLAssetID LLTransactionID::makeAssetID(const LLUUID& session) const
-{
- LLAssetID result;
- if (isNull())
- {
- result.setNull();
- }
- else
- {
- combine(session, result);
- }
- return result;
-}
-
-// Construct
-LLUUID::LLUUID()
-{
- setNull();
-}
-
-
-// Faster than copying from memory
-void LLUUID::setNull()
-{
- U32* word = (U32*)mData;
- word[0] = 0;
- word[1] = 0;
- word[2] = 0;
- word[3] = 0;
-}
-
-
-// Compare
-bool LLUUID::operator==(const LLUUID& rhs) const
-{
- U32* tmp = (U32*)mData;
- U32* rhstmp = (U32*)rhs.mData;
- // Note: binary & to avoid branching
- return
- (tmp[0] == rhstmp[0]) &
- (tmp[1] == rhstmp[1]) &
- (tmp[2] == rhstmp[2]) &
- (tmp[3] == rhstmp[3]);
-}
-
-
-bool LLUUID::operator!=(const LLUUID& rhs) const
-{
- U32* tmp = (U32*)mData;
- U32* rhstmp = (U32*)rhs.mData;
- // Note: binary | to avoid branching
- return
- (tmp[0] != rhstmp[0]) |
- (tmp[1] != rhstmp[1]) |
- (tmp[2] != rhstmp[2]) |
- (tmp[3] != rhstmp[3]);
-}
-
-/*
-// JC: This is dangerous. It allows UUIDs to be cast automatically
-// to integers, among other things. Use isNull() or notNull().
- LLUUID::operator bool() const
-{
- U32 *word = (U32 *)mData;
- return (word[0] | word[1] | word[2] | word[3]) > 0;
-}
-*/
-
-bool LLUUID::notNull() const
-{
- U32* word = (U32*)mData;
- return (word[0] | word[1] | word[2] | word[3]) > 0;
-}
-
-// Faster than == LLUUID::null because doesn't require
-// as much memory access.
-bool LLUUID::isNull() const
-{
- U32* word = (U32*)mData;
- // If all bits are zero, return !0 == true
- return !(word[0] | word[1] | word[2] | word[3]);
-}
-
-LLUUID::LLUUID(const char* in_string)
-{
- if (!in_string || in_string[0] == 0)
- {
- setNull();
- return;
- }
-
- set(in_string);
-}
-
-LLUUID::LLUUID(const std::string& in_string)
-{
- if (in_string.empty())
- {
- setNull();
- return;
- }
-
- set(in_string);
-}
-
-// IW: DON'T "optimize" these w/ U32s or you'll scoogie the sort order
-// IW: this will make me very sad
-bool LLUUID::operator<(const LLUUID& rhs) const
-{
- U32 i;
- for (i = 0; i < (UUID_BYTES - 1); i++)
- {
- if (mData[i] != rhs.mData[i])
- {
- return (mData[i] < rhs.mData[i]);
- }
- }
- return (mData[UUID_BYTES - 1] < rhs.mData[UUID_BYTES - 1]);
-}
-
-bool LLUUID::operator>(const LLUUID& rhs) const
-{
- U32 i;
- for (i = 0; i < (UUID_BYTES - 1); i++)
- {
- if (mData[i] != rhs.mData[i])
- {
- return (mData[i] > rhs.mData[i]);
- }
- }
- return (mData[UUID_BYTES - 1] > rhs.mData[UUID_BYTES - 1]);
-}
-
-U16 LLUUID::getCRC16() const
-{
- // A UUID is 16 bytes, or 8 shorts.
- U16* short_data = (U16*)mData;
- U16 out = 0;
- out += short_data[0];
- out += short_data[1];
- out += short_data[2];
- out += short_data[3];
- out += short_data[4];
- out += short_data[5];
- out += short_data[6];
- out += short_data[7];
- return out;
-}
-
-U32 LLUUID::getCRC32() const
-{
- U32* tmp = (U32*)mData;
- return tmp[0] + tmp[1] + tmp[2] + tmp[3];
-}
+/** + * @file lluuid.cpp + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + + // We can't use WIN32_LEAN_AND_MEAN here, needs lots of includes. +#if LL_WINDOWS +#include "llwin32headers.h" +// ugh, this is ugly. We need to straighten out our linking for this library +#pragma comment(lib, "IPHLPAPI.lib") +#include <iphlpapi.h> +#endif + +#include "llapp.h" +#include "lldefs.h" +#include "llerror.h" + +#include "lluuid.h" +#include "llerror.h" +#include "llrand.h" +#include "llstring.h" +#include "lltimer.h" +#include "llthread.h" +#include "llmutex.h" +#include "llmd5.h" +#include "hbxxh.h" + +const LLUUID LLUUID::null; +const LLTransactionID LLTransactionID::tnull; + +// static +LLMutex* LLUUID::mMutex = NULL; + + + +/* + +NOT DONE YET!!! + +static char BASE85_TABLE[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '!', '#', '$', '%', '&', '(', ')', '*', + '+', '-', ';', '[', '=', '>', '?', '@', '^', '_', + '`', '{', '|', '}', '~', '\0' +}; + + +void encode( char * fiveChars, unsigned int word ) throw( ) +{ +for( int ix = 0; ix < 5; ++ix ) { +fiveChars[4-ix] = encodeTable[ word % 85]; +word /= 85; +} +} + +To decode: +unsigned int decode( char const * fiveChars ) throw( bad_input_data ) +{ +unsigned int ret = 0; +for( int ix = 0; ix < 5; ++ix ) { +char * s = strchr( encodeTable, fiveChars[ ix ] ); +if( s == 0 ) LLTHROW(bad_input_data()); +ret = ret * 85 + (s-encodeTable); +} +return ret; +} + +void LLUUID::toBase85(char* out) +{ + U32* me = (U32*)&(mData[0]); + for(S32 i = 0; i < 4; ++i) + { + char* o = &out[i*i]; + for(S32 j = 0; j < 5; ++j) + { + o[4-j] = BASE85_TABLE[ me[i] % 85]; + word /= 85; + } + } +} + +unsigned int decode( char const * fiveChars ) throw( bad_input_data ) +{ + unsigned int ret = 0; + for( S32 ix = 0; ix < 5; ++ix ) + { + char * s = strchr( encodeTable, fiveChars[ ix ] ); + ret = ret * 85 + (s-encodeTable); + } + return ret; +} +*/ + +#define LL_USE_JANKY_RANDOM_NUMBER_GENERATOR 0 +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR +/** + * @brief a global for + */ +static U64 sJankyRandomSeed(LLUUID::getRandomSeed()); + +/** + * @brief generate a random U32. + */ +U32 janky_fast_random_bytes() +{ + sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); + return (U32)sJankyRandomSeed; +} + +/** + * @brief generate a random U32 from [0, val) + */ +U32 janky_fast_random_byes_range(U32 val) +{ + sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); + return (U32)(sJankyRandomSeed) % val; +} + +/** + * @brief generate a random U32 from [0, val) + */ +U32 janky_fast_random_seeded_bytes(U32 seed, U32 val) +{ + seed = U64L(1664525) * (U64)(seed)+U64L(1013904223); + return (U32)(seed) % val; +} +#endif + +// Common to all UUID implementations +void LLUUID::toString(std::string& out) const +{ + out = llformat( + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + (U8)(mData[0]), + (U8)(mData[1]), + (U8)(mData[2]), + (U8)(mData[3]), + (U8)(mData[4]), + (U8)(mData[5]), + (U8)(mData[6]), + (U8)(mData[7]), + (U8)(mData[8]), + (U8)(mData[9]), + (U8)(mData[10]), + (U8)(mData[11]), + (U8)(mData[12]), + (U8)(mData[13]), + (U8)(mData[14]), + (U8)(mData[15])); +} + +// *TODO: deprecate +void LLUUID::toString(char* out) const +{ + std::string buffer; + toString(buffer); + strcpy(out, buffer.c_str()); /* Flawfinder: ignore */ +} + +void LLUUID::toCompressedString(std::string& out) const +{ + char bytes[UUID_BYTES + 1]; + memcpy(bytes, mData, UUID_BYTES); /* Flawfinder: ignore */ + bytes[UUID_BYTES] = '\0'; + out.assign(bytes, UUID_BYTES); +} + +// *TODO: deprecate +void LLUUID::toCompressedString(char* out) const +{ + memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */ + out[UUID_BYTES] = '\0'; +} + +std::string LLUUID::getString() const +{ + return asString(); +} + +std::string LLUUID::asString() const +{ + std::string str; + toString(str); + return str; +} + +bool LLUUID::set(const char* in_string, bool emit) +{ + return set(ll_safe_string(in_string), emit); +} + +bool LLUUID::set(const std::string& in_string, bool emit) +{ + bool broken_format = false; + + // empty strings should make NULL uuid + if (in_string.empty()) + { + setNull(); + return true; + } + + if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ + { + // I'm a moron. First implementation didn't have the right UUID format. + // Shouldn't see any of these any more + if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ + { + if (emit) + { + LL_WARNS() << "Warning! Using broken UUID string format" << LL_ENDL; + } + broken_format = true; + } + else + { + // Bad UUID string. Spam as INFO, as most cases we don't care. + if (emit) + { + //don't spam the logs because a resident can't spell. + LL_WARNS() << "Bad UUID string: " << in_string << LL_ENDL; + } + setNull(); + return false; + } + } + + U8 cur_pos = 0; + S32 i; + for (i = 0; i < UUID_BYTES; i++) + { + if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) + { + cur_pos++; + if (broken_format && (i == 10)) + { + // Missing - in the broken format + cur_pos--; + } + } + + mData[i] = 0; + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + mData[i] += (U8)(in_string[cur_pos] - '0'); + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); + } + else + { + if (emit) + { + LL_WARNS() << "Invalid UUID string character" << LL_ENDL; + } + setNull(); + return false; + } + + mData[i] = mData[i] << 4; + cur_pos++; + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + mData[i] += (U8)(in_string[cur_pos] - '0'); + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); + } + else + { + if (emit) + { + LL_WARNS() << "Invalid UUID string character" << LL_ENDL; + } + setNull(); + return false; + } + cur_pos++; + } + + return true; +} + +bool LLUUID::validate(const std::string& in_string) +{ + bool broken_format = false; + if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ + { + // I'm a moron. First implementation didn't have the right UUID format. + if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ + { + broken_format = true; + } + else + { + return false; + } + } + + U8 cur_pos = 0; + for (U32 i = 0; i < 16; i++) + { + if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) + { + cur_pos++; + if (broken_format && (i == 10)) + { + // Missing - in the broken format + cur_pos--; + } + } + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + } + else + { + return false; + } + + cur_pos++; + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + } + else + { + return false; + } + cur_pos++; + } + return true; +} + +const LLUUID& LLUUID::operator^=(const LLUUID& rhs) +{ + U32* me = (U32*)&(mData[0]); + const U32* other = (U32*)&(rhs.mData[0]); + for (S32 i = 0; i < 4; ++i) + { + me[i] = me[i] ^ other[i]; + } + return *this; +} + +LLUUID LLUUID::operator^(const LLUUID& rhs) const +{ + LLUUID id(*this); + id ^= rhs; + return id; +} + +// WARNING: this algorithm SHALL NOT be changed. It is also used by the server +// and plays a role in some assets validation (e.g. clothing items). Changing +// it would cause invalid assets. +void LLUUID::combine(const LLUUID& other, LLUUID& result) const +{ + LLMD5 md5_uuid; + md5_uuid.update((unsigned char*)mData, 16); + md5_uuid.update((unsigned char*)other.mData, 16); + md5_uuid.finalize(); + md5_uuid.raw_digest(result.mData); +} + +LLUUID LLUUID::combine(const LLUUID& other) const +{ + LLUUID combination; + combine(other, combination); + return combination; +} + +std::ostream& operator<<(std::ostream& s, const LLUUID& uuid) +{ + std::string uuid_str; + uuid.toString(uuid_str); + s << uuid_str; + return s; +} + +std::istream& operator>>(std::istream& s, LLUUID& uuid) +{ + U32 i; + char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */ + for (i = 0; i < UUID_STR_LENGTH - 1; i++) + { + s >> uuid_str[i]; + } + uuid_str[i] = '\0'; + uuid.set(std::string(uuid_str)); + return s; +} + +static void get_random_bytes(void* buf, int nbytes) +{ + int i; + char* cp = (char*)buf; + + // *NOTE: If we are not using the janky generator ll_rand() + // generates at least 3 good bytes of data since it is 0 to + // RAND_MAX. This could be made more efficient by copying all the + // bytes. + for (i = 0; i < nbytes; i++) +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR + * cp++ = janky_fast_random_bytes() & 0xFF; +#else + * cp++ = ll_rand() & 0xFF; +#endif + return; +} + +#if LL_WINDOWS + +typedef struct _ASTAT_ +{ + ADAPTER_STATUS adapt; + NAME_BUFFER NameBuff[30]; +}ASTAT, * PASTAT; + +// static +S32 LLUUID::getNodeID(unsigned char* node_id) +{ + ASTAT Adapter; + NCB Ncb; + UCHAR uRetCode; + LANA_ENUM lenum; + int i; + int retval = 0; + + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBENUM; + Ncb.ncb_buffer = (UCHAR*)&lenum; + Ncb.ncb_length = sizeof(lenum); + uRetCode = Netbios(&Ncb); + + for (i = 0; i < lenum.length; i++) + { + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBRESET; + Ncb.ncb_lana_num = lenum.lana[i]; + + uRetCode = Netbios(&Ncb); + + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBASTAT; + Ncb.ncb_lana_num = lenum.lana[i]; + + strcpy((char*)Ncb.ncb_callname, "* "); /* Flawfinder: ignore */ + Ncb.ncb_buffer = (unsigned char*)&Adapter; + Ncb.ncb_length = sizeof(Adapter); + + uRetCode = Netbios(&Ncb); + if (uRetCode == 0) + { + memcpy(node_id, Adapter.adapt.adapter_address, 6); /* Flawfinder: ignore */ + retval = 1; + } + } + return retval; +} + +#elif LL_DARWIN +// macOS version of the UUID generation code... +/* + * Get an ethernet hardware address, if we can find it... + */ +#include <unistd.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/route.h> +#include <ifaddrs.h> + + // static +S32 LLUUID::getNodeID(unsigned char* node_id) +{ + int i; + unsigned char* a = NULL; + struct ifaddrs* ifap, * ifa; + int rv; + S32 result = 0; + + if ((rv = getifaddrs(&ifap)) == -1) + { + return -1; + } + if (ifap == NULL) + { + return -1; + } + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) + { + // printf("Interface %s, address family %d, ", ifa->ifa_name, ifa->ifa_addr->sa_family); + for (i = 0; i < ifa->ifa_addr->sa_len; i++) + { + // printf("%02X ", (unsigned char)ifa->ifa_addr->sa_data[i]); + } + // printf("\n"); + + if (ifa->ifa_addr->sa_family == AF_LINK) + { + // This is a link-level address + struct sockaddr_dl* lla = (struct sockaddr_dl*)ifa->ifa_addr; + + // printf("\tLink level address, type %02X\n", lla->sdl_type); + + if (lla->sdl_type == IFT_ETHER) + { + // Use the first ethernet MAC in the list. + // For some reason, the macro LLADDR() defined in net/if_dl.h doesn't expand correctly. This is what it would do. + a = (unsigned char*)&((lla)->sdl_data); + a += (lla)->sdl_nlen; + + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + { + continue; + } + + if (node_id) + { + memcpy(node_id, a, 6); + result = 1; + } + + // We found one. + break; + } + } + } + freeifaddrs(ifap); + + return result; +} + +#else + +// Linux version of the UUID generation code... +/* + * Get the ethernet hardware address, if we can find it... + */ +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <net/if.h> +#define HAVE_NETINET_IN_H +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#if !LL_DARWIN +#include <linux/sockios.h> +#endif +#endif + + // static +S32 LLUUID::getNodeID(unsigned char* node_id) +{ + int sd; + struct ifreq ifr, * ifrp; + struct ifconf ifc; + char buf[1024]; + int n, i; + unsigned char* a; + + /* + * BSD 4.4 defines the size of an ifreq to be + * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len + * However, under earlier systems, sa_len isn't present, so the size is + * just sizeof(struct ifreq) + */ +#ifdef HAVE_SA_LEN +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#define ifreq_size(i) max(sizeof(struct ifreq),\ + sizeof((i).ifr_name)+(i).ifr_addr.sa_len) +#else +#define ifreq_size(i) sizeof(struct ifreq) +#endif /* HAVE_SA_LEN*/ + + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sd < 0) { + return -1; + } + memset(buf, 0, sizeof(buf)); + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl(sd, SIOCGIFCONF, (char*)&ifc) < 0) { + close(sd); + return -1; + } + n = ifc.ifc_len; + for (i = 0; i < n; i += ifreq_size(*ifr)) { + ifrp = (struct ifreq*)((char*)ifc.ifc_buf + i); + strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); /* Flawfinder: ignore */ +#ifdef SIOCGIFHWADDR + if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) + continue; + a = (unsigned char*)&ifr.ifr_hwaddr.sa_data; +#else +#ifdef SIOCGENADDR + if (ioctl(sd, SIOCGENADDR, &ifr) < 0) + continue; + a = (unsigned char*)ifr.ifr_enaddr; +#else + /* + * XXX we don't have a way of getting the hardware + * address + */ + close(sd); + return 0; +#endif /* SIOCGENADDR */ +#endif /* SIOCGIFHWADDR */ + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + continue; + if (node_id) { + memcpy(node_id, a, 6); /* Flawfinder: ignore */ + close(sd); + return 1; + } + } + close(sd); + return 0; +} + +#endif + +S32 LLUUID::cmpTime(uuid_time_t* t1, uuid_time_t* t2) +{ + // Compare two time values. + + if (t1->high < t2->high) return -1; + if (t1->high > t2->high) return 1; + if (t1->low < t2->low) return -1; + if (t1->low > t2->low) return 1; + return 0; +} + +void LLUUID::getSystemTime(uuid_time_t* timestamp) +{ + // Get system time with 100ns precision. Time is since Oct 15, 1582. +#if LL_WINDOWS + ULARGE_INTEGER time; + GetSystemTimeAsFileTime((FILETIME*)&time); + // NT keeps time in FILETIME format which is 100ns ticks since + // Jan 1, 1601. UUIDs use time in 100ns ticks since Oct 15, 1582. + // The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec) + // + 18 years and 5 leap days. + time.QuadPart += + (unsigned __int64)(1000 * 1000 * 10) // seconds + * (unsigned __int64)(60 * 60 * 24) // days + * (unsigned __int64)(17 + 30 + 31 + 365 * 18 + 5); // # of days + + timestamp->high = time.HighPart; + timestamp->low = time.LowPart; +#else + struct timeval tp; + gettimeofday(&tp, 0); + + // Offset between UUID formatted times and Unix formatted times. + // UUID UTC base time is October 15, 1582. + // Unix base time is January 1, 1970. + U64 uuid_time = ((U64)tp.tv_sec * 10000000) + (tp.tv_usec * 10) + + U64L(0x01B21DD213814000); + timestamp->high = (U32)(uuid_time >> 32); + timestamp->low = (U32)(uuid_time & 0xFFFFFFFF); +#endif +} + +void LLUUID::getCurrentTime(uuid_time_t* timestamp) +{ + // Get current time as 60 bit 100ns ticks since whenever. + // Compensate for the fact that real clock resolution is less + // than 100ns. + + const U32 uuids_per_tick = 1024; + + static uuid_time_t time_last; + static U32 uuids_this_tick; + static bool init = false; + + if (!init) { + getSystemTime(&time_last); + uuids_this_tick = uuids_per_tick; + init = true; + mMutex = new LLMutex(); + } + + uuid_time_t time_now = { 0,0 }; + + while (1) { + getSystemTime(&time_now); + + // if clock reading changed since last UUID generated + if (cmpTime(&time_last, &time_now)) { + // reset count of uuid's generated with this clock reading + uuids_this_tick = 0; + break; + } + if (uuids_this_tick < uuids_per_tick) { + uuids_this_tick++; + break; + } + // going too fast for our clock; spin + } + + time_last = time_now; + + if (uuids_this_tick != 0) { + if (time_now.low & 0x80000000) { + time_now.low += uuids_this_tick; + if (!(time_now.low & 0x80000000)) + time_now.high++; + } + else + time_now.low += uuids_this_tick; + } + + timestamp->high = time_now.high; + timestamp->low = time_now.low; +} + +void LLUUID::generate() +{ + // Create a UUID. + uuid_time_t timestamp; + + static unsigned char node_id[6]; /* Flawfinder: ignore */ + static int has_init = 0; + + // Create a UUID. + static uuid_time_t time_last = { 0,0 }; + static U16 clock_seq = 0; +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR + static U32 seed = 0L; // dummy seed. reset it below +#endif + if (!has_init) + { + has_init = 1; + if (getNodeID(node_id) <= 0) + { + get_random_bytes(node_id, 6); + /* + * Set multicast bit, to prevent conflicts + * with IEEE 802 addresses obtained from + * network cards + */ + node_id[0] |= 0x80; + } + + getCurrentTime(&time_last); +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR + seed = time_last.low; +#endif + +#if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR + clock_seq = (U16)janky_fast_random_seeded_bytes(seed, 65536); +#else + clock_seq = (U16)ll_rand(65536); +#endif + } + + // get current time + getCurrentTime(×tamp); + U16 our_clock_seq = clock_seq; + + // if clock hasn't changed or went backward, change clockseq + if (cmpTime(×tamp, &time_last) != 1) + { + LLMutexLock lock(mMutex); + clock_seq = (clock_seq + 1) & 0x3FFF; + if (clock_seq == 0) + clock_seq++; + our_clock_seq = clock_seq; // Ensure we're using a different clock_seq value from previous time + } + + time_last = timestamp; + + memcpy(mData + 10, node_id, 6); /* Flawfinder: ignore */ + U32 tmp; + tmp = timestamp.low; + mData[3] = (unsigned char)tmp; + tmp >>= 8; + mData[2] = (unsigned char)tmp; + tmp >>= 8; + mData[1] = (unsigned char)tmp; + tmp >>= 8; + mData[0] = (unsigned char)tmp; + + tmp = (U16)timestamp.high; + mData[5] = (unsigned char)tmp; + tmp >>= 8; + mData[4] = (unsigned char)tmp; + + tmp = (timestamp.high >> 16) | 0x1000; + mData[7] = (unsigned char)tmp; + tmp >>= 8; + mData[6] = (unsigned char)tmp; + + tmp = our_clock_seq; + + mData[9] = (unsigned char)tmp; + tmp >>= 8; + mData[8] = (unsigned char)tmp; + + HBXXH128::digest(*this, (const void*)mData, 16); +} + +void LLUUID::generate(const std::string& hash_string) +{ + HBXXH128::digest(*this, hash_string); +} + +U32 LLUUID::getRandomSeed() +{ + static unsigned char seed[16]; /* Flawfinder: ignore */ + + getNodeID(&seed[0]); + + // Incorporate the pid into the seed to prevent + // processes that start on the same host at the same + // time from generating the same seed. + pid_t pid = LLApp::getPid(); + + seed[6] = (unsigned char)(pid >> 8); + seed[7] = (unsigned char)(pid); + getSystemTime((uuid_time_t*)(&seed[8])); + + U64 seed64 = HBXXH64::digest((const void*)seed, 16); + return U32(seed64) ^ U32(seed64 >> 32); +} + +bool LLUUID::parseUUID(const std::string& buf, LLUUID* value) +{ + if (buf.empty() || value == NULL) + { + return false; + } + + std::string temp(buf); + LLStringUtil::trim(temp); + if (LLUUID::validate(temp)) + { + value->set(temp); + return true; + } + return false; +} + +//static +LLUUID LLUUID::generateNewID(std::string hash_string) +{ + LLUUID new_id; + if (hash_string.empty()) + { + new_id.generate(); + } + else + { + new_id.generate(hash_string); + } + return new_id; +} + +LLAssetID LLTransactionID::makeAssetID(const LLUUID& session) const +{ + LLAssetID result; + if (isNull()) + { + result.setNull(); + } + else + { + combine(session, result); + } + return result; +} + +// Construct +LLUUID::LLUUID() +{ + setNull(); +} + + +// Faster than copying from memory +void LLUUID::setNull() +{ + U32* word = (U32*)mData; + word[0] = 0; + word[1] = 0; + word[2] = 0; + word[3] = 0; +} + + +// Compare +bool LLUUID::operator==(const LLUUID& rhs) const +{ + U32* tmp = (U32*)mData; + U32* rhstmp = (U32*)rhs.mData; + // Note: binary & to avoid branching + return + (tmp[0] == rhstmp[0]) & + (tmp[1] == rhstmp[1]) & + (tmp[2] == rhstmp[2]) & + (tmp[3] == rhstmp[3]); +} + + +bool LLUUID::operator!=(const LLUUID& rhs) const +{ + U32* tmp = (U32*)mData; + U32* rhstmp = (U32*)rhs.mData; + // Note: binary | to avoid branching + return + (tmp[0] != rhstmp[0]) | + (tmp[1] != rhstmp[1]) | + (tmp[2] != rhstmp[2]) | + (tmp[3] != rhstmp[3]); +} + +/* +// JC: This is dangerous. It allows UUIDs to be cast automatically +// to integers, among other things. Use isNull() or notNull(). + LLUUID::operator bool() const +{ + U32 *word = (U32 *)mData; + return (word[0] | word[1] | word[2] | word[3]) > 0; +} +*/ + +bool LLUUID::notNull() const +{ + U32* word = (U32*)mData; + return (word[0] | word[1] | word[2] | word[3]) > 0; +} + +// Faster than == LLUUID::null because doesn't require +// as much memory access. +bool LLUUID::isNull() const +{ + U32* word = (U32*)mData; + // If all bits are zero, return !0 == true + return !(word[0] | word[1] | word[2] | word[3]); +} + +LLUUID::LLUUID(const char* in_string) +{ + if (!in_string || in_string[0] == 0) + { + setNull(); + return; + } + + set(in_string); +} + +LLUUID::LLUUID(const std::string& in_string) +{ + if (in_string.empty()) + { + setNull(); + return; + } + + set(in_string); +} + +// IW: DON'T "optimize" these w/ U32s or you'll scoogie the sort order +// IW: this will make me very sad +bool LLUUID::operator<(const LLUUID& rhs) const +{ + U32 i; + for (i = 0; i < (UUID_BYTES - 1); i++) + { + if (mData[i] != rhs.mData[i]) + { + return (mData[i] < rhs.mData[i]); + } + } + return (mData[UUID_BYTES - 1] < rhs.mData[UUID_BYTES - 1]); +} + +bool LLUUID::operator>(const LLUUID& rhs) const +{ + U32 i; + for (i = 0; i < (UUID_BYTES - 1); i++) + { + if (mData[i] != rhs.mData[i]) + { + return (mData[i] > rhs.mData[i]); + } + } + return (mData[UUID_BYTES - 1] > rhs.mData[UUID_BYTES - 1]); +} + +U16 LLUUID::getCRC16() const +{ + // A UUID is 16 bytes, or 8 shorts. + U16* short_data = (U16*)mData; + U16 out = 0; + out += short_data[0]; + out += short_data[1]; + out += short_data[2]; + out += short_data[3]; + out += short_data[4]; + out += short_data[5]; + out += short_data[6]; + out += short_data[7]; + return out; +} + +U32 LLUUID::getCRC32() const +{ + U32* tmp = (U32*)mData; + return tmp[0] + tmp[1] + tmp[2] + tmp[3]; +} diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 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 <iostream>
-#include <set>
-#include <vector>
-#include "stdtypes.h"
-#include "llpreprocessor.h"
-#include <boost/functional/hash.hpp>
-
-class LLMutex;
-
-const S32 UUID_BYTES = 16;
-const S32 UUID_WORDS = 4;
-const S32 UUID_STR_LENGTH = 37; // actually wrong, should be 36 and use size below
-const S32 UUID_STR_SIZE = 37;
-const S32 UUID_BASE85_LENGTH = 21; // including the trailing NULL.
-
-struct uuid_time_t {
- U32 high;
- U32 low;
- };
-
-class LL_COMMON_API LLUUID
-{
-public:
- //
- // CREATORS
- //
- LLUUID();
- explicit LLUUID(const char *in_string); // Convert from string.
- explicit LLUUID(const std::string& in_string); // Convert from string.
- ~LLUUID() = default;
-
- //
- // MANIPULATORS
- //
- void generate(); // Generate a new UUID
- void generate(const std::string& stream); //Generate a new UUID based on hash of input stream
-
- static LLUUID generateNewID(std::string stream = ""); //static version of above for use in initializer expressions such as constructor params, etc.
-
- bool set(const char *in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings
- bool set(const std::string& in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings
- void setNull(); // Faster than setting to LLUUID::null.
-
- S32 cmpTime(uuid_time_t *t1, uuid_time_t *t2);
- static void getSystemTime(uuid_time_t *timestamp);
- void getCurrentTime(uuid_time_t *timestamp);
-
- //
- // ACCESSORS
- //
- bool isNull() const; // Faster than comparing to LLUUID::null.
- bool notNull() const; // Faster than comparing to LLUUID::null.
- // JC: This is dangerous. It allows UUIDs to be cast automatically
- // to integers, among other things. Use isNull() or notNull().
- // operator bool() const;
-
- // JC: These must return real bool's (not BOOLs) or else use of the STL
- // will generate bool-to-int performance warnings.
- bool operator==(const LLUUID &rhs) const;
- bool operator!=(const LLUUID &rhs) const;
- bool operator<(const LLUUID &rhs) const;
- bool operator>(const LLUUID &rhs) const;
-
- // xor functions. Useful since any two random uuids xored together
- // will yield a determinate third random unique id that can be
- // used as a key in a single uuid that represents 2.
- const LLUUID& operator^=(const LLUUID& rhs);
- LLUUID operator^(const LLUUID& rhs) const;
-
- // similar to functions above, but not invertible
- // yields a third random UUID that can be reproduced from the two inputs
- // but which, given the result and one of the inputs can't be used to
- // deduce the other input
- LLUUID combine(const LLUUID& other) const;
- void combine(const LLUUID& other, LLUUID& result) const;
-
- friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid);
- friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid);
-
- void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0)
- void toString(std::string& out) const;
- void toCompressedString(char *out) const; // Does not allocate memory, needs 17 characters (including \0)
- void toCompressedString(std::string& out) const;
-
- std::string asString() const;
- std::string getString() const;
-
- U16 getCRC16() const;
- U32 getCRC32() const;
-
- // Returns a 64 bits digest of the UUID, by XORing its two 64 bits long
- // words. HB
- inline U64 getDigest64() const
- {
- U64* tmp = (U64*)mData;
- return tmp[0] ^ tmp[1];
- }
-
- static bool validate(const std::string& in_string); // Validate that the UUID string is legal.
-
- static const LLUUID null;
- static LLMutex * mMutex;
-
- static U32 getRandomSeed();
- static S32 getNodeID(unsigned char * node_id);
-
- static bool parseUUID(const std::string& buf, LLUUID* value);
-
- U8 mData[UUID_BYTES];
-};
-static_assert(std::is_trivially_copyable<LLUUID>::value, "LLUUID must be trivial copy");
-static_assert(std::is_trivially_move_assignable<LLUUID>::value, "LLUUID must be trivial move");
-static_assert(std::is_standard_layout<LLUUID>::value, "LLUUID must be a standard layout type");
-
-typedef std::vector<LLUUID> uuid_vec_t;
-typedef std::set<LLUUID> uuid_set_t;
-
-// Helper structure for ordering lluuids in stl containers. eg:
-// std::map<LLUUID, LLWidget*, lluuid_less> widget_map;
-//
-// (isn't this the default behavior anyway? I think we could
-// everywhere replace these with uuid_set_t, but someone should
-// verify.)
-struct lluuid_less
-{
- bool operator()(const LLUUID& lhs, const LLUUID& rhs) const
- {
- return lhs < rhs;
- }
-};
-
-typedef std::set<LLUUID, lluuid_less> uuid_list_t;
-/*
- * Sub-classes for keeping transaction IDs and asset IDs
- * straight.
- */
-typedef LLUUID LLAssetID;
-
-class LL_COMMON_API LLTransactionID : public LLUUID
-{
-public:
- LLTransactionID() : LLUUID() { }
-
- static const LLTransactionID tnull;
- LLAssetID makeAssetID(const LLUUID& session) const;
-};
-
-// std::hash implementation for LLUUID
-namespace std
-{
- template<> struct hash<LLUUID>
- {
- inline size_t operator()(const LLUUID& id) const noexcept
- {
- return (size_t)id.getDigest64();
- }
- };
-}
-
-// For use with boost containers.
-inline size_t hash_value(const LLUUID& id) noexcept
-{
- return (size_t)id.getDigest64();
-}
-
-#endif // LL_LLUUID_H
+/** + * @file lluuid.h + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLUUID_H +#define LL_LLUUID_H + +#include <iostream> +#include <set> +#include <vector> +#include "stdtypes.h" +#include "llpreprocessor.h" +#include <boost/functional/hash.hpp> + +class LLMutex; + +const S32 UUID_BYTES = 16; +const S32 UUID_WORDS = 4; +const S32 UUID_STR_LENGTH = 37; // actually wrong, should be 36 and use size below +const S32 UUID_STR_SIZE = 37; +const S32 UUID_BASE85_LENGTH = 21; // including the trailing NULL. + +struct uuid_time_t { + U32 high; + U32 low; + }; + +class LL_COMMON_API LLUUID +{ +public: + // + // CREATORS + // + LLUUID(); + explicit LLUUID(const char *in_string); // Convert from string. + explicit LLUUID(const std::string& in_string); // Convert from string. + ~LLUUID() = default; + + // + // MANIPULATORS + // + void generate(); // Generate a new UUID + void generate(const std::string& stream); //Generate a new UUID based on hash of input stream + + static LLUUID generateNewID(std::string stream = ""); //static version of above for use in initializer expressions such as constructor params, etc. + + bool set(const char *in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings + bool set(const std::string& in_string, bool emit = true); // Convert from string, if emit is false, do not emit warnings + void setNull(); // Faster than setting to LLUUID::null. + + S32 cmpTime(uuid_time_t *t1, uuid_time_t *t2); + static void getSystemTime(uuid_time_t *timestamp); + void getCurrentTime(uuid_time_t *timestamp); + + // + // ACCESSORS + // + bool isNull() const; // Faster than comparing to LLUUID::null. + bool notNull() const; // Faster than comparing to LLUUID::null. + // JC: This is dangerous. It allows UUIDs to be cast automatically + // to integers, among other things. Use isNull() or notNull(). + // operator bool() const; + + // JC: These must return real bool's (not BOOLs) or else use of the STL + // will generate bool-to-int performance warnings. + bool operator==(const LLUUID &rhs) const; + bool operator!=(const LLUUID &rhs) const; + bool operator<(const LLUUID &rhs) const; + bool operator>(const LLUUID &rhs) const; + + // xor functions. Useful since any two random uuids xored together + // will yield a determinate third random unique id that can be + // used as a key in a single uuid that represents 2. + const LLUUID& operator^=(const LLUUID& rhs); + LLUUID operator^(const LLUUID& rhs) const; + + // similar to functions above, but not invertible + // yields a third random UUID that can be reproduced from the two inputs + // but which, given the result and one of the inputs can't be used to + // deduce the other input + LLUUID combine(const LLUUID& other) const; + void combine(const LLUUID& other, LLUUID& result) const; + + friend LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLUUID &uuid); + friend LL_COMMON_API std::istream& operator>>(std::istream& s, LLUUID &uuid); + + void toString(char *out) const; // Does not allocate memory, needs 36 characters (including \0) + void toString(std::string& out) const; + void toCompressedString(char *out) const; // Does not allocate memory, needs 17 characters (including \0) + void toCompressedString(std::string& out) const; + + std::string asString() const; + std::string getString() const; + + U16 getCRC16() const; + U32 getCRC32() const; + + // Returns a 64 bits digest of the UUID, by XORing its two 64 bits long + // words. HB + inline U64 getDigest64() const + { + U64* tmp = (U64*)mData; + return tmp[0] ^ tmp[1]; + } + + static bool validate(const std::string& in_string); // Validate that the UUID string is legal. + + static const LLUUID null; + static LLMutex * mMutex; + + static U32 getRandomSeed(); + static S32 getNodeID(unsigned char * node_id); + + static bool parseUUID(const std::string& buf, LLUUID* value); + + U8 mData[UUID_BYTES]; +}; +static_assert(std::is_trivially_copyable<LLUUID>::value, "LLUUID must be trivial copy"); +static_assert(std::is_trivially_move_assignable<LLUUID>::value, "LLUUID must be trivial move"); +static_assert(std::is_standard_layout<LLUUID>::value, "LLUUID must be a standard layout type"); + +typedef std::vector<LLUUID> uuid_vec_t; +typedef std::set<LLUUID> uuid_set_t; + +// Helper structure for ordering lluuids in stl containers. eg: +// std::map<LLUUID, LLWidget*, lluuid_less> widget_map; +// +// (isn't this the default behavior anyway? I think we could +// everywhere replace these with uuid_set_t, but someone should +// verify.) +struct lluuid_less +{ + bool operator()(const LLUUID& lhs, const LLUUID& rhs) const + { + return lhs < rhs; + } +}; + +typedef std::set<LLUUID, lluuid_less> uuid_list_t; +/* + * Sub-classes for keeping transaction IDs and asset IDs + * straight. + */ +typedef LLUUID LLAssetID; + +class LL_COMMON_API LLTransactionID : public LLUUID +{ +public: + LLTransactionID() : LLUUID() { } + + static const LLTransactionID tnull; + LLAssetID makeAssetID(const LLUUID& session) const; +}; + +// std::hash implementation for LLUUID +namespace std +{ + template<> struct hash<LLUUID> + { + inline size_t operator()(const LLUUID& id) const noexcept + { + return (size_t)id.getDigest64(); + } + }; +} + +// For use with boost containers. +inline size_t hash_value(const LLUUID& id) noexcept +{ + return (size_t)id.getDigest64(); +} + +#endif // LL_LLUUID_H diff --git a/indra/llcommon/llworkerthread.cpp b/indra/llcommon/llworkerthread.cpp index 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<LLWorkerClass*> delete_list;
- std::vector<LLWorkerClass*> abort_list;
- mDeleteMutex->lock();
- for (delete_list_t::iterator iter = mDeleteList.begin();
- iter != mDeleteList.end(); )
- {
- delete_list_t::iterator curiter = iter++;
- LLWorkerClass* worker = *curiter;
- if (worker->deleteOK())
- {
- if (worker->getFlags(LLWorkerClass::WCF_WORK_FINISHED))
- {
- worker->setFlags(LLWorkerClass::WCF_DELETE_REQUESTED);
- delete_list.push_back(worker);
- mDeleteList.erase(curiter);
- }
- else if (!worker->getFlags(LLWorkerClass::WCF_ABORT_REQUESTED))
- {
- abort_list.push_back(worker);
- }
- }
- }
- mDeleteMutex->unlock();
- // abort and delete after releasing mutex
- for (LLWorkerClass* worker : abort_list)
- {
- worker->abortWork(false);
- }
- for (LLWorkerClass* worker : delete_list)
- {
- if (worker->mRequestHandle)
- {
- // Finished but not completed
- completeRequest(worker->mRequestHandle);
- worker->mRequestHandle = LLWorkerThread::nullHandle();
- worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK);
- }
- delete worker;
- }
- // delete and aborted entries mean there's still work to do
- res += delete_list.size() + abort_list.size();
- return res;
-}
-
-//----------------------------------------------------------------------------
-
-LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param)
-{
- handle_t handle = generateHandle();
-
- WorkRequest* req = new WorkRequest(handle, workerclass, param);
-
- bool res = addRequest(req);
- if (!res)
- {
- LL_ERRS() << "add called after LLWorkerThread::cleanupClass()" << LL_ENDL;
- req->deleteRequest();
- handle = nullHandle();
- }
-
- return handle;
-}
-
-void LLWorkerThread::deleteWorker(LLWorkerClass* workerclass)
-{
- mDeleteMutex->lock();
- mDeleteList.push_back(workerclass);
- mDeleteMutex->unlock();
-}
-
-//============================================================================
-// Runs on its OWN thread
-
-LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param) :
- LLQueuedThread::QueuedRequest(handle),
- mWorkerClass(workerclass),
- mParam(param)
-{
-}
-
-LLWorkerThread::WorkRequest::~WorkRequest()
-{
-}
-
-// virtual (required for access by LLWorkerThread)
-void LLWorkerThread::WorkRequest::deleteRequest()
-{
- LLQueuedThread::QueuedRequest::deleteRequest();
-}
-
-// virtual
-bool LLWorkerThread::WorkRequest::processRequest()
-{
- LL_PROFILE_ZONE_SCOPED;
- LLWorkerClass* workerclass = getWorkerClass();
- workerclass->setWorking(true);
- bool complete = workerclass->doWork(getParam());
- workerclass->setWorking(false);
- return complete;
-}
-
-// virtual
-void LLWorkerThread::WorkRequest::finishRequest(bool completed)
-{
- LL_PROFILE_ZONE_SCOPED;
- LLWorkerClass* workerclass = getWorkerClass();
- workerclass->finishWork(getParam(), completed);
- U32 flags = LLWorkerClass::WCF_WORK_FINISHED | (completed ? 0 : LLWorkerClass::WCF_WORK_ABORTED);
- workerclass->setFlags(flags);
-}
-
-//============================================================================
-// LLWorkerClass:: operates in main thread
-
-LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& name)
- : mWorkerThread(workerthread),
- mWorkerClassName(name),
- mRequestHandle(LLWorkerThread::nullHandle()),
- mMutex(),
- mWorkFlags(0)
-{
- if (!mWorkerThread)
- {
- LL_ERRS() << "LLWorkerClass() called with NULL workerthread: " << name << LL_ENDL;
- }
-}
-
-LLWorkerClass::~LLWorkerClass()
-{
- llassert_always(!(mWorkFlags & WCF_WORKING));
- llassert_always(mWorkFlags & WCF_DELETE_REQUESTED);
- llassert_always(!mMutex.isLocked());
- if (mRequestHandle != LLWorkerThread::nullHandle())
- {
- LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle);
- if (!workreq)
- {
- LL_ERRS() << "LLWorkerClass destroyed with stale work handle" << LL_ENDL;
- }
- if (workreq->getStatus() != LLWorkerThread::STATUS_ABORTED &&
- workreq->getStatus() != LLWorkerThread::STATUS_COMPLETE)
- {
- LL_ERRS() << "LLWorkerClass destroyed with active worker! Worker Status: " << workreq->getStatus() << LL_ENDL;
- }
- }
-}
-
-void LLWorkerClass::setWorkerThread(LLWorkerThread* workerthread)
-{
- mMutex.lock();
- if (mRequestHandle != LLWorkerThread::nullHandle())
- {
- LL_ERRS() << "LLWorkerClass attempt to change WorkerThread with active worker!" << LL_ENDL;
- }
- mWorkerThread = workerthread;
- mMutex.unlock();
-}
-
-//----------------------------------------------------------------------------
-
-//virtual
-void LLWorkerClass::finishWork(S32 param, bool success)
-{
-}
-
-//virtual
-bool LLWorkerClass::deleteOK()
-{
- return true; // default always OK
-}
-
-//----------------------------------------------------------------------------
-
-// Called from worker thread
-void LLWorkerClass::setWorking(bool working)
-{
- mMutex.lock();
- if (working)
- {
- llassert_always(!(mWorkFlags & WCF_WORKING));
- setFlags(WCF_WORKING);
- }
- else
- {
- llassert_always((mWorkFlags & WCF_WORKING));
- clearFlags(WCF_WORKING);
- }
- mMutex.unlock();
-}
-
-//----------------------------------------------------------------------------
-
-bool LLWorkerClass::yield()
-{
- LLThread::yield();
- mWorkerThread->checkPause();
- bool res;
- mMutex.lock();
- res = (getFlags() & WCF_ABORT_REQUESTED) != 0;
- mMutex.unlock();
- return res;
-}
-
-//----------------------------------------------------------------------------
-
-// calls startWork, adds doWork() to queue
-void LLWorkerClass::addWork(S32 param)
-{
- mMutex.lock();
- llassert_always(!(mWorkFlags & (WCF_WORKING|WCF_HAVE_WORK)));
- if (mRequestHandle != LLWorkerThread::nullHandle())
- {
- LL_ERRS() << "LLWorkerClass attempt to add work with active worker!" << LL_ENDL;
- }
-#if _DEBUG
-// LL_INFOS() << "addWork: " << mWorkerClassName << " Param: " << param << LL_ENDL;
-#endif
- startWork(param);
- clearFlags(WCF_WORK_FINISHED|WCF_WORK_ABORTED);
- setFlags(WCF_HAVE_WORK);
- mRequestHandle = mWorkerThread->addWorkRequest(this, param);
- mMutex.unlock();
-}
-
-void LLWorkerClass::abortWork(bool autocomplete)
-{
- mMutex.lock();
-#if _DEBUG
-// LLWorkerThread::WorkRequest* workreq = mWorkerThread->getRequest(mRequestHandle);
-// if (workreq)
-// LL_INFOS() << "abortWork: " << mWorkerClassName << " Param: " << workreq->getParam() << LL_ENDL;
-#endif
- if (mRequestHandle != LLWorkerThread::nullHandle())
- {
- mWorkerThread->abortRequest(mRequestHandle, autocomplete);
- setFlags(WCF_ABORT_REQUESTED);
- }
- mMutex.unlock();
-}
-
-// if doWork is complete or aborted, call endWork() and return true
-bool LLWorkerClass::checkWork(bool aborting)
-{
- LLMutexLock lock(&mMutex);
- bool complete = false, abort = false;
- if (mRequestHandle != LLWorkerThread::nullHandle())
- {
- LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle);
- if(!workreq)
- {
- if(mWorkerThread->isQuitting() || mWorkerThread->isStopped()) //the mWorkerThread is not running
- {
- mRequestHandle = LLWorkerThread::nullHandle();
- clearFlags(WCF_HAVE_WORK);
- }
- else
- {
- llassert_always(workreq);
- }
- return true ;
- }
-
- LLQueuedThread::status_t status = workreq->getStatus();
- if (status == LLWorkerThread::STATUS_ABORTED)
- {
- complete = true;
- abort = true;
- }
- else if (status == LLWorkerThread::STATUS_COMPLETE)
- {
- complete = true;
- }
- else
- {
- llassert_always(!aborting || (workreq->getFlags() & LLQueuedThread::FLAG_ABORT));
- }
- if (complete)
- {
- llassert_always(!(getFlags(WCF_WORKING)));
- endWork(workreq->getParam(), abort);
- mWorkerThread->completeRequest(mRequestHandle);
- mRequestHandle = LLWorkerThread::nullHandle();
- clearFlags(WCF_HAVE_WORK);
- }
- }
- else
- {
- complete = true;
- }
- return complete;
-}
-
-void LLWorkerClass::scheduleDelete()
-{
- bool do_delete = false;
- mMutex.lock();
- if (!(getFlags(WCF_DELETE_REQUESTED)))
- {
- setFlags(WCF_DELETE_REQUESTED);
- do_delete = true;
- }
- mMutex.unlock();
- if (do_delete)
- {
- mWorkerThread->deleteWorker(this);
- }
-}
-
-//============================================================================
-
+/** + * @file llworkerthread.cpp + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llworkerthread.h" +#include "llstl.h" + +#if USE_FRAME_CALLBACK_MANAGER +#include "llframecallbackmanager.h" +#endif + +//============================================================================ +// Run on MAIN thread + +LLWorkerThread::LLWorkerThread(const std::string& name, bool threaded, bool should_pause) : + LLQueuedThread(name, threaded, should_pause) +{ + mDeleteMutex = new LLMutex(); + + if(!mLocalAPRFilePoolp) + { + mLocalAPRFilePoolp = new LLVolatileAPRPool() ; + } +} + +LLWorkerThread::~LLWorkerThread() +{ + // Delete any workers in the delete queue (should be safe - had better be!) + if (!mDeleteList.empty()) + { + LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size() + << " entries in delete list." << LL_ENDL; + } + + delete mDeleteMutex; + + // ~LLQueuedThread() will be called here +} + +//called only in destructor. +void LLWorkerThread::clearDeleteList() +{ + // Delete any workers in the delete queue (should be safe - had better be!) + if (!mDeleteList.empty()) + { + LL_WARNS() << "Worker Thread: " << mName << " destroyed with " << mDeleteList.size() + << " entries in delete list." << LL_ENDL; + + mDeleteMutex->lock(); + for (LLWorkerClass* worker : mDeleteList) + { + worker->mRequestHandle = LLWorkerThread::nullHandle(); + worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK); + worker->clearFlags(LLWorkerClass::WCF_WORKING); + delete worker; + } + mDeleteList.clear() ; + mDeleteMutex->unlock() ; + } +} + +// virtual +size_t LLWorkerThread::update(F32 max_time_ms) +{ + auto res = LLQueuedThread::update(max_time_ms); + // Delete scheduled workers + std::vector<LLWorkerClass*> delete_list; + std::vector<LLWorkerClass*> abort_list; + mDeleteMutex->lock(); + for (delete_list_t::iterator iter = mDeleteList.begin(); + iter != mDeleteList.end(); ) + { + delete_list_t::iterator curiter = iter++; + LLWorkerClass* worker = *curiter; + if (worker->deleteOK()) + { + if (worker->getFlags(LLWorkerClass::WCF_WORK_FINISHED)) + { + worker->setFlags(LLWorkerClass::WCF_DELETE_REQUESTED); + delete_list.push_back(worker); + mDeleteList.erase(curiter); + } + else if (!worker->getFlags(LLWorkerClass::WCF_ABORT_REQUESTED)) + { + abort_list.push_back(worker); + } + } + } + mDeleteMutex->unlock(); + // abort and delete after releasing mutex + for (LLWorkerClass* worker : abort_list) + { + worker->abortWork(false); + } + for (LLWorkerClass* worker : delete_list) + { + if (worker->mRequestHandle) + { + // Finished but not completed + completeRequest(worker->mRequestHandle); + worker->mRequestHandle = LLWorkerThread::nullHandle(); + worker->clearFlags(LLWorkerClass::WCF_HAVE_WORK); + } + delete worker; + } + // delete and aborted entries mean there's still work to do + res += delete_list.size() + abort_list.size(); + return res; +} + +//---------------------------------------------------------------------------- + +LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param) +{ + handle_t handle = generateHandle(); + + WorkRequest* req = new WorkRequest(handle, workerclass, param); + + bool res = addRequest(req); + if (!res) + { + LL_ERRS() << "add called after LLWorkerThread::cleanupClass()" << LL_ENDL; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +void LLWorkerThread::deleteWorker(LLWorkerClass* workerclass) +{ + mDeleteMutex->lock(); + mDeleteList.push_back(workerclass); + mDeleteMutex->unlock(); +} + +//============================================================================ +// Runs on its OWN thread + +LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param) : + LLQueuedThread::QueuedRequest(handle), + mWorkerClass(workerclass), + mParam(param) +{ +} + +LLWorkerThread::WorkRequest::~WorkRequest() +{ +} + +// virtual (required for access by LLWorkerThread) +void LLWorkerThread::WorkRequest::deleteRequest() +{ + LLQueuedThread::QueuedRequest::deleteRequest(); +} + +// virtual +bool LLWorkerThread::WorkRequest::processRequest() +{ + LL_PROFILE_ZONE_SCOPED; + LLWorkerClass* workerclass = getWorkerClass(); + workerclass->setWorking(true); + bool complete = workerclass->doWork(getParam()); + workerclass->setWorking(false); + return complete; +} + +// virtual +void LLWorkerThread::WorkRequest::finishRequest(bool completed) +{ + LL_PROFILE_ZONE_SCOPED; + LLWorkerClass* workerclass = getWorkerClass(); + workerclass->finishWork(getParam(), completed); + U32 flags = LLWorkerClass::WCF_WORK_FINISHED | (completed ? 0 : LLWorkerClass::WCF_WORK_ABORTED); + workerclass->setFlags(flags); +} + +//============================================================================ +// LLWorkerClass:: operates in main thread + +LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& name) + : mWorkerThread(workerthread), + mWorkerClassName(name), + mRequestHandle(LLWorkerThread::nullHandle()), + mMutex(), + mWorkFlags(0) +{ + if (!mWorkerThread) + { + LL_ERRS() << "LLWorkerClass() called with NULL workerthread: " << name << LL_ENDL; + } +} + +LLWorkerClass::~LLWorkerClass() +{ + llassert_always(!(mWorkFlags & WCF_WORKING)); + llassert_always(mWorkFlags & WCF_DELETE_REQUESTED); + llassert_always(!mMutex.isLocked()); + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle); + if (!workreq) + { + LL_ERRS() << "LLWorkerClass destroyed with stale work handle" << LL_ENDL; + } + if (workreq->getStatus() != LLWorkerThread::STATUS_ABORTED && + workreq->getStatus() != LLWorkerThread::STATUS_COMPLETE) + { + LL_ERRS() << "LLWorkerClass destroyed with active worker! Worker Status: " << workreq->getStatus() << LL_ENDL; + } + } +} + +void LLWorkerClass::setWorkerThread(LLWorkerThread* workerthread) +{ + mMutex.lock(); + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + LL_ERRS() << "LLWorkerClass attempt to change WorkerThread with active worker!" << LL_ENDL; + } + mWorkerThread = workerthread; + mMutex.unlock(); +} + +//---------------------------------------------------------------------------- + +//virtual +void LLWorkerClass::finishWork(S32 param, bool success) +{ +} + +//virtual +bool LLWorkerClass::deleteOK() +{ + return true; // default always OK +} + +//---------------------------------------------------------------------------- + +// Called from worker thread +void LLWorkerClass::setWorking(bool working) +{ + mMutex.lock(); + if (working) + { + llassert_always(!(mWorkFlags & WCF_WORKING)); + setFlags(WCF_WORKING); + } + else + { + llassert_always((mWorkFlags & WCF_WORKING)); + clearFlags(WCF_WORKING); + } + mMutex.unlock(); +} + +//---------------------------------------------------------------------------- + +bool LLWorkerClass::yield() +{ + LLThread::yield(); + mWorkerThread->checkPause(); + bool res; + mMutex.lock(); + res = (getFlags() & WCF_ABORT_REQUESTED) != 0; + mMutex.unlock(); + return res; +} + +//---------------------------------------------------------------------------- + +// calls startWork, adds doWork() to queue +void LLWorkerClass::addWork(S32 param) +{ + mMutex.lock(); + llassert_always(!(mWorkFlags & (WCF_WORKING|WCF_HAVE_WORK))); + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + LL_ERRS() << "LLWorkerClass attempt to add work with active worker!" << LL_ENDL; + } +#if _DEBUG +// LL_INFOS() << "addWork: " << mWorkerClassName << " Param: " << param << LL_ENDL; +#endif + startWork(param); + clearFlags(WCF_WORK_FINISHED|WCF_WORK_ABORTED); + setFlags(WCF_HAVE_WORK); + mRequestHandle = mWorkerThread->addWorkRequest(this, param); + mMutex.unlock(); +} + +void LLWorkerClass::abortWork(bool autocomplete) +{ + mMutex.lock(); +#if _DEBUG +// LLWorkerThread::WorkRequest* workreq = mWorkerThread->getRequest(mRequestHandle); +// if (workreq) +// LL_INFOS() << "abortWork: " << mWorkerClassName << " Param: " << workreq->getParam() << LL_ENDL; +#endif + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + mWorkerThread->abortRequest(mRequestHandle, autocomplete); + setFlags(WCF_ABORT_REQUESTED); + } + mMutex.unlock(); +} + +// if doWork is complete or aborted, call endWork() and return true +bool LLWorkerClass::checkWork(bool aborting) +{ + LLMutexLock lock(&mMutex); + bool complete = false, abort = false; + if (mRequestHandle != LLWorkerThread::nullHandle()) + { + LLWorkerThread::WorkRequest* workreq = (LLWorkerThread::WorkRequest*)mWorkerThread->getRequest(mRequestHandle); + if(!workreq) + { + if(mWorkerThread->isQuitting() || mWorkerThread->isStopped()) //the mWorkerThread is not running + { + mRequestHandle = LLWorkerThread::nullHandle(); + clearFlags(WCF_HAVE_WORK); + } + else + { + llassert_always(workreq); + } + return true ; + } + + LLQueuedThread::status_t status = workreq->getStatus(); + if (status == LLWorkerThread::STATUS_ABORTED) + { + complete = true; + abort = true; + } + else if (status == LLWorkerThread::STATUS_COMPLETE) + { + complete = true; + } + else + { + llassert_always(!aborting || (workreq->getFlags() & LLQueuedThread::FLAG_ABORT)); + } + if (complete) + { + llassert_always(!(getFlags(WCF_WORKING))); + endWork(workreq->getParam(), abort); + mWorkerThread->completeRequest(mRequestHandle); + mRequestHandle = LLWorkerThread::nullHandle(); + clearFlags(WCF_HAVE_WORK); + } + } + else + { + complete = true; + } + return complete; +} + +void LLWorkerClass::scheduleDelete() +{ + bool do_delete = false; + mMutex.lock(); + if (!(getFlags(WCF_DELETE_REQUESTED))) + { + setFlags(WCF_DELETE_REQUESTED); + do_delete = true; + } + mMutex.unlock(); + if (do_delete) + { + mWorkerThread->deleteWorker(this); + } +} + +//============================================================================ + diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h index 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 <list>
-#include <map>
-#include <queue>
-#include <set>
-#include <string>
-
-#include "llqueuedthread.h"
-#include "llatomic.h"
-#include "llmutex.h"
-
-#define USE_FRAME_CALLBACK_MANAGER 0
-
-//============================================================================
-
-class LLWorkerClass;
-
-//============================================================================
-// Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small
-// It is assumed that LLWorkerThreads are rarely created/destroyed.
-
-class LL_COMMON_API LLWorkerThread : public LLQueuedThread
-{
- friend class LLWorkerClass;
-public:
- class WorkRequest : public LLQueuedThread::QueuedRequest
- {
- protected:
- virtual ~WorkRequest(); // use deleteRequest()
-
- public:
- WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param);
-
- S32 getParam()
- {
- return mParam;
- }
- LLWorkerClass* getWorkerClass()
- {
- return mWorkerClass;
- }
-
- /*virtual*/ bool processRequest();
- /*virtual*/ void finishRequest(bool completed);
- /*virtual*/ void deleteRequest();
-
- private:
- LLWorkerClass* mWorkerClass;
- S32 mParam;
- };
-
-protected:
- void clearDeleteList() ;
-
-private:
- typedef std::list<LLWorkerClass*> delete_list_t;
- delete_list_t mDeleteList;
- LLMutex* mDeleteMutex;
-
-public:
- LLWorkerThread(const std::string& name, bool threaded = true, bool should_pause = false);
- ~LLWorkerThread();
-
- /*virtual*/ size_t update(F32 max_time_ms);
-
- handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param);
-
- S32 getNumDeletes() { return (S32)mDeleteList.size(); } // debug
-
-private:
- void deleteWorker(LLWorkerClass* workerclass); // schedule for deletion
-
-};
-
-//============================================================================
-
-// This is a base class which any class with worker functions should derive from.
-// Example Usage:
-// LLMyWorkerClass* foo = new LLMyWorkerClass();
-// foo->fetchData(); // calls addWork()
-// while(1) // main loop
-// {
-// if (foo->hasData()) // calls checkWork()
-// foo->processData();
-// }
-//
-// WorkerClasses only have one set of work functions. If they need to do multiple
-// background tasks, use 'param' to switch amnong them.
-// Only one background task can be active at a time (per instance).
-// i.e. don't call addWork() if haveWork() returns true
-
-class LL_COMMON_API LLWorkerClass
-{
- friend class LLWorkerThread;
- friend class LLWorkerThread::WorkRequest;
-
-public:
- typedef LLWorkerThread::handle_t handle_t;
- enum FLAGS
- {
- WCF_HAVE_WORK = 0x01,
- WCF_WORKING = 0x02,
- WCF_WORK_FINISHED = 0x10,
- WCF_WORK_ABORTED = 0x20,
- WCF_DELETE_REQUESTED = 0x40,
- WCF_ABORT_REQUESTED = 0x80
- };
-
-public:
- LLWorkerClass(LLWorkerThread* workerthread, const std::string& name);
- virtual ~LLWorkerClass();
-
- // pure virtual, called from WORKER THREAD, returns true if done
- virtual bool doWork(S32 param)=0; // Called from WorkRequest::processRequest()
- // virtual, called from finishRequest() after completed or aborted
- virtual void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD)
- // virtual, returns true if safe to delete the worker
- virtual bool deleteOK(); // called from update() (WORK THREAD)
-
- // schedlueDelete(): schedules deletion once aborted or completed
- void scheduleDelete();
-
- bool haveWork() { return getFlags(WCF_HAVE_WORK); } // may still be true if aborted
- bool isWorking() { return getFlags(WCF_WORKING); }
- bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); }
-
- const std::string& getName() const { return mWorkerClassName; }
-
-protected:
- // called from WORKER THREAD
- void setWorking(bool working);
-
- // Call from doWork only to avoid eating up cpu time.
- // Returns true if work has been aborted
- // yields the current thread and calls mWorkerThread->checkPause()
- bool yield();
-
- void setWorkerThread(LLWorkerThread* workerthread);
-
- // addWork(): calls startWork, adds doWork() to queue
- void addWork(S32 param);
-
- // abortWork(): requests that work be aborted
- void abortWork(bool autocomplete);
-
- // checkWork(): if doWork is complete or aborted, call endWork() and return true
- bool checkWork(bool aborting = false);
-
-private:
- void setFlags(U32 flags) { mWorkFlags = mWorkFlags | flags; }
- void clearFlags(U32 flags) { mWorkFlags = mWorkFlags & ~flags; }
- U32 getFlags() { return mWorkFlags; }
-public:
- bool getFlags(U32 flags) { return (mWorkFlags & flags) != 0; }
-
-private:
- // pure virtuals
- virtual void startWork(S32 param)=0; // called from addWork() (MAIN THREAD)
- virtual void endWork(S32 param, bool aborted)=0; // called from doWork() (MAIN THREAD)
-
-protected:
- LLWorkerThread* mWorkerThread;
- std::string mWorkerClassName;
- handle_t mRequestHandle;
-
-private:
- LLMutex mMutex;
- LLAtomicU32 mWorkFlags;
-};
-
-//============================================================================
-
-
-#endif // LL_LLWORKERTHREAD_H
+/** + * @file llworkerthread.h + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLWORKERTHREAD_H +#define LL_LLWORKERTHREAD_H + +#include <list> +#include <map> +#include <queue> +#include <set> +#include <string> + +#include "llqueuedthread.h" +#include "llatomic.h" +#include "llmutex.h" + +#define USE_FRAME_CALLBACK_MANAGER 0 + +//============================================================================ + +class LLWorkerClass; + +//============================================================================ +// Note: ~LLWorkerThread is O(N) N=# of worker threads, assumed to be small +// It is assumed that LLWorkerThreads are rarely created/destroyed. + +class LL_COMMON_API LLWorkerThread : public LLQueuedThread +{ + friend class LLWorkerClass; +public: + class WorkRequest : public LLQueuedThread::QueuedRequest + { + protected: + virtual ~WorkRequest(); // use deleteRequest() + + public: + WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param); + + S32 getParam() + { + return mParam; + } + LLWorkerClass* getWorkerClass() + { + return mWorkerClass; + } + + /*virtual*/ bool processRequest(); + /*virtual*/ void finishRequest(bool completed); + /*virtual*/ void deleteRequest(); + + private: + LLWorkerClass* mWorkerClass; + S32 mParam; + }; + +protected: + void clearDeleteList() ; + +private: + typedef std::list<LLWorkerClass*> delete_list_t; + delete_list_t mDeleteList; + LLMutex* mDeleteMutex; + +public: + LLWorkerThread(const std::string& name, bool threaded = true, bool should_pause = false); + ~LLWorkerThread(); + + /*virtual*/ size_t update(F32 max_time_ms); + + handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param); + + S32 getNumDeletes() { return (S32)mDeleteList.size(); } // debug + +private: + void deleteWorker(LLWorkerClass* workerclass); // schedule for deletion + +}; + +//============================================================================ + +// This is a base class which any class with worker functions should derive from. +// Example Usage: +// LLMyWorkerClass* foo = new LLMyWorkerClass(); +// foo->fetchData(); // calls addWork() +// while(1) // main loop +// { +// if (foo->hasData()) // calls checkWork() +// foo->processData(); +// } +// +// WorkerClasses only have one set of work functions. If they need to do multiple +// background tasks, use 'param' to switch amnong them. +// Only one background task can be active at a time (per instance). +// i.e. don't call addWork() if haveWork() returns true + +class LL_COMMON_API LLWorkerClass +{ + friend class LLWorkerThread; + friend class LLWorkerThread::WorkRequest; + +public: + typedef LLWorkerThread::handle_t handle_t; + enum FLAGS + { + WCF_HAVE_WORK = 0x01, + WCF_WORKING = 0x02, + WCF_WORK_FINISHED = 0x10, + WCF_WORK_ABORTED = 0x20, + WCF_DELETE_REQUESTED = 0x40, + WCF_ABORT_REQUESTED = 0x80 + }; + +public: + LLWorkerClass(LLWorkerThread* workerthread, const std::string& name); + virtual ~LLWorkerClass(); + + // pure virtual, called from WORKER THREAD, returns true if done + virtual bool doWork(S32 param)=0; // Called from WorkRequest::processRequest() + // virtual, called from finishRequest() after completed or aborted + virtual void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) + // virtual, returns true if safe to delete the worker + virtual bool deleteOK(); // called from update() (WORK THREAD) + + // schedlueDelete(): schedules deletion once aborted or completed + void scheduleDelete(); + + bool haveWork() { return getFlags(WCF_HAVE_WORK); } // may still be true if aborted + bool isWorking() { return getFlags(WCF_WORKING); } + bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); } + + const std::string& getName() const { return mWorkerClassName; } + +protected: + // called from WORKER THREAD + void setWorking(bool working); + + // Call from doWork only to avoid eating up cpu time. + // Returns true if work has been aborted + // yields the current thread and calls mWorkerThread->checkPause() + bool yield(); + + void setWorkerThread(LLWorkerThread* workerthread); + + // addWork(): calls startWork, adds doWork() to queue + void addWork(S32 param); + + // abortWork(): requests that work be aborted + void abortWork(bool autocomplete); + + // checkWork(): if doWork is complete or aborted, call endWork() and return true + bool checkWork(bool aborting = false); + +private: + void setFlags(U32 flags) { mWorkFlags = mWorkFlags | flags; } + void clearFlags(U32 flags) { mWorkFlags = mWorkFlags & ~flags; } + U32 getFlags() { return mWorkFlags; } +public: + bool getFlags(U32 flags) { return (mWorkFlags & flags) != 0; } + +private: + // pure virtuals + virtual void startWork(S32 param)=0; // called from addWork() (MAIN THREAD) + virtual void endWork(S32 param, bool aborted)=0; // called from doWork() (MAIN THREAD) + +protected: + LLWorkerThread* mWorkerThread; + std::string mWorkerClassName; + handle_t mRequestHandle; + +private: + LLMutex mMutex; + LLAtomicU32 mWorkFlags; +}; + +//============================================================================ + + +#endif // LL_LLWORKERTHREAD_H diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 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 <boost/assign/list_of.hpp>
-#include "../llstring.h"
-#include "StringVec.h" // must come BEFORE lltut.h
-#include "../test/lltut.h"
-
-using boost::assign::list_of;
-
-namespace tut
-{
- struct string_index
- {
- };
- typedef test_group<string_index> string_index_t;
- typedef string_index_t::object string_index_object_t;
- tut::string_index_t tut_string_index("LLString");
-
- template<> template<>
- void string_index_object_t::test<1>()
- {
- std::string llstr1;
- ensure("Empty std::string", (llstr1.size() == 0) && llstr1.empty());
-
- std::string llstr2("Hello");
- ensure("std::string = Hello", (!strcmp(llstr2.c_str(), "Hello")) && (llstr2.size() == 5) && !llstr2.empty());
-
- std::string llstr3(llstr2);
- ensure("std::string = std::string(std::string)", (!strcmp(llstr3.c_str(), "Hello")) && (llstr3.size() == 5) && !llstr3.empty());
-
- std::string str("Hello World");
- std::string llstr4(str, 6);
- ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (!strcmp(llstr4.c_str(), "World")) && (llstr4.size() == 5) && !llstr4.empty());
-
- std::string llstr5(str, str.size());
- ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (llstr5.size() == 0) && llstr5.empty());
-
- std::string llstr6(5, 'A');
- ensure("std::string = std::string(count, c)", (!strcmp(llstr6.c_str(), "AAAAA")) && (llstr6.size() == 5) && !llstr6.empty());
-
- std::string llstr7("Hello World", 5);
- ensure("std::string(s, n)", (!strcmp(llstr7.c_str(), "Hello")) && (llstr7.size() == 5) && !llstr7.empty());
-
- std::string llstr8("Hello World", 6, 5);
- ensure("std::string(s, n, count)", (!strcmp(llstr8.c_str(), "World")) && (llstr8.size() == 5) && !llstr8.empty());
-
- std::string llstr9("Hello World", sizeof("Hello World")-1, 5); // go past end
- ensure("std::string(s, n, count) goes past end", (llstr9.size() == 0) && llstr9.empty());
- }
-
- template<> template<>
- void string_index_object_t::test<3>()
- {
- std::string str("Len=5");
- ensure("isValidIndex failed", LLStringUtil::isValidIndex(str, 0) == true &&
- LLStringUtil::isValidIndex(str, 5) == true &&
- LLStringUtil::isValidIndex(str, 6) == false);
-
- std::string str1;
- ensure("isValidIndex failed fo rempty string", LLStringUtil::isValidIndex(str1, 0) == false);
- }
-
- template<> template<>
- void string_index_object_t::test<4>()
- {
- std::string str_val(" Testing the extra whitespaces ");
- LLStringUtil::trimHead(str_val);
- ensure_equals("1: trimHead failed", str_val, "Testing the extra whitespaces ");
-
- std::string str_val1("\n\t\r\n Testing the extra whitespaces ");
- LLStringUtil::trimHead(str_val1);
- ensure_equals("2: trimHead failed", str_val1, "Testing the extra whitespaces ");
- }
-
- template<> template<>
- void string_index_object_t::test<5>()
- {
- std::string str_val(" Testing the extra whitespaces ");
- LLStringUtil::trimTail(str_val);
- ensure_equals("1: trimTail failed", str_val, " Testing the extra whitespaces");
-
- std::string str_val1("\n Testing the extra whitespaces \n\t\r\n ");
- LLStringUtil::trimTail(str_val1);
- ensure_equals("2: trimTail failed", str_val1, "\n Testing the extra whitespaces");
- }
-
-
- template<> template<>
- void string_index_object_t::test<6>()
- {
- std::string str_val(" \t \r Testing the extra \r\n whitespaces \n \t ");
- LLStringUtil::trim(str_val);
- ensure_equals("1: trim failed", str_val, "Testing the extra \r\n whitespaces");
- }
-
- template<> template<>
- void string_index_object_t::test<7>()
- {
- std::string str("Second LindenLabs");
- LLStringUtil::truncate(str, 6);
- ensure_equals("1: truncate", str, "Second");
-
- // further truncate more than the length
- LLStringUtil::truncate(str, 0);
- ensure_equals("2: truncate", str, "");
- }
-
- template<> template<>
- void string_index_object_t::test<8>()
- {
- std::string str_val("SecondLife Source");
- LLStringUtil::toUpper(str_val);
- ensure_equals("toUpper failed", str_val, "SECONDLIFE SOURCE");
- }
-
- template<> template<>
- void string_index_object_t::test<9>()
- {
- std::string str_val("SecondLife Source");
- LLStringUtil::toLower(str_val);
- ensure_equals("toLower failed", str_val, "secondlife source");
- }
-
- template<> template<>
- void string_index_object_t::test<10>()
- {
- std::string str_val("Second");
- ensure("1. isHead failed", LLStringUtil::isHead(str_val, "SecondLife Source") == true);
- ensure("2. isHead failed", LLStringUtil::isHead(str_val, " SecondLife Source") == false);
- std::string str_val2("");
- ensure("3. isHead failed", LLStringUtil::isHead(str_val2, "") == false);
- }
-
- template<> template<>
- void string_index_object_t::test<11>()
- {
- std::string str_val("Hello.\n\n Lindenlabs. \n This is \na simple test.\n");
- std::string orig_str_val(str_val);
- LLStringUtil::addCRLF(str_val);
- ensure_equals("addCRLF failed", str_val, "Hello.\r\n\r\n Lindenlabs. \r\n This is \r\na simple test.\r\n");
- LLStringUtil::removeCRLF(str_val);
- ensure_equals("removeCRLF failed", str_val, orig_str_val);
- }
-
- template<> template<>
- void string_index_object_t::test<12>()
- {
- std::string str_val("Hello.\n\n\t \t Lindenlabs. \t\t");
- std::string orig_str_val(str_val);
- LLStringUtil::replaceTabsWithSpaces(str_val, 1);
- ensure_equals("replaceTabsWithSpaces failed", str_val, "Hello.\n\n Lindenlabs. ");
- LLStringUtil::replaceTabsWithSpaces(orig_str_val, 0);
- ensure_equals("replaceTabsWithSpaces failed for 0", orig_str_val, "Hello.\n\n Lindenlabs. ");
-
- str_val = "\t\t\t\t";
- LLStringUtil::replaceTabsWithSpaces(str_val, 0);
- ensure_equals("replaceTabsWithSpaces failed for all tabs", str_val, "");
- }
-
- template<> template<>
- void string_index_object_t::test<13>()
- {
- std::string str_val("Hello.\n\n\t\t\r\nLindenlabsX.");
- LLStringUtil::replaceNonstandardASCII(str_val, 'X');
- ensure_equals("replaceNonstandardASCII failed", str_val, "Hello.\n\nXXX\nLindenlabsX.");
- }
-
- template<> template<>
- void string_index_object_t::test<14>()
- {
- std::string str_val("Hello.\n\t\r\nABCDEFGHIABABAB");
- LLStringUtil::replaceChar(str_val, 'A', 'X');
- ensure_equals("1: replaceChar failed", str_val, "Hello.\n\t\r\nXBCDEFGHIXBXBXB");
- std::string str_val1("Hello.\n\t\r\nABCDEFGHIABABAB");
- }
-
- template<> template<>
- void string_index_object_t::test<15>()
- {
- std::string str_val("Hello.\n\r\t");
- ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == true);
-
- str_val = "ABC ";
- ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == false);
- }
-
- template<> template<>
- void string_index_object_t::test<16>()
- {
- std::string str_val("Hello.\n\r\t Again!");
- LLStringUtil::stripNonprintable(str_val);
- ensure_equals("stripNonprintable failed", str_val, "Hello. Again!");
-
- str_val = "\r\n\t\t";
- LLStringUtil::stripNonprintable(str_val);
- ensure_equals("stripNonprintable resulting in empty string failed", str_val, "");
-
- str_val = "";
- LLStringUtil::stripNonprintable(str_val);
- ensure_equals("stripNonprintable of empty string resulting in empty string failed", str_val, "");
- }
-
- template<> template<>
- void string_index_object_t::test<17>()
- {
- bool value;
- std::string str_val("1");
- ensure("convertToBOOL 1 failed", LLStringUtil::convertToBOOL(str_val, value) && value);
- str_val = "T";
- ensure("convertToBOOL T failed", LLStringUtil::convertToBOOL(str_val, value) && value);
- str_val = "t";
- ensure("convertToBOOL t failed", LLStringUtil::convertToBOOL(str_val, value) && value);
- str_val = "TRUE";
- ensure("convertToBOOL TRUE failed", LLStringUtil::convertToBOOL(str_val, value) && value);
- str_val = "True";
- ensure("convertToBOOL True failed", LLStringUtil::convertToBOOL(str_val, value) && value);
- str_val = "true";
- ensure("convertToBOOL true failed", LLStringUtil::convertToBOOL(str_val, value) && value);
-
- str_val = "0";
- ensure("convertToBOOL 0 failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
- str_val = "F";
- ensure("convertToBOOL F failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
- str_val = "f";
- ensure("convertToBOOL f failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
- str_val = "FALSE";
- ensure("convertToBOOL FASLE failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
- str_val = "False";
- ensure("convertToBOOL False failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
- str_val = "false";
- ensure("convertToBOOL false failed", LLStringUtil::convertToBOOL(str_val, value) && !value);
-
- str_val = "Tblah";
- ensure("convertToBOOL false failed", !LLStringUtil::convertToBOOL(str_val, value));
- }
-
- template<> template<>
- void string_index_object_t::test<18>()
- {
- U8 value;
- std::string str_val("255");
- ensure("1: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 255);
-
- str_val = "0";
- ensure("2: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 0);
-
- str_val = "-1";
- ensure("3: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value));
-
- str_val = "256"; // bigger than MAX_U8
- ensure("4: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value));
- }
-
- template<> template<>
- void string_index_object_t::test<19>()
- {
- S8 value;
- std::string str_val("127");
- ensure("1: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 127);
-
- str_val = "0";
- ensure("2: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 0);
-
- str_val = "-128";
- ensure("3: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == -128);
-
- str_val = "128"; // bigger than MAX_S8
- ensure("4: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value));
-
- str_val = "-129";
- ensure("5: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value));
- }
-
- template<> template<>
- void string_index_object_t::test<20>()
- {
- S16 value;
- std::string str_val("32767");
- ensure("1: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 32767);
-
- str_val = "0";
- ensure("2: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 0);
-
- str_val = "-32768";
- ensure("3: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == -32768);
-
- str_val = "32768";
- ensure("4: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value));
-
- str_val = "-32769";
- ensure("5: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value));
- }
-
- template<> template<>
- void string_index_object_t::test<21>()
- {
- U16 value;
- std::string str_val("65535"); //0xFFFF
- ensure("1: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 65535);
-
- str_val = "0";
- ensure("2: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 0);
-
- str_val = "-1";
- ensure("3: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value));
-
- str_val = "65536";
- ensure("4: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value));
- }
-
- template<> template<>
- void string_index_object_t::test<22>()
- {
- U32 value;
- std::string str_val("4294967295"); //0xFFFFFFFF
- ensure("1: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 4294967295UL);
-
- str_val = "0";
- ensure("2: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 0);
-
- str_val = "4294967296";
- ensure("3: convertToU32 failed", !LLStringUtil::convertToU32(str_val, value));
- }
-
- template<> template<>
- void string_index_object_t::test<23>()
- {
- S32 value;
- std::string str_val("2147483647"); //0x7FFFFFFF
- ensure("1: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 2147483647);
-
- str_val = "0";
- ensure("2: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 0);
-
- // Avoid "unary minus operator applied to unsigned type" warning on VC++. JC
- S32 min_val = -2147483647 - 1;
- str_val = "-2147483648";
- ensure("3: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == min_val);
-
- str_val = "2147483648";
- ensure("4: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value));
-
- str_val = "-2147483649";
- ensure("5: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value));
- }
-
- template<> template<>
- void string_index_object_t::test<24>()
- {
- F32 value;
- std::string str_val("2147483647"); //0x7FFFFFFF
- ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647);
-
- str_val = "0";
- ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0);
-
- /* Need to find max/min F32 values
- str_val = "-2147483648";
- ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648);
-
- str_val = "2147483648";
- ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value));
-
- str_val = "-2147483649";
- ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value));
- */
- }
-
- template<> template<>
- void string_index_object_t::test<25>()
- {
- F64 value;
- std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF
- ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807LL);
-
- str_val = "0";
- ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F);
-
- /* Need to find max/min F64 values
- str_val = "-2147483648";
- ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648);
-
- str_val = "2147483648";
- ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value));
-
- str_val = "-2147483649";
- ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value));
- */
- }
-
- template<> template<>
- void string_index_object_t::test<26>()
- {
- const char* str1 = NULL;
- const char* str2 = NULL;
-
- ensure("1: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0);
- str2 = "A";
- ensure("2: compareStrings failed", LLStringUtil::compareStrings(str1, str2) > 0);
- ensure("3: compareStrings failed", LLStringUtil::compareStrings(str2, str1) < 0);
-
- str1 = "A is smaller than B";
- str2 = "B is greater than A";
- ensure("4: compareStrings failed", LLStringUtil::compareStrings(str1, str2) < 0);
-
- str2 = "A is smaller than B";
- ensure("5: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0);
- }
-
- template<> template<>
- void string_index_object_t::test<27>()
- {
- const char* str1 = NULL;
- const char* str2 = NULL;
-
- ensure("1: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0);
- str2 = "A";
- ensure("2: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) > 0);
- ensure("3: compareInsensitive failed", LLStringUtil::compareInsensitive(str2, str1) < 0);
-
- str1 = "A is equal to a";
- str2 = "a is EQUAL to A";
- ensure("4: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0);
- }
-
- template<> template<>
- void string_index_object_t::test<28>()
- {
- std::string lhs_str("PROgraM12files");
- std::string rhs_str("PROgram12Files");
- ensure("compareDict 1 failed", LLStringUtil::compareDict(lhs_str, rhs_str) < 0);
- ensure("precedesDict 1 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == true);
-
- lhs_str = "PROgram12Files";
- rhs_str = "PROgram12Files";
- ensure("compareDict 2 failed", LLStringUtil::compareDict(lhs_str, rhs_str) == 0);
- ensure("precedesDict 2 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false);
-
- lhs_str = "PROgram12Files";
- rhs_str = "PROgRAM12FILES";
- ensure("compareDict 3 failed", LLStringUtil::compareDict(lhs_str, rhs_str) > 0);
- ensure("precedesDict 3 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false);
- }
-
- template<> template<>
- void string_index_object_t::test<29>()
- {
- char str1[] = "First String...";
- char str2[100];
-
- LLStringUtil::copy(str2, str1, 100);
- ensure("LLStringUtil::copy with enough dest length failed", strcmp(str2, str1) == 0);
- LLStringUtil::copy(str2, str1, sizeof("First"));
- ensure("LLStringUtil::copy with less dest length failed", strcmp(str2, "First") == 0);
- }
-
- template<> template<>
- void string_index_object_t::test<30>()
- {
- std::string str1 = "This is the sentence...";
- std::string str2 = "This is the ";
- std::string str3 = "first ";
- std::string str4 = "This is the first sentence...";
- std::string str5 = "This is the sentence...first ";
- std::string dest;
-
- dest = str1;
- LLStringUtil::copyInto(dest, str3, str2.length());
- ensure("LLStringUtil::copyInto insert failed", dest == str4);
-
- dest = str1;
- LLStringUtil::copyInto(dest, str3, dest.length());
- ensure("LLStringUtil::copyInto append failed", dest == str5);
- }
-
- template<> template<>
- void string_index_object_t::test<31>()
- {
- std::string stripped;
-
- // Plain US ASCII text, including spaces and punctuation,
- // should not be altered.
- std::string simple_text = "Hello, world!";
- stripped = LLStringFn::strip_invalid_xml(simple_text);
- ensure("Simple text passed unchanged", stripped == simple_text);
-
- // Control characters should be removed
- // except for 0x09, 0x0a, 0x0d
- std::string control_chars;
- for (char c = 0x01; c < 0x20; c++)
- {
- control_chars.push_back(c);
- }
- std::string allowed_control_chars;
- allowed_control_chars.push_back( (char)0x09 );
- allowed_control_chars.push_back( (char)0x0a );
- allowed_control_chars.push_back( (char)0x0d );
-
- stripped = LLStringFn::strip_invalid_xml(control_chars);
- ensure("Only tab, LF, CR control characters allowed",
- stripped == allowed_control_chars);
-
- // UTF-8 should be passed intact, including high byte
- // characters. Try Francais (with C squiggle cedilla)
- std::string french = "Fran";
- french.push_back( (char)0xC3 );
- french.push_back( (char)0xA7 );
- french += "ais";
- stripped = LLStringFn::strip_invalid_xml( french );
- ensure("UTF-8 high byte text is allowed", french == stripped );
- }
-
- template<> template<>
- void string_index_object_t::test<32>()
- {
- // Test LLStringUtil::format() string interpolation
- LLStringUtil::format_map_t fmt_map;
- std::string s;
- int subcount;
-
- fmt_map["[TRICK1]"] = "[A]";
- fmt_map["[A]"] = "a";
- fmt_map["[B]"] = "b";
- fmt_map["[AAA]"] = "aaa";
- fmt_map["[BBB]"] = "bbb";
- fmt_map["[TRICK2]"] = "[A]";
- fmt_map["[EXPLOIT]"] = "!!!!!!!!!!!![EXPLOIT]!!!!!!!!!!!!";
- fmt_map["[KEYLONGER]"] = "short";
- fmt_map["[KEYSHORTER]"] = "Am I not a long string?";
- fmt_map["?"] = "?";
- fmt_map["[DELETE]"] = "";
- fmt_map["[]"] = "[]"; // doesn't do a substitution, but shouldn't crash either
-
- for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter)
- {
- // Test when source string is entirely one key
- std::string s1 = (std::string)iter->first;
- std::string s2 = (std::string)iter->second;
- subcount = LLStringUtil::format(s1, fmt_map);
- ensure_equals("LLStringUtil::format: Raw interpolation result", s1, s2);
- if (s1 == "?" || s1 == "[]") // no interp expected
- {
- ensure_equals("LLStringUtil::format: Raw interpolation result count", 0, subcount);
- }
- else
- {
- ensure_equals("LLStringUtil::format: Raw interpolation result count", 1, subcount);
- }
- }
-
- for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter)
- {
- // Test when source string is one key, duplicated
- std::string s1 = (std::string)iter->first;
- std::string s2 = (std::string)iter->second;
- s = s1 + s1 + s1 + s1;
- subcount = LLStringUtil::format(s, fmt_map);
- ensure_equals("LLStringUtil::format: Rawx4 interpolation result", s, s2 + s2 + s2 + s2);
- if (s1 == "?" || s1 == "[]") // no interp expected
- {
- ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 0, subcount);
- }
- else
- {
- ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 4, subcount);
- }
- }
-
- // Test when source string has no keys
- std::string srcs = "!!!!!!!!!!!!!!!!";
- s = srcs;
- subcount = LLStringUtil::format(s, fmt_map);
- ensure_equals("LLStringUtil::format: No key test result", s, srcs);
- ensure_equals("LLStringUtil::format: No key test result count", 0, subcount);
-
- // Test when source string has no keys and is empty
- std::string srcs3;
- s = srcs3;
- subcount = LLStringUtil::format(s, fmt_map);
- ensure("LLStringUtil::format: No key test3 result", s.empty());
- ensure_equals("LLStringUtil::format: No key test3 result count", 0, subcount);
-
- // Test a substitution where a key is substituted with blankness
- std::string srcs2 = "[DELETE]";
- s = srcs2;
- subcount = LLStringUtil::format(s, fmt_map);
- ensure("LLStringUtil::format: Delete key test2 result", s.empty());
- ensure_equals("LLStringUtil::format: Delete key test2 result count", 1, subcount);
-
- // Test an assorted substitution
- std::string srcs4 = "[TRICK1][A][B][AAA][BBB][TRICK2][KEYLONGER][KEYSHORTER]?[DELETE]";
- s = srcs4;
- subcount = LLStringUtil::format(s, fmt_map);
- ensure_equals("LLStringUtil::format: Assorted Test1 result", s, "[A]abaaabbb[A]shortAm I not a long string??");
- ensure_equals("LLStringUtil::format: Assorted Test1 result count", 9, subcount);
-
- // Test an assorted substitution
- std::string srcs5 = "[DELETE]?[KEYSHORTER][KEYLONGER][TRICK2][BBB][AAA][B][A][TRICK1]";
- s = srcs5;
- subcount = LLStringUtil::format(s, fmt_map);
- ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "?Am I not a long string?short[A]bbbaaaba[A]");
- ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount);
-
- // Test on nested brackets
- std::string srcs6 = "[[TRICK1]][[A]][[B]][[AAA]][[BBB]][[TRICK2]][[KEYLONGER]][[KEYSHORTER]]?[[DELETE]]";
- s = srcs6;
- subcount = LLStringUtil::format(s, fmt_map);
- ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "[[A]][a][b][aaa][bbb][[A]][short][Am I not a long string?]?[]");
- ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount);
-
-
- // Test an assorted substitution
- std::string srcs8 = "foo[DELETE]bar?";
- s = srcs8;
- subcount = LLStringUtil::format(s, fmt_map);
- ensure_equals("LLStringUtil::format: Assorted Test3 result", s, "foobar?");
- ensure_equals("LLStringUtil::format: Assorted Test3 result count", 1, subcount);
- }
-
- template<> template<>
- void string_index_object_t::test<33>()
- {
- // Test LLStringUtil::format() string interpolation
- LLStringUtil::format_map_t blank_fmt_map;
- std::string s;
- int subcount;
-
- // Test substituting out of a blank format_map
- std::string srcs6 = "12345";
- s = srcs6;
- subcount = LLStringUtil::format(s, blank_fmt_map);
- ensure_equals("LLStringUtil::format: Blankfmt Test1 result", s, "12345");
- ensure_equals("LLStringUtil::format: Blankfmt Test1 result count", 0, subcount);
-
- // Test substituting a blank string out of a blank format_map
- std::string srcs7;
- s = srcs7;
- subcount = LLStringUtil::format(s, blank_fmt_map);
- ensure("LLStringUtil::format: Blankfmt Test2 result", s.empty());
- ensure_equals("LLStringUtil::format: Blankfmt Test2 result count", 0, subcount);
- }
-
- template<> template<>
- void string_index_object_t::test<34>()
- {
- // Test that incorrect LLStringUtil::format() use does not explode.
- LLStringUtil::format_map_t nasty_fmt_map;
- std::string s;
- int subcount;
-
- nasty_fmt_map[""] = "never used"; // see, this is nasty.
-
- // Test substituting out of a nasty format_map
- std::string srcs6 = "12345";
- s = srcs6;
- subcount = LLStringUtil::format(s, nasty_fmt_map);
- ensure_equals("LLStringUtil::format: Nastyfmt Test1 result", s, "12345");
- ensure_equals("LLStringUtil::format: Nastyfmt Test1 result count", 0, subcount);
-
- // Test substituting a blank string out of a nasty format_map
- std::string srcs7;
- s = srcs7;
- subcount = LLStringUtil::format(s, nasty_fmt_map);
- ensure("LLStringUtil::format: Nastyfmt Test2 result", s.empty());
- ensure_equals("LLStringUtil::format: Nastyfmt Test2 result count", 0, subcount);
- }
-
- template<> template<>
- void string_index_object_t::test<35>()
- {
- // Make sure startsWith works
- std::string string("anybody in there?");
- std::string substr("anybody");
- ensure("startsWith works.", LLStringUtil::startsWith(string, substr));
- }
-
- template<> template<>
- void string_index_object_t::test<36>()
- {
- // Make sure startsWith correctly fails
- std::string string("anybody in there?");
- std::string substr("there");
- ensure("startsWith fails.", !LLStringUtil::startsWith(string, substr));
- }
-
- template<> template<>
- void string_index_object_t::test<37>()
- {
- // startsWith fails on empty strings
- std::string value("anybody in there?");
- std::string empty;
- ensure("empty string.", !LLStringUtil::startsWith(value, empty));
- ensure("empty substr.", !LLStringUtil::startsWith(empty, value));
- ensure("empty everything.", !LLStringUtil::startsWith(empty, empty));
- }
-
- template<> template<>
- void string_index_object_t::test<38>()
- {
- // Make sure endsWith works correctly
- std::string string("anybody in there?");
- std::string substr("there?");
- ensure("endsWith works.", LLStringUtil::endsWith(string, substr));
- }
-
- template<> template<>
- void string_index_object_t::test<39>()
- {
- // Make sure endsWith correctly fails
- std::string string("anybody in there?");
- std::string substr("anybody");
- ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr));
- substr = "there";
- ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr));
- substr = "ther?";
- ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr));
- }
-
- template<> template<>
- void string_index_object_t::test<40>()
- {
- // endsWith fails on empty strings
- std::string value("anybody in there?");
- std::string empty;
- ensure("empty string.", !LLStringUtil::endsWith(value, empty));
- ensure("empty substr.", !LLStringUtil::endsWith(empty, value));
- ensure("empty everything.", !LLStringUtil::endsWith(empty, empty));
- }
-
- template<> template<>
- void string_index_object_t::test<41>()
- {
- set_test_name("getTokens(\"delims\")");
- ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec());
- ensure_equals("only delims",
- LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec());
- ensure_equals("sequence of delims",
- LLStringUtil::getTokens(",,, one ,,,", ","), list_of("one"));
- // nat considers this a dubious implementation side effect, but I'd
- // hate to change it now...
- ensure_equals("noncontiguous tokens",
- LLStringUtil::getTokens(", ,, , one ,,,", ","), list_of("")("")("one"));
- ensure_equals("space-padded tokens",
- LLStringUtil::getTokens(", one , two ,", ","), list_of("one")("two"));
- ensure_equals("no delims", LLStringUtil::getTokens("one", ","), list_of("one"));
- }
-
- // Shorthand for verifying that getTokens() behaves the same when you
- // don't pass a string of escape characters, when you pass an empty string
- // (different overloads), and when you pass a string of characters that
- // aren't actually present.
- void ensure_getTokens(const std::string& desc,
- const std::string& string,
- const std::string& drop_delims,
- const std::string& keep_delims,
- const std::string& quotes,
- const std::vector<std::string>& expect)
- {
- ensure_equals(desc + " - no esc",
- LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes),
- expect);
- ensure_equals(desc + " - empty esc",
- LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""),
- expect);
- ensure_equals(desc + " - unused esc",
- LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"),
- expect);
- }
-
- void ensure_getTokens(const std::string& desc,
- const std::string& string,
- const std::string& drop_delims,
- const std::string& keep_delims,
- const std::vector<std::string>& expect)
- {
- ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect);
- }
-
- template<> template<>
- void string_index_object_t::test<42>()
- {
- set_test_name("getTokens(\"delims\", etc.)");
- // Signatures to test in this method:
- // getTokens(string, drop_delims, keep_delims [, quotes [, escapes]])
- // If you omit keep_delims, you get the older function (test above).
-
- // cases like the getTokens(string, delims) tests above
- ensure_getTokens("empty string", "", " ", "", StringVec());
- ensure_getTokens("only delims",
- " \r\n ", " \r\n", "", StringVec());
- ensure_getTokens("sequence of delims",
- ",,, one ,,,", ", ", "", list_of("one"));
- // Note contrast with the case in the previous method
- ensure_getTokens("noncontiguous tokens",
- ", ,, , one ,,,", ", ", "", list_of("one"));
- ensure_getTokens("space-padded tokens",
- ", one , two ,", ", ", "",
- list_of("one")("two"));
- ensure_getTokens("no delims", "one", ",", "", list_of("one"));
-
- // drop_delims vs. keep_delims
- ensure_getTokens("arithmetic",
- " ab+def / xx* yy ", " ", "+-*/",
- list_of("ab")("+")("def")("/")("xx")("*")("yy"));
-
- // quotes
- ensure_getTokens("no quotes",
- "She said, \"Don't go.\"", " ", ",", "",
- list_of("She")("said")(",")("\"Don't")("go.\""));
- ensure_getTokens("quotes",
- "She said, \"Don't go.\"", " ", ",", "\"",
- list_of("She")("said")(",")("Don't go."));
- ensure_getTokens("quotes and delims",
- "run c:/'Documents and Settings'/someone", " ", "", "'",
- list_of("run")("c:/Documents and Settings/someone"));
- ensure_getTokens("unmatched quote",
- "baby don't leave", " ", "", "'",
- list_of("baby")("don't")("leave"));
- ensure_getTokens("adjacent quoted",
- "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'",
- list_of("abcdef \"ghijkl' mnopqr"));
- ensure_getTokens("quoted empty string",
- "--set SomeVar ''", " ", "", "'",
- list_of("--set")("SomeVar")(""));
-
- // escapes
- // Don't use backslash as an escape for these tests -- you'll go nuts
- // between the C++ string scanner and getTokens() escapes. Test with
- // something else!
- ensure_equals("escaped delims",
- LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"),
- list_of(" a")("-")("dog-gone phrase"));
- ensure_equals("escaped quotes",
- LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"),
- list_of("say:")("this isn't working."));
- ensure_equals("escaped escape",
- LLStringUtil::getTokens("want x^^2", " ", "", "", "^"),
- list_of("want")("x^2"));
- ensure_equals("escape at end",
- LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"),
- list_of("it's up")("there^"));
- }
-}
+/** + * @file llstring_test.cpp + * @author Adroit, Steve Linden, Tofu Linden + * @date 2006-12-24 + * @brief Test cases of llstring.cpp + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include <boost/assign/list_of.hpp> +#include "../llstring.h" +#include "StringVec.h" // must come BEFORE lltut.h +#include "../test/lltut.h" + +using boost::assign::list_of; + +namespace tut +{ + struct string_index + { + }; + typedef test_group<string_index> string_index_t; + typedef string_index_t::object string_index_object_t; + tut::string_index_t tut_string_index("LLString"); + + template<> template<> + void string_index_object_t::test<1>() + { + std::string llstr1; + ensure("Empty std::string", (llstr1.size() == 0) && llstr1.empty()); + + std::string llstr2("Hello"); + ensure("std::string = Hello", (!strcmp(llstr2.c_str(), "Hello")) && (llstr2.size() == 5) && !llstr2.empty()); + + std::string llstr3(llstr2); + ensure("std::string = std::string(std::string)", (!strcmp(llstr3.c_str(), "Hello")) && (llstr3.size() == 5) && !llstr3.empty()); + + std::string str("Hello World"); + std::string llstr4(str, 6); + ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (!strcmp(llstr4.c_str(), "World")) && (llstr4.size() == 5) && !llstr4.empty()); + + std::string llstr5(str, str.size()); + ensure("std::string = std::string(s, size_type pos, size_type n = npos)", (llstr5.size() == 0) && llstr5.empty()); + + std::string llstr6(5, 'A'); + ensure("std::string = std::string(count, c)", (!strcmp(llstr6.c_str(), "AAAAA")) && (llstr6.size() == 5) && !llstr6.empty()); + + std::string llstr7("Hello World", 5); + ensure("std::string(s, n)", (!strcmp(llstr7.c_str(), "Hello")) && (llstr7.size() == 5) && !llstr7.empty()); + + std::string llstr8("Hello World", 6, 5); + ensure("std::string(s, n, count)", (!strcmp(llstr8.c_str(), "World")) && (llstr8.size() == 5) && !llstr8.empty()); + + std::string llstr9("Hello World", sizeof("Hello World")-1, 5); // go past end + ensure("std::string(s, n, count) goes past end", (llstr9.size() == 0) && llstr9.empty()); + } + + template<> template<> + void string_index_object_t::test<3>() + { + std::string str("Len=5"); + ensure("isValidIndex failed", LLStringUtil::isValidIndex(str, 0) == true && + LLStringUtil::isValidIndex(str, 5) == true && + LLStringUtil::isValidIndex(str, 6) == false); + + std::string str1; + ensure("isValidIndex failed fo rempty string", LLStringUtil::isValidIndex(str1, 0) == false); + } + + template<> template<> + void string_index_object_t::test<4>() + { + std::string str_val(" Testing the extra whitespaces "); + LLStringUtil::trimHead(str_val); + ensure_equals("1: trimHead failed", str_val, "Testing the extra whitespaces "); + + std::string str_val1("\n\t\r\n Testing the extra whitespaces "); + LLStringUtil::trimHead(str_val1); + ensure_equals("2: trimHead failed", str_val1, "Testing the extra whitespaces "); + } + + template<> template<> + void string_index_object_t::test<5>() + { + std::string str_val(" Testing the extra whitespaces "); + LLStringUtil::trimTail(str_val); + ensure_equals("1: trimTail failed", str_val, " Testing the extra whitespaces"); + + std::string str_val1("\n Testing the extra whitespaces \n\t\r\n "); + LLStringUtil::trimTail(str_val1); + ensure_equals("2: trimTail failed", str_val1, "\n Testing the extra whitespaces"); + } + + + template<> template<> + void string_index_object_t::test<6>() + { + std::string str_val(" \t \r Testing the extra \r\n whitespaces \n \t "); + LLStringUtil::trim(str_val); + ensure_equals("1: trim failed", str_val, "Testing the extra \r\n whitespaces"); + } + + template<> template<> + void string_index_object_t::test<7>() + { + std::string str("Second LindenLabs"); + LLStringUtil::truncate(str, 6); + ensure_equals("1: truncate", str, "Second"); + + // further truncate more than the length + LLStringUtil::truncate(str, 0); + ensure_equals("2: truncate", str, ""); + } + + template<> template<> + void string_index_object_t::test<8>() + { + std::string str_val("SecondLife Source"); + LLStringUtil::toUpper(str_val); + ensure_equals("toUpper failed", str_val, "SECONDLIFE SOURCE"); + } + + template<> template<> + void string_index_object_t::test<9>() + { + std::string str_val("SecondLife Source"); + LLStringUtil::toLower(str_val); + ensure_equals("toLower failed", str_val, "secondlife source"); + } + + template<> template<> + void string_index_object_t::test<10>() + { + std::string str_val("Second"); + ensure("1. isHead failed", LLStringUtil::isHead(str_val, "SecondLife Source") == true); + ensure("2. isHead failed", LLStringUtil::isHead(str_val, " SecondLife Source") == false); + std::string str_val2(""); + ensure("3. isHead failed", LLStringUtil::isHead(str_val2, "") == false); + } + + template<> template<> + void string_index_object_t::test<11>() + { + std::string str_val("Hello.\n\n Lindenlabs. \n This is \na simple test.\n"); + std::string orig_str_val(str_val); + LLStringUtil::addCRLF(str_val); + ensure_equals("addCRLF failed", str_val, "Hello.\r\n\r\n Lindenlabs. \r\n This is \r\na simple test.\r\n"); + LLStringUtil::removeCRLF(str_val); + ensure_equals("removeCRLF failed", str_val, orig_str_val); + } + + template<> template<> + void string_index_object_t::test<12>() + { + std::string str_val("Hello.\n\n\t \t Lindenlabs. \t\t"); + std::string orig_str_val(str_val); + LLStringUtil::replaceTabsWithSpaces(str_val, 1); + ensure_equals("replaceTabsWithSpaces failed", str_val, "Hello.\n\n Lindenlabs. "); + LLStringUtil::replaceTabsWithSpaces(orig_str_val, 0); + ensure_equals("replaceTabsWithSpaces failed for 0", orig_str_val, "Hello.\n\n Lindenlabs. "); + + str_val = "\t\t\t\t"; + LLStringUtil::replaceTabsWithSpaces(str_val, 0); + ensure_equals("replaceTabsWithSpaces failed for all tabs", str_val, ""); + } + + template<> template<> + void string_index_object_t::test<13>() + { + std::string str_val("Hello.\n\n\t\t\r\nLindenlabsX."); + LLStringUtil::replaceNonstandardASCII(str_val, 'X'); + ensure_equals("replaceNonstandardASCII failed", str_val, "Hello.\n\nXXX\nLindenlabsX."); + } + + template<> template<> + void string_index_object_t::test<14>() + { + std::string str_val("Hello.\n\t\r\nABCDEFGHIABABAB"); + LLStringUtil::replaceChar(str_val, 'A', 'X'); + ensure_equals("1: replaceChar failed", str_val, "Hello.\n\t\r\nXBCDEFGHIXBXBXB"); + std::string str_val1("Hello.\n\t\r\nABCDEFGHIABABAB"); + } + + template<> template<> + void string_index_object_t::test<15>() + { + std::string str_val("Hello.\n\r\t"); + ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == true); + + str_val = "ABC "; + ensure("containsNonprintable failed", LLStringUtil::containsNonprintable(str_val) == false); + } + + template<> template<> + void string_index_object_t::test<16>() + { + std::string str_val("Hello.\n\r\t Again!"); + LLStringUtil::stripNonprintable(str_val); + ensure_equals("stripNonprintable failed", str_val, "Hello. Again!"); + + str_val = "\r\n\t\t"; + LLStringUtil::stripNonprintable(str_val); + ensure_equals("stripNonprintable resulting in empty string failed", str_val, ""); + + str_val = ""; + LLStringUtil::stripNonprintable(str_val); + ensure_equals("stripNonprintable of empty string resulting in empty string failed", str_val, ""); + } + + template<> template<> + void string_index_object_t::test<17>() + { + bool value; + std::string str_val("1"); + ensure("convertToBOOL 1 failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "T"; + ensure("convertToBOOL T failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "t"; + ensure("convertToBOOL t failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "TRUE"; + ensure("convertToBOOL TRUE failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "True"; + ensure("convertToBOOL True failed", LLStringUtil::convertToBOOL(str_val, value) && value); + str_val = "true"; + ensure("convertToBOOL true failed", LLStringUtil::convertToBOOL(str_val, value) && value); + + str_val = "0"; + ensure("convertToBOOL 0 failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "F"; + ensure("convertToBOOL F failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "f"; + ensure("convertToBOOL f failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "FALSE"; + ensure("convertToBOOL FASLE failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "False"; + ensure("convertToBOOL False failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + str_val = "false"; + ensure("convertToBOOL false failed", LLStringUtil::convertToBOOL(str_val, value) && !value); + + str_val = "Tblah"; + ensure("convertToBOOL false failed", !LLStringUtil::convertToBOOL(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<18>() + { + U8 value; + std::string str_val("255"); + ensure("1: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 255); + + str_val = "0"; + ensure("2: convertToU8 failed", LLStringUtil::convertToU8(str_val, value) && value == 0); + + str_val = "-1"; + ensure("3: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); + + str_val = "256"; // bigger than MAX_U8 + ensure("4: convertToU8 failed", !LLStringUtil::convertToU8(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<19>() + { + S8 value; + std::string str_val("127"); + ensure("1: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 127); + + str_val = "0"; + ensure("2: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == 0); + + str_val = "-128"; + ensure("3: convertToS8 failed", LLStringUtil::convertToS8(str_val, value) && value == -128); + + str_val = "128"; // bigger than MAX_S8 + ensure("4: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); + + str_val = "-129"; + ensure("5: convertToS8 failed", !LLStringUtil::convertToS8(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<20>() + { + S16 value; + std::string str_val("32767"); + ensure("1: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 32767); + + str_val = "0"; + ensure("2: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == 0); + + str_val = "-32768"; + ensure("3: convertToS16 failed", LLStringUtil::convertToS16(str_val, value) && value == -32768); + + str_val = "32768"; + ensure("4: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); + + str_val = "-32769"; + ensure("5: convertToS16 failed", !LLStringUtil::convertToS16(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<21>() + { + U16 value; + std::string str_val("65535"); //0xFFFF + ensure("1: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 65535); + + str_val = "0"; + ensure("2: convertToU16 failed", LLStringUtil::convertToU16(str_val, value) && value == 0); + + str_val = "-1"; + ensure("3: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); + + str_val = "65536"; + ensure("4: convertToU16 failed", !LLStringUtil::convertToU16(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<22>() + { + U32 value; + std::string str_val("4294967295"); //0xFFFFFFFF + ensure("1: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 4294967295UL); + + str_val = "0"; + ensure("2: convertToU32 failed", LLStringUtil::convertToU32(str_val, value) && value == 0); + + str_val = "4294967296"; + ensure("3: convertToU32 failed", !LLStringUtil::convertToU32(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<23>() + { + S32 value; + std::string str_val("2147483647"); //0x7FFFFFFF + ensure("1: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 2147483647); + + str_val = "0"; + ensure("2: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == 0); + + // Avoid "unary minus operator applied to unsigned type" warning on VC++. JC + S32 min_val = -2147483647 - 1; + str_val = "-2147483648"; + ensure("3: convertToS32 failed", LLStringUtil::convertToS32(str_val, value) && value == min_val); + + str_val = "2147483648"; + ensure("4: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); + + str_val = "-2147483649"; + ensure("5: convertToS32 failed", !LLStringUtil::convertToS32(str_val, value)); + } + + template<> template<> + void string_index_object_t::test<24>() + { + F32 value; + std::string str_val("2147483647"); //0x7FFFFFFF + ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647); + + str_val = "0"; + ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0); + + /* Need to find max/min F32 values + str_val = "-2147483648"; + ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); + + str_val = "2147483648"; + ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + + str_val = "-2147483649"; + ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + */ + } + + template<> template<> + void string_index_object_t::test<25>() + { + F64 value; + std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF + ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807LL); + + str_val = "0"; + ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F); + + /* Need to find max/min F64 values + str_val = "-2147483648"; + ensure("3: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == -2147483648); + + str_val = "2147483648"; + ensure("4: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + + str_val = "-2147483649"; + ensure("5: convertToF32 failed", !LLStringUtil::convertToF32(str_val, value)); + */ + } + + template<> template<> + void string_index_object_t::test<26>() + { + const char* str1 = NULL; + const char* str2 = NULL; + + ensure("1: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); + str2 = "A"; + ensure("2: compareStrings failed", LLStringUtil::compareStrings(str1, str2) > 0); + ensure("3: compareStrings failed", LLStringUtil::compareStrings(str2, str1) < 0); + + str1 = "A is smaller than B"; + str2 = "B is greater than A"; + ensure("4: compareStrings failed", LLStringUtil::compareStrings(str1, str2) < 0); + + str2 = "A is smaller than B"; + ensure("5: compareStrings failed", LLStringUtil::compareStrings(str1, str2) == 0); + } + + template<> template<> + void string_index_object_t::test<27>() + { + const char* str1 = NULL; + const char* str2 = NULL; + + ensure("1: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); + str2 = "A"; + ensure("2: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) > 0); + ensure("3: compareInsensitive failed", LLStringUtil::compareInsensitive(str2, str1) < 0); + + str1 = "A is equal to a"; + str2 = "a is EQUAL to A"; + ensure("4: compareInsensitive failed", LLStringUtil::compareInsensitive(str1, str2) == 0); + } + + template<> template<> + void string_index_object_t::test<28>() + { + std::string lhs_str("PROgraM12files"); + std::string rhs_str("PROgram12Files"); + ensure("compareDict 1 failed", LLStringUtil::compareDict(lhs_str, rhs_str) < 0); + ensure("precedesDict 1 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == true); + + lhs_str = "PROgram12Files"; + rhs_str = "PROgram12Files"; + ensure("compareDict 2 failed", LLStringUtil::compareDict(lhs_str, rhs_str) == 0); + ensure("precedesDict 2 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); + + lhs_str = "PROgram12Files"; + rhs_str = "PROgRAM12FILES"; + ensure("compareDict 3 failed", LLStringUtil::compareDict(lhs_str, rhs_str) > 0); + ensure("precedesDict 3 failed", LLStringUtil::precedesDict(lhs_str, rhs_str) == false); + } + + template<> template<> + void string_index_object_t::test<29>() + { + char str1[] = "First String..."; + char str2[100]; + + LLStringUtil::copy(str2, str1, 100); + ensure("LLStringUtil::copy with enough dest length failed", strcmp(str2, str1) == 0); + LLStringUtil::copy(str2, str1, sizeof("First")); + ensure("LLStringUtil::copy with less dest length failed", strcmp(str2, "First") == 0); + } + + template<> template<> + void string_index_object_t::test<30>() + { + std::string str1 = "This is the sentence..."; + std::string str2 = "This is the "; + std::string str3 = "first "; + std::string str4 = "This is the first sentence..."; + std::string str5 = "This is the sentence...first "; + std::string dest; + + dest = str1; + LLStringUtil::copyInto(dest, str3, str2.length()); + ensure("LLStringUtil::copyInto insert failed", dest == str4); + + dest = str1; + LLStringUtil::copyInto(dest, str3, dest.length()); + ensure("LLStringUtil::copyInto append failed", dest == str5); + } + + template<> template<> + void string_index_object_t::test<31>() + { + std::string stripped; + + // Plain US ASCII text, including spaces and punctuation, + // should not be altered. + std::string simple_text = "Hello, world!"; + stripped = LLStringFn::strip_invalid_xml(simple_text); + ensure("Simple text passed unchanged", stripped == simple_text); + + // Control characters should be removed + // except for 0x09, 0x0a, 0x0d + std::string control_chars; + for (char c = 0x01; c < 0x20; c++) + { + control_chars.push_back(c); + } + std::string allowed_control_chars; + allowed_control_chars.push_back( (char)0x09 ); + allowed_control_chars.push_back( (char)0x0a ); + allowed_control_chars.push_back( (char)0x0d ); + + stripped = LLStringFn::strip_invalid_xml(control_chars); + ensure("Only tab, LF, CR control characters allowed", + stripped == allowed_control_chars); + + // UTF-8 should be passed intact, including high byte + // characters. Try Francais (with C squiggle cedilla) + std::string french = "Fran"; + french.push_back( (char)0xC3 ); + french.push_back( (char)0xA7 ); + french += "ais"; + stripped = LLStringFn::strip_invalid_xml( french ); + ensure("UTF-8 high byte text is allowed", french == stripped ); + } + + template<> template<> + void string_index_object_t::test<32>() + { + // Test LLStringUtil::format() string interpolation + LLStringUtil::format_map_t fmt_map; + std::string s; + int subcount; + + fmt_map["[TRICK1]"] = "[A]"; + fmt_map["[A]"] = "a"; + fmt_map["[B]"] = "b"; + fmt_map["[AAA]"] = "aaa"; + fmt_map["[BBB]"] = "bbb"; + fmt_map["[TRICK2]"] = "[A]"; + fmt_map["[EXPLOIT]"] = "!!!!!!!!!!!![EXPLOIT]!!!!!!!!!!!!"; + fmt_map["[KEYLONGER]"] = "short"; + fmt_map["[KEYSHORTER]"] = "Am I not a long string?"; + fmt_map["?"] = "?"; + fmt_map["[DELETE]"] = ""; + fmt_map["[]"] = "[]"; // doesn't do a substitution, but shouldn't crash either + + for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) + { + // Test when source string is entirely one key + std::string s1 = (std::string)iter->first; + std::string s2 = (std::string)iter->second; + subcount = LLStringUtil::format(s1, fmt_map); + ensure_equals("LLStringUtil::format: Raw interpolation result", s1, s2); + if (s1 == "?" || s1 == "[]") // no interp expected + { + ensure_equals("LLStringUtil::format: Raw interpolation result count", 0, subcount); + } + else + { + ensure_equals("LLStringUtil::format: Raw interpolation result count", 1, subcount); + } + } + + for (LLStringUtil::format_map_t::const_iterator iter = fmt_map.begin(); iter != fmt_map.end(); ++iter) + { + // Test when source string is one key, duplicated + std::string s1 = (std::string)iter->first; + std::string s2 = (std::string)iter->second; + s = s1 + s1 + s1 + s1; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Rawx4 interpolation result", s, s2 + s2 + s2 + s2); + if (s1 == "?" || s1 == "[]") // no interp expected + { + ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 0, subcount); + } + else + { + ensure_equals("LLStringUtil::format: Rawx4 interpolation result count", 4, subcount); + } + } + + // Test when source string has no keys + std::string srcs = "!!!!!!!!!!!!!!!!"; + s = srcs; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: No key test result", s, srcs); + ensure_equals("LLStringUtil::format: No key test result count", 0, subcount); + + // Test when source string has no keys and is empty + std::string srcs3; + s = srcs3; + subcount = LLStringUtil::format(s, fmt_map); + ensure("LLStringUtil::format: No key test3 result", s.empty()); + ensure_equals("LLStringUtil::format: No key test3 result count", 0, subcount); + + // Test a substitution where a key is substituted with blankness + std::string srcs2 = "[DELETE]"; + s = srcs2; + subcount = LLStringUtil::format(s, fmt_map); + ensure("LLStringUtil::format: Delete key test2 result", s.empty()); + ensure_equals("LLStringUtil::format: Delete key test2 result count", 1, subcount); + + // Test an assorted substitution + std::string srcs4 = "[TRICK1][A][B][AAA][BBB][TRICK2][KEYLONGER][KEYSHORTER]?[DELETE]"; + s = srcs4; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Assorted Test1 result", s, "[A]abaaabbb[A]shortAm I not a long string??"); + ensure_equals("LLStringUtil::format: Assorted Test1 result count", 9, subcount); + + // Test an assorted substitution + std::string srcs5 = "[DELETE]?[KEYSHORTER][KEYLONGER][TRICK2][BBB][AAA][B][A][TRICK1]"; + s = srcs5; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "?Am I not a long string?short[A]bbbaaaba[A]"); + ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); + + // Test on nested brackets + std::string srcs6 = "[[TRICK1]][[A]][[B]][[AAA]][[BBB]][[TRICK2]][[KEYLONGER]][[KEYSHORTER]]?[[DELETE]]"; + s = srcs6; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Assorted Test2 result", s, "[[A]][a][b][aaa][bbb][[A]][short][Am I not a long string?]?[]"); + ensure_equals("LLStringUtil::format: Assorted Test2 result count", 9, subcount); + + + // Test an assorted substitution + std::string srcs8 = "foo[DELETE]bar?"; + s = srcs8; + subcount = LLStringUtil::format(s, fmt_map); + ensure_equals("LLStringUtil::format: Assorted Test3 result", s, "foobar?"); + ensure_equals("LLStringUtil::format: Assorted Test3 result count", 1, subcount); + } + + template<> template<> + void string_index_object_t::test<33>() + { + // Test LLStringUtil::format() string interpolation + LLStringUtil::format_map_t blank_fmt_map; + std::string s; + int subcount; + + // Test substituting out of a blank format_map + std::string srcs6 = "12345"; + s = srcs6; + subcount = LLStringUtil::format(s, blank_fmt_map); + ensure_equals("LLStringUtil::format: Blankfmt Test1 result", s, "12345"); + ensure_equals("LLStringUtil::format: Blankfmt Test1 result count", 0, subcount); + + // Test substituting a blank string out of a blank format_map + std::string srcs7; + s = srcs7; + subcount = LLStringUtil::format(s, blank_fmt_map); + ensure("LLStringUtil::format: Blankfmt Test2 result", s.empty()); + ensure_equals("LLStringUtil::format: Blankfmt Test2 result count", 0, subcount); + } + + template<> template<> + void string_index_object_t::test<34>() + { + // Test that incorrect LLStringUtil::format() use does not explode. + LLStringUtil::format_map_t nasty_fmt_map; + std::string s; + int subcount; + + nasty_fmt_map[""] = "never used"; // see, this is nasty. + + // Test substituting out of a nasty format_map + std::string srcs6 = "12345"; + s = srcs6; + subcount = LLStringUtil::format(s, nasty_fmt_map); + ensure_equals("LLStringUtil::format: Nastyfmt Test1 result", s, "12345"); + ensure_equals("LLStringUtil::format: Nastyfmt Test1 result count", 0, subcount); + + // Test substituting a blank string out of a nasty format_map + std::string srcs7; + s = srcs7; + subcount = LLStringUtil::format(s, nasty_fmt_map); + ensure("LLStringUtil::format: Nastyfmt Test2 result", s.empty()); + ensure_equals("LLStringUtil::format: Nastyfmt Test2 result count", 0, subcount); + } + + template<> template<> + void string_index_object_t::test<35>() + { + // Make sure startsWith works + std::string string("anybody in there?"); + std::string substr("anybody"); + ensure("startsWith works.", LLStringUtil::startsWith(string, substr)); + } + + template<> template<> + void string_index_object_t::test<36>() + { + // Make sure startsWith correctly fails + std::string string("anybody in there?"); + std::string substr("there"); + ensure("startsWith fails.", !LLStringUtil::startsWith(string, substr)); + } + + template<> template<> + void string_index_object_t::test<37>() + { + // startsWith fails on empty strings + std::string value("anybody in there?"); + std::string empty; + ensure("empty string.", !LLStringUtil::startsWith(value, empty)); + ensure("empty substr.", !LLStringUtil::startsWith(empty, value)); + ensure("empty everything.", !LLStringUtil::startsWith(empty, empty)); + } + + template<> template<> + void string_index_object_t::test<38>() + { + // Make sure endsWith works correctly + std::string string("anybody in there?"); + std::string substr("there?"); + ensure("endsWith works.", LLStringUtil::endsWith(string, substr)); + } + + template<> template<> + void string_index_object_t::test<39>() + { + // Make sure endsWith correctly fails + std::string string("anybody in there?"); + std::string substr("anybody"); + ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); + substr = "there"; + ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); + substr = "ther?"; + ensure("endsWith fails.", !LLStringUtil::endsWith(string, substr)); + } + + template<> template<> + void string_index_object_t::test<40>() + { + // endsWith fails on empty strings + std::string value("anybody in there?"); + std::string empty; + ensure("empty string.", !LLStringUtil::endsWith(value, empty)); + ensure("empty substr.", !LLStringUtil::endsWith(empty, value)); + ensure("empty everything.", !LLStringUtil::endsWith(empty, empty)); + } + + template<> template<> + void string_index_object_t::test<41>() + { + set_test_name("getTokens(\"delims\")"); + ensure_equals("empty string", LLStringUtil::getTokens("", " "), StringVec()); + ensure_equals("only delims", + LLStringUtil::getTokens(" \r\n ", " \r\n"), StringVec()); + ensure_equals("sequence of delims", + LLStringUtil::getTokens(",,, one ,,,", ","), list_of("one")); + // nat considers this a dubious implementation side effect, but I'd + // hate to change it now... + ensure_equals("noncontiguous tokens", + LLStringUtil::getTokens(", ,, , one ,,,", ","), list_of("")("")("one")); + ensure_equals("space-padded tokens", + LLStringUtil::getTokens(", one , two ,", ","), list_of("one")("two")); + ensure_equals("no delims", LLStringUtil::getTokens("one", ","), list_of("one")); + } + + // Shorthand for verifying that getTokens() behaves the same when you + // don't pass a string of escape characters, when you pass an empty string + // (different overloads), and when you pass a string of characters that + // aren't actually present. + void ensure_getTokens(const std::string& desc, + const std::string& string, + const std::string& drop_delims, + const std::string& keep_delims, + const std::string& quotes, + const std::vector<std::string>& expect) + { + ensure_equals(desc + " - no esc", + LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes), + expect); + ensure_equals(desc + " - empty esc", + LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, ""), + expect); + ensure_equals(desc + " - unused esc", + LLStringUtil::getTokens(string, drop_delims, keep_delims, quotes, "!"), + expect); + } + + void ensure_getTokens(const std::string& desc, + const std::string& string, + const std::string& drop_delims, + const std::string& keep_delims, + const std::vector<std::string>& expect) + { + ensure_getTokens(desc, string, drop_delims, keep_delims, "", expect); + } + + template<> template<> + void string_index_object_t::test<42>() + { + set_test_name("getTokens(\"delims\", etc.)"); + // Signatures to test in this method: + // getTokens(string, drop_delims, keep_delims [, quotes [, escapes]]) + // If you omit keep_delims, you get the older function (test above). + + // cases like the getTokens(string, delims) tests above + ensure_getTokens("empty string", "", " ", "", StringVec()); + ensure_getTokens("only delims", + " \r\n ", " \r\n", "", StringVec()); + ensure_getTokens("sequence of delims", + ",,, one ,,,", ", ", "", list_of("one")); + // Note contrast with the case in the previous method + ensure_getTokens("noncontiguous tokens", + ", ,, , one ,,,", ", ", "", list_of("one")); + ensure_getTokens("space-padded tokens", + ", one , two ,", ", ", "", + list_of("one")("two")); + ensure_getTokens("no delims", "one", ",", "", list_of("one")); + + // drop_delims vs. keep_delims + ensure_getTokens("arithmetic", + " ab+def / xx* yy ", " ", "+-*/", + list_of("ab")("+")("def")("/")("xx")("*")("yy")); + + // quotes + ensure_getTokens("no quotes", + "She said, \"Don't go.\"", " ", ",", "", + list_of("She")("said")(",")("\"Don't")("go.\"")); + ensure_getTokens("quotes", + "She said, \"Don't go.\"", " ", ",", "\"", + list_of("She")("said")(",")("Don't go.")); + ensure_getTokens("quotes and delims", + "run c:/'Documents and Settings'/someone", " ", "", "'", + list_of("run")("c:/Documents and Settings/someone")); + ensure_getTokens("unmatched quote", + "baby don't leave", " ", "", "'", + list_of("baby")("don't")("leave")); + ensure_getTokens("adjacent quoted", + "abc'def \"ghi'\"jkl' mno\"pqr", " ", "", "\"'", + list_of("abcdef \"ghijkl' mnopqr")); + ensure_getTokens("quoted empty string", + "--set SomeVar ''", " ", "", "'", + list_of("--set")("SomeVar")("")); + + // escapes + // Don't use backslash as an escape for these tests -- you'll go nuts + // between the C++ string scanner and getTokens() escapes. Test with + // something else! + ensure_equals("escaped delims", + LLStringUtil::getTokens("^ a - dog^-gone^ phrase", " ", "-", "", "^"), + list_of(" a")("-")("dog-gone phrase")); + ensure_equals("escaped quotes", + LLStringUtil::getTokens("say: 'this isn^'t w^orking'.", " ", "", "'", "^"), + list_of("say:")("this isn't working.")); + ensure_equals("escaped escape", + LLStringUtil::getTokens("want x^^2", " ", "", "", "^"), + list_of("want")("x^2")); + ensure_equals("escape at end", + LLStringUtil::getTokens("it's^ up there^", " ", "", "'", "^"), + list_of("it's up")("there^")); + } +} |