summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authorAnsariel <ansariel.hiller@phoenixviewer.com>2024-05-22 21:25:21 +0200
committerAndrey Lihatskiy <alihatskiy@productengine.com>2024-05-22 22:40:26 +0300
commite2e37cced861b98de8c1a7c9c0d3a50d2d90e433 (patch)
tree1bb897489ce524986f6196201c10ac0d8861aa5f /indra/llcommon
parent069ea06848f766466f1a281144c82a0f2bd79f3a (diff)
Fix line endlings
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/StackWalker.h452
-rw-r--r--indra/llcommon/llapp.cpp1492
-rw-r--r--indra/llcommon/llapp.h684
-rw-r--r--indra/llcommon/llapr.cpp1494
-rw-r--r--indra/llcommon/llapr.h402
-rw-r--r--indra/llcommon/llassettype.cpp492
-rw-r--r--indra/llcommon/llbase64.cpp154
-rw-r--r--indra/llcommon/llbase64.h76
-rw-r--r--indra/llcommon/llcallbacklist.cpp460
-rw-r--r--indra/llcommon/llcommon.cpp306
-rw-r--r--indra/llcommon/llcommon.h86
-rw-r--r--indra/llcommon/llcrc.cpp446
-rw-r--r--indra/llcommon/llcrc.h136
-rw-r--r--indra/llcommon/llerror.cpp3324
-rw-r--r--indra/llcommon/lleventemitter.h204
-rw-r--r--indra/llcommon/lleventtimer.h244
-rw-r--r--indra/llcommon/llframetimer.cpp300
-rw-r--r--indra/llcommon/llframetimer.h302
-rw-r--r--indra/llcommon/llkeythrottle.h662
-rw-r--r--indra/llcommon/lllivefile.cpp396
-rw-r--r--indra/llcommon/llmemory.cpp708
-rw-r--r--indra/llcommon/llmemory.h836
-rw-r--r--indra/llcommon/llmetricperformancetester.cpp666
-rw-r--r--indra/llcommon/llmetricperformancetester.h430
-rw-r--r--indra/llcommon/llmortician.cpp212
-rw-r--r--indra/llcommon/llmortician.h112
-rw-r--r--indra/llcommon/llmutex.cpp836
-rw-r--r--indra/llcommon/llmutex.h542
-rw-r--r--indra/llcommon/llnametable.h206
-rw-r--r--indra/llcommon/llpriqueuemap.h290
-rw-r--r--indra/llcommon/llprocess.cpp2716
-rw-r--r--indra/llcommon/llqueuedthread.cpp1174
-rw-r--r--indra/llcommon/llqueuedthread.h356
-rw-r--r--indra/llcommon/llregistry.h706
-rw-r--r--indra/llcommon/llsdserialize.cpp4940
-rw-r--r--indra/llcommon/llsdutil.cpp2166
-rw-r--r--indra/llcommon/llsdutil.h1348
-rw-r--r--indra/llcommon/llstacktrace.cpp336
-rw-r--r--indra/llcommon/llstl.h1426
-rw-r--r--indra/llcommon/llstring.cpp3512
-rw-r--r--indra/llcommon/llstring.h4078
-rw-r--r--indra/llcommon/llstringtable.h418
-rw-r--r--indra/llcommon/llsys.cpp2828
-rw-r--r--indra/llcommon/llsys.h348
-rw-r--r--indra/llcommon/llthread.cpp944
-rw-r--r--indra/llcommon/lltimer.cpp1216
-rw-r--r--indra/llcommon/lltimer.h376
-rw-r--r--indra/llcommon/lluri.cpp1516
-rw-r--r--indra/llcommon/lluri.h386
-rw-r--r--indra/llcommon/lluuid.cpp2154
-rw-r--r--indra/llcommon/lluuid.h388
-rw-r--r--indra/llcommon/llworkerthread.cpp796
-rw-r--r--indra/llcommon/llworkerthread.h402
-rw-r--r--indra/llcommon/tests/llstring_test.cpp1742
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, &current) ;
- }
-
- 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, &current) ;
+ }
+
+ 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(&timestamp);
- U16 our_clock_seq = clock_seq;
-
- // if clock hasn't changed or went backward, change clockseq
- if (cmpTime(&timestamp, &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(&timestamp);
+ U16 our_clock_seq = clock_seq;
+
+ // if clock hasn't changed or went backward, change clockseq
+ if (cmpTime(&timestamp, &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^"));
+ }
+}