diff options
| author | Nat Goodspeed <nat@lindenlab.com> | 2012-02-12 21:59:51 -0500 | 
|---|---|---|
| committer | Nat Goodspeed <nat@lindenlab.com> | 2012-02-12 21:59:51 -0500 | 
| commit | 6da3f0907cea3748adb6be9a7c393835109adf3b (patch) | |
| tree | 8a569f192a49913db7365c97548c1227a123127e /indra/llcommon | |
| parent | 8804fa524c084edb9f49f4122e2c7330a80c79b5 (diff) | |
| parent | 90a1b67cc43792e1ca0047eaca51530aa14e134c (diff) | |
Automated merge with ssh://hg.lindenlab.com/nat/viewer-new-apr
Diffstat (limited to 'indra/llcommon')
| -rw-r--r-- | indra/llcommon/CMakeLists.txt | 14 | ||||
| -rw-r--r-- | indra/llcommon/llinitparam.cpp | 469 | ||||
| -rw-r--r-- | indra/llcommon/llinitparam.h | 2294 | ||||
| -rw-r--r-- | indra/llcommon/llprocess.cpp | 642 | ||||
| -rw-r--r-- | indra/llcommon/llprocess.h | 207 | ||||
| -rw-r--r-- | indra/llcommon/llprocesslauncher.cpp | 357 | ||||
| -rw-r--r-- | indra/llcommon/llprocesslauncher.h | 90 | ||||
| -rw-r--r-- | indra/llcommon/llregistry.h | 351 | ||||
| -rw-r--r-- | indra/llcommon/llsdparam.cpp | 342 | ||||
| -rw-r--r-- | indra/llcommon/llsdparam.h | 126 | ||||
| -rw-r--r-- | indra/llcommon/llstreamqueue.cpp | 24 | ||||
| -rw-r--r-- | indra/llcommon/llstreamqueue.h | 240 | ||||
| -rw-r--r-- | indra/llcommon/llstring.h | 239 | ||||
| -rw-r--r-- | indra/llcommon/tests/llprocess_test.cpp | 706 | ||||
| -rw-r--r-- | indra/llcommon/tests/llsdserialize_test.cpp | 274 | ||||
| -rw-r--r-- | indra/llcommon/tests/llstreamqueue_test.cpp | 197 | 
16 files changed, 5769 insertions, 803 deletions
| diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 0a3eaec5c5..3255e28e8e 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -61,6 +61,7 @@ set(llcommon_SOURCE_FILES      llformat.cpp      llframetimer.cpp      llheartbeat.cpp +    llinitparam.cpp      llinstancetracker.cpp      llliveappconfig.cpp      lllivefile.cpp @@ -74,13 +75,14 @@ set(llcommon_SOURCE_FILES      llmortician.cpp      lloptioninterface.cpp      llptrto.cpp  -    llprocesslauncher.cpp +    llprocess.cpp      llprocessor.cpp      llqueuedthread.cpp      llrand.cpp      llrefcount.cpp      llrun.cpp      llsd.cpp +    llsdparam.cpp      llsdserialize.cpp      llsdserialize_xml.cpp      llsdutil.cpp @@ -88,6 +90,7 @@ set(llcommon_SOURCE_FILES      llsingleton.cpp      llstat.cpp      llstacktrace.cpp +    llstreamqueue.cpp      llstreamtools.cpp      llstring.cpp      llstringtable.cpp @@ -173,6 +176,7 @@ set(llcommon_HEADER_FILES      llheartbeat.h      llhttpstatuscodes.h      llindexedqueue.h +    llinitparam.h      llinstancetracker.h      llkeythrottle.h      lllazy.h @@ -196,7 +200,7 @@ set(llcommon_HEADER_FILES      llpointer.h      llpreprocessor.h      llpriqueuemap.h -    llprocesslauncher.h +    llprocess.h      llprocessor.h      llptrskiplist.h      llptrskipmap.h @@ -204,10 +208,12 @@ set(llcommon_HEADER_FILES      llqueuedthread.h      llrand.h      llrefcount.h +    llregistry.h      llrun.h      llrefcount.h      llsafehandle.h      llsd.h +    llsdparam.h      llsdserialize.h      llsdserialize_xml.h      llsdutil.h @@ -221,6 +227,7 @@ set(llcommon_HEADER_FILES      llstat.h      llstatenums.h      llstl.h +    llstreamqueue.h      llstreamtools.h      llstrider.h      llstring.h @@ -326,6 +333,9 @@ if (LL_TESTS)    LL_ADD_INTEGRATION_TEST(reflection "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}") +  LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}" +                          "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py") +  LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")    # *TODO - reenable these once tcmalloc libs no longer break the build.    #ADD_BUILD_TEST(llallocator llcommon) diff --git a/indra/llcommon/llinitparam.cpp b/indra/llcommon/llinitparam.cpp new file mode 100644 index 0000000000..db72aa19b9 --- /dev/null +++ b/indra/llcommon/llinitparam.cpp @@ -0,0 +1,469 @@ +/**  + * @file llinitparam.cpp + * @brief parameter block abstraction for creating complex objects and  + * parsing construction parameters from xml and LLSD + * + * $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 "llinitparam.h" + + +namespace LLInitParam +{ +	// +	// Param +	// +	Param::Param(BaseBlock* enclosing_block) +	:	mIsProvided(false) +	{ +		const U8* my_addr = reinterpret_cast<const U8*>(this); +		const U8* block_addr = reinterpret_cast<const U8*>(enclosing_block); +		mEnclosingBlockOffset = 0x7FFFffff & (U32)(my_addr - block_addr); +	} + +	// +	// ParamDescriptor +	// +	ParamDescriptor::ParamDescriptor(param_handle_t p,  +									merge_func_t merge_func,  +									deserialize_func_t deserialize_func,  +									serialize_func_t serialize_func, +									validation_func_t validation_func, +									inspect_func_t inspect_func, +									S32 min_count, +									S32 max_count) +	:	mParamHandle(p), +		mMergeFunc(merge_func), +		mDeserializeFunc(deserialize_func), +		mSerializeFunc(serialize_func), +		mValidationFunc(validation_func), +		mInspectFunc(inspect_func), +		mMinCount(min_count), +		mMaxCount(max_count), +		mUserData(NULL) +	{} + +	ParamDescriptor::ParamDescriptor() +	:	mParamHandle(0), +		mMergeFunc(NULL), +		mDeserializeFunc(NULL), +		mSerializeFunc(NULL), +		mValidationFunc(NULL), +		mInspectFunc(NULL), +		mMinCount(0), +		mMaxCount(0), +		mUserData(NULL) +	{} + +	ParamDescriptor::~ParamDescriptor() +	{ +		delete mUserData; +	} + +	// +	// Parser +	// +	Parser::~Parser() +	{} + +	void Parser::parserWarning(const std::string& message) +	{ +		if (mParseSilently) return; +		llwarns << message << llendl; +	} +	 +	void Parser::parserError(const std::string& message) +	{ +		if (mParseSilently) return; +		llerrs << message << llendl; +	} + + +	// +	// BlockDescriptor +	// +	void BlockDescriptor::aggregateBlockData(BlockDescriptor& src_block_data)  +	{ +		mNamedParams.insert(src_block_data.mNamedParams.begin(), src_block_data.mNamedParams.end()); +		std::copy(src_block_data.mUnnamedParams.begin(), src_block_data.mUnnamedParams.end(), std::back_inserter(mUnnamedParams)); +		std::copy(src_block_data.mValidationList.begin(), src_block_data.mValidationList.end(), std::back_inserter(mValidationList)); +		std::copy(src_block_data.mAllParams.begin(), src_block_data.mAllParams.end(), std::back_inserter(mAllParams)); +	} + +	BlockDescriptor::BlockDescriptor() +	:	mMaxParamOffset(0), +		mInitializationState(UNINITIALIZED), +		mCurrentBlockPtr(NULL) +	{} + +	// called by each derived class in least to most derived order +	void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) +	{ +		descriptor.mCurrentBlockPtr = this; +		descriptor.mMaxParamOffset = block_size; + +		switch(descriptor.mInitializationState) +		{ +		case BlockDescriptor::UNINITIALIZED: +			// copy params from base class here +			descriptor.aggregateBlockData(base_descriptor); + +			descriptor.mInitializationState = BlockDescriptor::INITIALIZING; +			break; +		case BlockDescriptor::INITIALIZING: +			descriptor.mInitializationState = BlockDescriptor::INITIALIZED; +			break; +		case BlockDescriptor::INITIALIZED: +			// nothing to do +			break; +		} +	} + +	param_handle_t BaseBlock::getHandleFromParam(const Param* param) const +	{ +		const U8* param_address = reinterpret_cast<const U8*>(param); +		const U8* baseblock_address = reinterpret_cast<const U8*>(this); +		return (param_address - baseblock_address); +	} + +	bool BaseBlock::submitValue(Parser::name_stack_t& name_stack, Parser& p, bool silent) +	{ +		if (!deserializeBlock(p, std::make_pair(name_stack.begin(), name_stack.end()), true)) +		{ +			if (!silent) +			{ +				p.parserWarning(llformat("Failed to parse parameter \"%s\"", p.getCurrentElementName().c_str())); +			} +			return false; +		} +		return true; +	} + + +	bool BaseBlock::validateBlock(bool emit_errors) const +	{ +		const BlockDescriptor& block_data = mostDerivedBlockDescriptor(); +		for (BlockDescriptor::param_validation_list_t::const_iterator it = block_data.mValidationList.begin(); it != block_data.mValidationList.end(); ++it) +		{ +			const Param* param = getParamFromHandle(it->first); +			if (!it->second(param)) +			{ +				if (emit_errors) +				{ +					llwarns << "Invalid param \"" << getParamName(block_data, param) << "\"" << llendl; +				} +				return false; +			} +		} +		return true; +	} + +	void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const +	{ +		// named param is one like LLView::Params::follows +		// unnamed param is like LLView::Params::rect - implicit +		const BlockDescriptor& block_data = mostDerivedBlockDescriptor(); + +		for (BlockDescriptor::param_list_t::const_iterator it = block_data.mUnnamedParams.begin();  +			it != block_data.mUnnamedParams.end();  +			++it) +		{ +			param_handle_t param_handle = (*it)->mParamHandle; +			const Param* param = getParamFromHandle(param_handle); +			ParamDescriptor::serialize_func_t serialize_func = (*it)->mSerializeFunc; +			if (serialize_func) +			{ +				const Param* diff_param = diff_block ? diff_block->getParamFromHandle(param_handle) : NULL; +				// each param descriptor remembers its serial number +				// so we can inspect the same param under different names +				// and see that it has the same number +				name_stack.push_back(std::make_pair("", true)); +				serialize_func(*param, parser, name_stack, diff_param); +				name_stack.pop_back(); +			} +		} + +		for(BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); +			it != block_data.mNamedParams.end(); +			++it) +		{ +			param_handle_t param_handle = it->second->mParamHandle; +			const Param* param = getParamFromHandle(param_handle); +			ParamDescriptor::serialize_func_t serialize_func = it->second->mSerializeFunc; +			if (serialize_func && param->anyProvided()) +			{ +				// Ensure this param has not already been serialized +				// Prevents <rect> from being serialized as its own tag. +				bool duplicate = false; +				for (BlockDescriptor::param_list_t::const_iterator it2 = block_data.mUnnamedParams.begin();  +					it2 != block_data.mUnnamedParams.end();  +					++it2) +				{ +					if (param_handle == (*it2)->mParamHandle) +					{ +						duplicate = true; +						break; +					} +				} + +				//FIXME: for now, don't attempt to serialize values under synonyms, as current parsers +				// don't know how to detect them +				if (duplicate)  +				{ +					continue; +				} + +				name_stack.push_back(std::make_pair(it->first, !duplicate)); +				const Param* diff_param = diff_block ? diff_block->getParamFromHandle(param_handle) : NULL; +				serialize_func(*param, parser, name_stack, diff_param); +				name_stack.pop_back(); +			} +		} +	} + +	bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_count, S32 max_count) const +	{ +		// named param is one like LLView::Params::follows +		// unnamed param is like LLView::Params::rect - implicit +		const BlockDescriptor& block_data = mostDerivedBlockDescriptor(); + +		for (BlockDescriptor::param_list_t::const_iterator it = block_data.mUnnamedParams.begin();  +			it != block_data.mUnnamedParams.end();  +			++it) +		{ +			param_handle_t param_handle = (*it)->mParamHandle; +			const Param* param = getParamFromHandle(param_handle); +			ParamDescriptor::inspect_func_t inspect_func = (*it)->mInspectFunc; +			if (inspect_func) +			{ +				name_stack.push_back(std::make_pair("", true)); +				inspect_func(*param, parser, name_stack, (*it)->mMinCount, (*it)->mMaxCount); +				name_stack.pop_back(); +			} +		} + +		for(BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); +			it != block_data.mNamedParams.end(); +			++it) +		{ +			param_handle_t param_handle = it->second->mParamHandle; +			const Param* param = getParamFromHandle(param_handle); +			ParamDescriptor::inspect_func_t inspect_func = it->second->mInspectFunc; +			if (inspect_func) +			{ +				// Ensure this param has not already been inspected +				bool duplicate = false; +				for (BlockDescriptor::param_list_t::const_iterator it2 = block_data.mUnnamedParams.begin();  +					it2 != block_data.mUnnamedParams.end();  +					++it2) +				{ +					if (param_handle == (*it2)->mParamHandle) +					{ +						duplicate = true; +						break; +					} +				} + +				name_stack.push_back(std::make_pair(it->first, !duplicate)); +				inspect_func(*param, parser, name_stack, it->second->mMinCount, it->second->mMaxCount); +				name_stack.pop_back(); +			} +		} + +		return true; +	} + +	bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool ignored) +	{ +		BlockDescriptor& block_data = mostDerivedBlockDescriptor(); +		bool names_left = name_stack_range.first != name_stack_range.second; + +		bool new_name = names_left +						? name_stack_range.first->second +						: true; + +		if (names_left) +		{ +			const std::string& top_name = name_stack_range.first->first; + +			ParamDescriptor::deserialize_func_t deserialize_func = NULL; +			Param* paramp = NULL; + +			BlockDescriptor::param_map_t::iterator found_it = block_data.mNamedParams.find(top_name); +			if (found_it != block_data.mNamedParams.end()) +			{ +				// find pointer to member parameter from offset table +				paramp = getParamFromHandle(found_it->second->mParamHandle); +				deserialize_func = found_it->second->mDeserializeFunc; +					 +				Parser::name_stack_range_t new_name_stack(name_stack_range.first, name_stack_range.second); +				++new_name_stack.first; +				if (deserialize_func(*paramp, p, new_name_stack, new_name)) +				{ +					// value is no longer new, we know about it now +					name_stack_range.first->second = false; +					return true; +				} +				else +				{ +					return false; +				} +			} +		} + +		// try to parse unnamed parameters, in declaration order +		for ( BlockDescriptor::param_list_t::iterator it = block_data.mUnnamedParams.begin();  +			it != block_data.mUnnamedParams.end();  +			++it) +		{ +			Param* paramp = getParamFromHandle((*it)->mParamHandle); +			ParamDescriptor::deserialize_func_t deserialize_func = (*it)->mDeserializeFunc; + +			if (deserialize_func && deserialize_func(*paramp, p, name_stack_range, new_name)) +			{ +				return true; +			} +		} + +		// if no match, and no names left on stack, this is just an existence assertion of this block +		// verify by calling readValue with NoParamValue type, an inherently unparseable type +		if (!names_left) +		{ +			Flag no_value; +			return p.readValue(no_value); +		} + +		return false; +	} + +	//static  +	void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptorPtr in_param, const char* char_name) +	{ +		// create a copy of the param descriptor in mAllParams +		// so other data structures can store a pointer to it +		block_data.mAllParams.push_back(in_param); +		ParamDescriptorPtr param(block_data.mAllParams.back()); + +		std::string name(char_name); +		if ((size_t)param->mParamHandle > block_data.mMaxParamOffset) +		{ +			llerrs << "Attempted to register param with block defined for parent class, make sure to derive from LLInitParam::Block<YOUR_CLASS, PARAM_BLOCK_BASE_CLASS>" << llendl; +		} + +		if (name.empty()) +		{ +			block_data.mUnnamedParams.push_back(param); +		} +		else +		{ +			// don't use insert, since we want to overwrite existing entries +			block_data.mNamedParams[name] = param; +		} + +		if (param->mValidationFunc) +		{ +			block_data.mValidationList.push_back(std::make_pair(param->mParamHandle, param->mValidationFunc)); +		} +	} + +	void BaseBlock::addSynonym(Param& param, const std::string& synonym) +	{ +		BlockDescriptor& block_data = mostDerivedBlockDescriptor(); +		if (block_data.mInitializationState == BlockDescriptor::INITIALIZING) +		{ +			param_handle_t handle = getHandleFromParam(¶m); +			 +			// check for invalid derivation from a paramblock (i.e. without using +			// Block<T, Base_Class> +			if ((size_t)handle > block_data.mMaxParamOffset) +			{ +				llerrs << "Attempted to register param with block defined for parent class, make sure to derive from LLInitParam::Block<YOUR_CLASS, PARAM_BLOCK_BASE_CLASS>" << llendl; +			} + +			ParamDescriptorPtr param_descriptor = findParamDescriptor(param); +			if (param_descriptor) +			{ +				if (synonym.empty()) +				{ +					block_data.mUnnamedParams.push_back(param_descriptor); +				} +				else +				{ +					block_data.mNamedParams[synonym] = param_descriptor; +				} +			} +		} +	} + +	const std::string& BaseBlock::getParamName(const BlockDescriptor& block_data, const Param* paramp) const +	{ +		param_handle_t handle = getHandleFromParam(paramp); +		for (BlockDescriptor::param_map_t::const_iterator it = block_data.mNamedParams.begin(); it != block_data.mNamedParams.end(); ++it) +		{ +			if (it->second->mParamHandle == handle) +			{ +				return it->first; +			} +		} + +		return LLStringUtil::null; +	} + +	ParamDescriptorPtr BaseBlock::findParamDescriptor(const Param& param) +	{ +		param_handle_t handle = getHandleFromParam(¶m); +		BlockDescriptor& descriptor = mostDerivedBlockDescriptor(); +		BlockDescriptor::all_params_list_t::iterator end_it = descriptor.mAllParams.end(); +		for (BlockDescriptor::all_params_list_t::iterator it = descriptor.mAllParams.begin(); +			it != end_it; +			++it) +		{ +			if ((*it)->mParamHandle == handle) return *it; +		} +		return ParamDescriptorPtr(); +	} + +	// take all provided params from other and apply to self +	// NOTE: this requires that "other" is of the same derived type as this +	bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) +	{ +		bool some_param_changed = false; +		BlockDescriptor::all_params_list_t::const_iterator end_it = block_data.mAllParams.end(); +		for (BlockDescriptor::all_params_list_t::const_iterator it = block_data.mAllParams.begin(); +			it != end_it; +			++it) +		{ +			const Param* other_paramp = other.getParamFromHandle((*it)->mParamHandle); +			ParamDescriptor::merge_func_t merge_func = (*it)->mMergeFunc; +			if (merge_func) +			{ +				Param* paramp = getParamFromHandle((*it)->mParamHandle); +				llassert(paramp->mEnclosingBlockOffset == (*it)->mParamHandle); +				some_param_changed |= merge_func(*paramp, *other_paramp, overwrite); +			} +		} +		return some_param_changed; +	} +} diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h new file mode 100644 index 0000000000..beaf07e56b --- /dev/null +++ b/indra/llcommon/llinitparam.h @@ -0,0 +1,2294 @@ +/**  + * @file llinitparam.h + * @brief parameter block abstraction for creating complex objects and  + * parsing construction parameters from xml and LLSD + * + * $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$ + */ + +#ifndef LL_LLPARAM_H +#define LL_LLPARAM_H + +#include <vector> +#include <boost/function.hpp> +#include <boost/type_traits/is_convertible.hpp> +#include <boost/unordered_map.hpp> +#include <boost/shared_ptr.hpp> + +#include "llerror.h" + +namespace LLInitParam +{ +	// used to indicate no matching value to a given name when parsing +	struct Flag{}; + +	template<typename T> const T& defaultValue() { static T value; return value; } + +	template <typename T, bool IS_BOOST_FUNCTION = boost::is_convertible<T, boost::function_base>::value > +    struct ParamCompare  +	{ +    	static bool equals(const T &a, const T &b) +		{ +			return a == b; +		} +    }; + +	// boost function types are not comparable +	template<typename T> +	struct ParamCompare<T, true> +	{ +		static bool equals(const T&a, const T &b) +		{ +			return false; +		} +	}; + +	template<>  +	struct ParamCompare<LLSD, false> +	{ +		static bool equals(const LLSD &a, const LLSD &b) { return false; } +	}; + +	template<> +	struct ParamCompare<Flag, false> +	{ +		static bool equals(const Flag& a, const Flag& b) { return false; } +	}; + + +	// helper functions and classes +	typedef ptrdiff_t param_handle_t; + +	// empty default implementation of key cache +	// leverages empty base class optimization +	template <typename T> +	class TypeValues +	{ +	private: +		struct Inaccessable{}; +	public: +		typedef std::map<std::string, T> value_name_map_t; +		typedef Inaccessable name_t; + +		void setValueName(const std::string& key) {} +		std::string getValueName() const { return ""; } +		std::string calcValueName(const T& value) const { return ""; } +		void clearValueName() const {} + +		static bool getValueFromName(const std::string& name, T& value) +		{ +			return false; +		} + +		static bool valueNamesExist() +		{ +			return false; +		} + +		static std::vector<std::string>* getPossibleValues() +		{ +			return NULL; +		} + +		static value_name_map_t* getValueNames() {return NULL;} +	}; + +	template <typename T, typename DERIVED_TYPE = TypeValues<T> > +	class TypeValuesHelper +	{ +	public: +		typedef typename std::map<std::string, T> value_name_map_t; +		typedef std::string name_t; + +		//TODO: cache key by index to save on param block size +		void setValueName(const std::string& value_name)  +		{ +			mValueName = value_name;  +		} + +		std::string getValueName() const +		{  +			return mValueName;  +		} + +		std::string calcValueName(const T& value) const +		{ +			value_name_map_t* map = getValueNames(); +			for (typename value_name_map_t::iterator it = map->begin(), end_it = map->end(); +				it != end_it; +				++it) +			{ +				if (ParamCompare<T>::equals(it->second, value)) +				{ +					return it->first; +				} +			} + +			return ""; +		} + +		void clearValueName() const +		{ +			mValueName.clear(); +		} + +		static bool getValueFromName(const std::string& name, T& value) +		{ +			value_name_map_t* map = getValueNames(); +			typename value_name_map_t::iterator found_it = map->find(name); +			if (found_it == map->end()) return false; + +			value = found_it->second; +			return true; +		} + +		static bool valueNamesExist() +		{ +			return !getValueNames()->empty(); +		} +	 +		static value_name_map_t* getValueNames() +		{ +			static value_name_map_t sMap; +			static bool sInitialized = false; + +			if (!sInitialized) +			{ +				sInitialized = true; +				DERIVED_TYPE::declareValues(); +			} +			return &sMap; +		} + +		static std::vector<std::string>* getPossibleValues() +		{ +			static std::vector<std::string> sValues; + +			value_name_map_t* map = getValueNames(); +			for (typename value_name_map_t::iterator it = map->begin(), end_it = map->end(); +				 it != end_it; +				 ++it) +			{ +				sValues.push_back(it->first); +			} +			return &sValues; +		} + +		static void declare(const std::string& name, const T& value) +		{ +			(*getValueNames())[name] = value; +		} + +	protected: +		static void getName(const std::string& name, const T& value) +		{} + +		mutable std::string	mValueName; +	}; + +	class LL_COMMON_API Parser +	{ +		LOG_CLASS(Parser); + +	public: +		 +		struct CompareTypeID +		{ +			bool operator()(const std::type_info* lhs, const std::type_info* rhs) const +			{ +				return lhs->before(*rhs); +			} +		}; + +		typedef std::vector<std::pair<std::string, bool> >					name_stack_t; +		typedef std::pair<name_stack_t::iterator, name_stack_t::iterator>	name_stack_range_t; +		typedef std::vector<std::string>									possible_values_t; + +		typedef bool (*parser_read_func_t)(Parser& parser, void* output); +		typedef bool (*parser_write_func_t)(Parser& parser, const void*, name_stack_t&); +		typedef boost::function<void (name_stack_t&, S32, S32, const possible_values_t*)>	parser_inspect_func_t; + +		typedef std::map<const std::type_info*, parser_read_func_t, CompareTypeID>		parser_read_func_map_t; +		typedef std::map<const std::type_info*, parser_write_func_t, CompareTypeID>		parser_write_func_map_t; +		typedef std::map<const std::type_info*, parser_inspect_func_t, CompareTypeID>	parser_inspect_func_map_t; + +		Parser(parser_read_func_map_t& read_map, parser_write_func_map_t& write_map, parser_inspect_func_map_t& inspect_map) +		:	mParseSilently(false), +			mParserReadFuncs(&read_map), +			mParserWriteFuncs(&write_map), +			mParserInspectFuncs(&inspect_map) +		{} +		virtual ~Parser(); + +		template <typename T> bool readValue(T& param) +	    { +		    parser_read_func_map_t::iterator found_it = mParserReadFuncs->find(&typeid(T)); +		    if (found_it != mParserReadFuncs->end()) +		    { +			    return found_it->second(*this, (void*)¶m); +		    } +		    return false; +	    } + +		template <typename T> bool writeValue(const T& param, name_stack_t& name_stack) +		{ +		    parser_write_func_map_t::iterator found_it = mParserWriteFuncs->find(&typeid(T)); +		    if (found_it != mParserWriteFuncs->end()) +		    { +			    return found_it->second(*this, (const void*)¶m, name_stack); +		    } +		    return false; +		} + +		// dispatch inspection to registered inspection functions, for each parameter in a param block +		template <typename T> bool inspectValue(name_stack_t& name_stack, S32 min_count, S32 max_count, const possible_values_t* possible_values) +		{ +		    parser_inspect_func_map_t::iterator found_it = mParserInspectFuncs->find(&typeid(T)); +		    if (found_it != mParserInspectFuncs->end()) +		    { +			    found_it->second(name_stack, min_count, max_count, possible_values); +				return true; +		    } +			return false; +		} + +		virtual std::string getCurrentElementName() = 0; +		virtual void parserWarning(const std::string& message); +		virtual void parserError(const std::string& message); +		void setParseSilently(bool silent) { mParseSilently = silent; } + +	protected: +		template <typename T> +		void registerParserFuncs(parser_read_func_t read_func, parser_write_func_t write_func = NULL) +		{ +			mParserReadFuncs->insert(std::make_pair(&typeid(T), read_func)); +			mParserWriteFuncs->insert(std::make_pair(&typeid(T), write_func)); +		} + +		template <typename T> +		void registerInspectFunc(parser_inspect_func_t inspect_func) +		{ +			mParserInspectFuncs->insert(std::make_pair(&typeid(T), inspect_func)); +		} + +		bool				mParseSilently; + +	private: +		parser_read_func_map_t*		mParserReadFuncs; +		parser_write_func_map_t*	mParserWriteFuncs; +		parser_inspect_func_map_t*	mParserInspectFuncs; +	}; + +	class Param; + +	// various callbacks and constraints associated with an individual param +	struct LL_COMMON_API ParamDescriptor +	{ +		struct UserData +		{ +			virtual ~UserData() {} +		}; + +		typedef bool(*merge_func_t)(Param&, const Param&, bool); +		typedef bool(*deserialize_func_t)(Param&, Parser&, const Parser::name_stack_range_t&, bool); +		typedef void(*serialize_func_t)(const Param&, Parser&, Parser::name_stack_t&, const Param* diff_param); +		typedef void(*inspect_func_t)(const Param&, Parser&, Parser::name_stack_t&, S32 min_count, S32 max_count); +		typedef bool(*validation_func_t)(const Param*); + +		ParamDescriptor(param_handle_t p,  +						merge_func_t merge_func,  +						deserialize_func_t deserialize_func,  +						serialize_func_t serialize_func, +						validation_func_t validation_func, +						inspect_func_t inspect_func, +						S32 min_count, +						S32 max_count); + +		ParamDescriptor(); +		~ParamDescriptor(); + +		param_handle_t		mParamHandle; +		merge_func_t		mMergeFunc; +		deserialize_func_t	mDeserializeFunc; +		serialize_func_t	mSerializeFunc; +		inspect_func_t		mInspectFunc; +		validation_func_t	mValidationFunc; +		S32					mMinCount; +		S32					mMaxCount; +		S32					mNumRefs; +		UserData*			mUserData; +	}; + +	typedef boost::shared_ptr<ParamDescriptor> ParamDescriptorPtr; + +	// each derived Block class keeps a static data structure maintaining offsets to various params +	class LL_COMMON_API BlockDescriptor +	{ +	public: +		BlockDescriptor(); + +		typedef enum e_initialization_state +		{ +			UNINITIALIZED, +			INITIALIZING, +			INITIALIZED +		} EInitializationState; + +		void aggregateBlockData(BlockDescriptor& src_block_data); + +		typedef boost::unordered_map<const std::string, ParamDescriptorPtr>						param_map_t;  +		typedef std::vector<ParamDescriptorPtr>													param_list_t;  +		typedef std::list<ParamDescriptorPtr>													all_params_list_t; +		typedef std::vector<std::pair<param_handle_t, ParamDescriptor::validation_func_t> >		param_validation_list_t; + +		param_map_t						mNamedParams;			// parameters with associated names +		param_list_t					mUnnamedParams;			// parameters with_out_ associated names +		param_validation_list_t			mValidationList;		// parameters that must be validated +		all_params_list_t				mAllParams;				// all parameters, owns descriptors +		size_t							mMaxParamOffset; +		EInitializationState			mInitializationState;	// whether or not static block data has been initialized +		class BaseBlock*				mCurrentBlockPtr;		// pointer to block currently being constructed +	}; + +	class LL_COMMON_API BaseBlock +	{ +	public: +		//TODO: implement in terms of owned_ptr +		template<typename T> +		class Lazy +		{ +		public: +			Lazy() +				: mPtr(NULL) +			{} + +			~Lazy() +			{ +				delete mPtr; +			} + +			Lazy(const Lazy& other) +			{ +				if (other.mPtr) +				{ +					mPtr = new T(*other.mPtr); +				} +				else +				{ +					mPtr = NULL; +				} +			} + +			Lazy<T>& operator = (const Lazy<T>& other) +			{ +				if (other.mPtr) +				{ +					mPtr = new T(*other.mPtr); +				} +				else +				{ +					mPtr = NULL; +				} +				return *this; +			} + +			bool empty() const +			{ +				return mPtr == NULL; +			} + +			void set(const T& other) +			{ +				delete mPtr; +				mPtr = new T(other); +			} + +			const T& get() const +			{ +				return ensureInstance(); +			} + +			T& get() +			{ +				return ensureInstance(); +			} + +		private: +			// lazily allocate an instance of T +			T* ensureInstance() const +			{ +				if (mPtr == NULL) +				{ +					mPtr = new T(); +				} +				return mPtr; +			} + +		private: +			// if you get a compilation error with this, that means you are using a forward declared struct for T +			// unfortunately, the type traits we rely on don't work with forward declared typed +			//static const int dummy = sizeof(T); + +			mutable T* mPtr; +		}; + +		// "Multiple" constraint types, put here in root class to avoid ambiguity during use +		struct AnyAmount +		{ +			enum { minCount = 0 }; +			enum { maxCount = U32_MAX }; +		}; + +		template<U32 MIN_AMOUNT> +		struct AtLeast +		{ +			enum { minCount = MIN_AMOUNT }; +			enum { maxCount = U32_MAX }; +		}; + +		template<U32 MAX_AMOUNT> +		struct AtMost +		{ +			enum { minCount = 0 }; +			enum { maxCount = MAX_AMOUNT }; +		}; + +		template<U32 MIN_AMOUNT, U32 MAX_AMOUNT> +		struct Between +		{ +			enum { minCount = MIN_AMOUNT }; +			enum { maxCount = MAX_AMOUNT }; +		}; + +		template<U32 EXACT_COUNT> +		struct Exactly +		{ +			enum { minCount = EXACT_COUNT }; +			enum { maxCount = EXACT_COUNT }; +		}; + +		// this typedef identifies derived classes as being blocks +		typedef void baseblock_base_class_t; +		LOG_CLASS(BaseBlock); +		friend class Param; + +		virtual ~BaseBlock() {} +		bool submitValue(Parser::name_stack_t& name_stack, Parser& p, bool silent=false); + +		param_handle_t getHandleFromParam(const Param* param) const; +		bool validateBlock(bool emit_errors = true) const; + +		Param* getParamFromHandle(const param_handle_t param_handle) +		{ +			if (param_handle == 0) return NULL; + +			U8* baseblock_address = reinterpret_cast<U8*>(this); +			return reinterpret_cast<Param*>(baseblock_address + param_handle); +		} + +		const Param* getParamFromHandle(const param_handle_t param_handle) const +		{ +			const U8* baseblock_address = reinterpret_cast<const U8*>(this); +			return reinterpret_cast<const Param*>(baseblock_address + param_handle); +		} + +		void addSynonym(Param& param, const std::string& synonym); + +		// Blocks can override this to do custom tracking of changes +		virtual void paramChanged(const Param& changed_param, bool user_provided) {} + +		bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); +		void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const; +		bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const; + +		virtual const BlockDescriptor& mostDerivedBlockDescriptor() const { return selfBlockDescriptor(); } +		virtual BlockDescriptor& mostDerivedBlockDescriptor() { return selfBlockDescriptor(); } + +		// take all provided params from other and apply to self +		bool overwriteFrom(const BaseBlock& other) +		{ +			return false; +		} + +		// take all provided params that are not already provided, and apply to self +		bool fillFrom(const BaseBlock& other) +		{ +			return false; +		} + +		static void addParam(BlockDescriptor& block_data, ParamDescriptorPtr param, const char* name); + +		ParamDescriptorPtr findParamDescriptor(const Param& param); + +	protected: +		void init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size); + + +		bool mergeBlockParam(bool source_provided, bool dst_provided, BlockDescriptor& block_data, const BaseBlock& source, bool overwrite) +		{ +			return mergeBlock(block_data, source, overwrite); +		} +		// take all provided params from other and apply to self +		bool mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite); + +		static BlockDescriptor& selfBlockDescriptor() +		{ +			static BlockDescriptor sBlockDescriptor; +			return sBlockDescriptor; +		} + +	private: +		const std::string& getParamName(const BlockDescriptor& block_data, const Param* paramp) const; +	}; + +	template<typename T> +	struct ParamCompare<BaseBlock::Lazy<T>, false > +	{ +		static bool equals(const BaseBlock::Lazy<T>& a, const BaseBlock::Lazy<T>& b) { return !a.empty() || !b.empty(); } +	}; + +	class LL_COMMON_API Param +	{ +	public: +		void setProvided(bool is_provided = true) +		{ +			mIsProvided = is_provided; +			enclosingBlock().paramChanged(*this, is_provided); +		} + +		Param& operator =(const Param& other) +		{ +			mIsProvided = other.mIsProvided; +			// don't change mEnclosingblockoffset +			return *this; +		} +	protected: + +		bool anyProvided() const { return mIsProvided; } + +		Param(BaseBlock* enclosing_block); + +		// store pointer to enclosing block as offset to reduce space and allow for quick copying +		BaseBlock& enclosingBlock() const +		{  +			const U8* my_addr = reinterpret_cast<const U8*>(this); +			// get address of enclosing BLOCK class using stored offset to enclosing BaseBlock class +			return *const_cast<BaseBlock*> +				(reinterpret_cast<const BaseBlock*> +					(my_addr - (ptrdiff_t)(S32)mEnclosingBlockOffset)); +		} + +	private: +		friend class BaseBlock; + +		U32		mEnclosingBlockOffset:31; +		U32		mIsProvided:1; + +	}; + +	// these templates allow us to distinguish between template parameters +	// that derive from BaseBlock and those that don't +	template<typename T, typename Void = void> +	struct IsBlock +	{ +		static const bool value = false; +		struct EmptyBase {}; +		typedef EmptyBase base_class_t; +	}; + +	template<typename T> +	struct IsBlock<T, typename T::baseblock_base_class_t> +	{ +		static const bool value = true; +		typedef BaseBlock base_class_t; +	}; + +	template<typename T> +	struct IsBlock<BaseBlock::Lazy<T>, typename T::baseblock_base_class_t > +	{ +		static const bool value = true; +		typedef BaseBlock base_class_t; +	}; + +	template<typename T, typename NAME_VALUE_LOOKUP, bool VALUE_IS_BLOCK = IsBlock<T>::value> +	class ParamValue : public NAME_VALUE_LOOKUP +	{ +	public: +		typedef const T&							value_assignment_t; +		typedef T									value_t; +		typedef ParamValue<T, NAME_VALUE_LOOKUP, VALUE_IS_BLOCK>	self_t; + +		ParamValue(): mValue() {} +		ParamValue(value_assignment_t other) : mValue(other) {} + +		void setValue(value_assignment_t val) +		{ +			mValue = val; +		} + +		value_assignment_t getValue() const +		{ +			return mValue; +		} + +		T& getValue() +		{ +			return mValue; +		} + +		operator value_assignment_t() const +		{ +			return mValue; +		} + +		value_assignment_t operator()() const +		{ +			return mValue; +		} + +		void operator ()(const typename NAME_VALUE_LOOKUP::name_t& name) +		{ +			*this = name; +		} + +		self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) +		{ +			if (NAME_VALUE_LOOKUP::getValueFromName(name, mValue)) +			{ +				setValueName(name); +			} + +			return *this; +		} + +	protected: +		T mValue; +	}; + +	template<typename T, typename NAME_VALUE_LOOKUP> +	class ParamValue<T, NAME_VALUE_LOOKUP, true>  +	:	public T, +		public NAME_VALUE_LOOKUP +	{ +	public: +		typedef const T&							value_assignment_t; +		typedef T									value_t; +		typedef ParamValue<T, NAME_VALUE_LOOKUP, true>	self_t; + +		ParamValue()  +		:	T(), +			mValidated(false) +		{} + +		ParamValue(value_assignment_t other) +		:	T(other), +			mValidated(false) +		{} + +		void setValue(value_assignment_t val) +		{ +			*this = val; +		} + +		value_assignment_t getValue() const +		{ +			return *this; +		} + +		T& getValue() +		{ +			return *this; +		} + +		operator value_assignment_t() const +		{ +			return *this; +		} +		 +		value_assignment_t operator()() const +		{ +			return *this; +		} + +		void operator ()(const typename NAME_VALUE_LOOKUP::name_t& name) +		{ +			*this = name; +		} + +		self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) +		{ +			if (NAME_VALUE_LOOKUP::getValueFromName(name, *this)) +			{ +				setValueName(name); +			} + +			return *this; +		} + +	protected: +		mutable bool 	mValidated; // lazy validation flag +	}; + +	template<typename NAME_VALUE_LOOKUP> +	class ParamValue<std::string, NAME_VALUE_LOOKUP, false> +	: public NAME_VALUE_LOOKUP +	{ +	public: +		typedef const std::string&	value_assignment_t; +		typedef std::string			value_t; +		typedef ParamValue<std::string, NAME_VALUE_LOOKUP, false>	self_t; + +		ParamValue(): mValue() {} +		ParamValue(value_assignment_t other) : mValue(other) {} + +		void setValue(value_assignment_t val) +		{ +			if (NAME_VALUE_LOOKUP::getValueFromName(val, mValue)) +			{ +				NAME_VALUE_LOOKUP::setValueName(val); +			} +			else +			{ +				mValue = val; +			} +		} + +		value_assignment_t getValue() const +		{ +			return mValue; +		} + +		std::string& getValue() +		{ +			return mValue; +		} + +		operator value_assignment_t() const +		{ +			return mValue; +		} + +		value_assignment_t operator()() const +		{ +			return mValue; +		} + +	protected: +		std::string mValue; +	}; + + +	template<typename T, typename NAME_VALUE_LOOKUP = TypeValues<T> > +	struct ParamIterator +	{ +		typedef typename std::vector<ParamValue<T, NAME_VALUE_LOOKUP> >::const_iterator		const_iterator; +		typedef typename std::vector<ParamValue<T, NAME_VALUE_LOOKUP> >::iterator			iterator; +	}; + +	// specialize for custom parsing/decomposition of specific classes +	// e.g. TypedParam<LLRect> has left, top, right, bottom, etc... +	template<typename	T, +			typename	NAME_VALUE_LOOKUP = TypeValues<T>, +			bool		HAS_MULTIPLE_VALUES = false, +			bool		VALUE_IS_BLOCK = IsBlock<ParamValue<T, NAME_VALUE_LOOKUP> >::value> +	class TypedParam  +	:	public Param,  +		public ParamValue<T, NAME_VALUE_LOOKUP> +	{ +	public: +		typedef	TypedParam<T, NAME_VALUE_LOOKUP, HAS_MULTIPLE_VALUES, VALUE_IS_BLOCK>		self_t; +		typedef ParamValue<T, NAME_VALUE_LOOKUP>											param_value_t; +		typedef typename param_value_t::value_assignment_t				value_assignment_t; +		typedef NAME_VALUE_LOOKUP															name_value_lookup_t; + +		using param_value_t::operator(); + +		TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count)  +		:	Param(block_descriptor.mCurrentBlockPtr) +		{ +			if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) +			{ + 				ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( +												block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +												&mergeWith, +												&deserializeParam, +												&serializeParam, +												validate_func, +												&inspectParam, +												min_count, max_count)); +				BaseBlock::addParam(block_descriptor, param_descriptor, name); +			} + +			setValue(value); +		}  + +		bool isProvided() const { return Param::anyProvided(); } + +		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) +		{  +			self_t& typed_param = static_cast<self_t&>(param); +			// no further names in stack, attempt to parse value now +			if (name_stack_range.first == name_stack_range.second) +			{ +				if (parser.readValue(typed_param.getValue())) +				{ +					typed_param.clearValueName(); +					typed_param.setProvided(); +					return true; +				} +				 +				// try to parse a known named value +				if(name_value_lookup_t::valueNamesExist()) +				{ +					// try to parse a known named value +					std::string name; +					if (parser.readValue(name)) +					{ +						// try to parse a per type named value +						if (name_value_lookup_t::getValueFromName(name, typed_param.getValue())) +						{ +							typed_param.setValueName(name); +							typed_param.setProvided(); +							return true; +						} + +					} +				} +			} +			return false; +		} + +		static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) +		{ +			const self_t& typed_param = static_cast<const self_t&>(param); +			if (!typed_param.isProvided()) return; + +			if (!name_stack.empty()) +			{ +				name_stack.back().second = true; +			} + +			std::string key = typed_param.getValueName(); + +			// first try to write out name of name/value pair + +			if (!key.empty()) +			{ +				if (!diff_param || !ParamCompare<std::string>::equals(static_cast<const self_t*>(diff_param)->getValueName(), key)) +				{ +					parser.writeValue(key, name_stack); +				} +			} +			// then try to serialize value directly +			else if (!diff_param || !ParamCompare<T>::equals(typed_param.getValue(), static_cast<const self_t*>(diff_param)->getValue())) +			{ +				if (!parser.writeValue(typed_param.getValue(), name_stack))  +				{ +					std::string calculated_key = typed_param.calcValueName(typed_param.getValue()); +					if (!diff_param || !ParamCompare<std::string>::equals(static_cast<const self_t*>(diff_param)->getValueName(), calculated_key)) +					{ +						parser.writeValue(calculated_key, name_stack); +					} +				} +			} +		} + +		static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) +		{ +			// tell parser about our actual type +			parser.inspectValue<T>(name_stack, min_count, max_count, NULL); +			// then tell it about string-based alternatives ("red", "blue", etc. for LLColor4) +			if (name_value_lookup_t::getPossibleValues()) +			{ +				parser.inspectValue<std::string>(name_stack, min_count, max_count, name_value_lookup_t::getPossibleValues()); +			} +		} + +		void set(value_assignment_t val, bool flag_as_provided = true) +		{ +			param_value_t::clearValueName(); +			setValue(val); +			setProvided(flag_as_provided); +		} + +		self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) +		{ +			return static_cast<self_t&>(param_value_t::operator =(name)); +		} + +	protected: + +		self_t& operator =(const self_t& other) +		{ +			param_value_t::operator =(other); +			Param::operator =(other); +			return *this; +		} + +		static bool mergeWith(Param& dst, const Param& src, bool overwrite) +		{ +			const self_t& src_typed_param = static_cast<const self_t&>(src); +			self_t& dst_typed_param = static_cast<self_t&>(dst); + +			if (src_typed_param.isProvided() +				&& (overwrite || !dst_typed_param.isProvided())) +			{ +				dst_typed_param.set(src_typed_param.getValue()); +				return true; +			} +			return false; +		} +	}; + +	// parameter that is a block +	template <typename T, typename NAME_VALUE_LOOKUP> +	class TypedParam<T, NAME_VALUE_LOOKUP, false, true>  +	:	public Param, +		public ParamValue<T, NAME_VALUE_LOOKUP> +	{ +	public: +		typedef ParamValue<T, NAME_VALUE_LOOKUP>				param_value_t; +		typedef typename param_value_t::value_assignment_t		value_assignment_t; +		typedef TypedParam<T, NAME_VALUE_LOOKUP, false, true>	self_t; +		typedef NAME_VALUE_LOOKUP								name_value_lookup_t; + +		using param_value_t::operator(); + +		TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count) +		:	Param(block_descriptor.mCurrentBlockPtr), +			param_value_t(value) +		{ +			if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) +			{ +				ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( +												block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +												&mergeWith, +												&deserializeParam, +												&serializeParam, +												validate_func,  +												&inspectParam, +												min_count, max_count)); +				BaseBlock::addParam(block_descriptor, param_descriptor, name); +			} +		} + +		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) +		{  +			self_t& typed_param = static_cast<self_t&>(param); +			// attempt to parse block... +			if(typed_param.deserializeBlock(parser, name_stack_range, new_name)) +			{ +				typed_param.clearValueName(); +				typed_param.setProvided(); +				return true; +			} + +			if(name_value_lookup_t::valueNamesExist()) +			{ +				// try to parse a known named value +				std::string name; +				if (parser.readValue(name)) +				{ +					// try to parse a per type named value +					if (name_value_lookup_t::getValueFromName(name, typed_param.getValue())) +					{ +						typed_param.setValueName(name); +						typed_param.setProvided(); +						return true; +					} + +				} +			} +			return false; +		} + +		static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) +		{ +			const self_t& typed_param = static_cast<const self_t&>(param); +			if (!typed_param.isProvided()) return; + +			if (!name_stack.empty()) +			{ +				name_stack.back().second = true; +			} + +			std::string key = typed_param.getValueName(); +			if (!key.empty()) +			{ +				if (!parser.writeValue(key, name_stack)) +				{ +					return; +				} +			} +			else +			{ +				typed_param.serializeBlock(parser, name_stack, static_cast<const self_t*>(diff_param)); +			} +		} + +		static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) +		{ +			// I am a param that is also a block, so just recurse into my contents +			const self_t& typed_param = static_cast<const self_t&>(param); +			typed_param.inspectBlock(parser, name_stack, min_count, max_count); +		} + +		// a param-that-is-a-block is provided when the user has set one of its child params +		// *and* the block as a whole validates +		bool isProvided() const  +		{  +			// only validate block when it hasn't already passed validation with current data +			if (Param::anyProvided() && !param_value_t::mValidated) +			{ +				// a sub-block is "provided" when it has been filled in enough to be valid +				param_value_t::mValidated = param_value_t::validateBlock(false); +			} +			return Param::anyProvided() && param_value_t::mValidated; +		} + +		// assign block contents to this param-that-is-a-block +		void set(value_assignment_t val, bool flag_as_provided = true) +		{ +			setValue(val); +			param_value_t::clearValueName(); +			// force revalidation of block +			// next call to isProvided() will update provision status based on validity +			param_value_t::mValidated = false; +			setProvided(flag_as_provided); +		} + +		self_t& operator =(const typename NAME_VALUE_LOOKUP::name_t& name) +		{ +			return static_cast<self_t&>(param_value_t::operator =(name)); +		} + +		// propagate changed status up to enclosing block +		/*virtual*/ void paramChanged(const Param& changed_param, bool user_provided) +		{  +			param_value_t::paramChanged(changed_param, user_provided); +			if (user_provided) +			{ +				// a child param has been explicitly changed +				// so *some* aspect of this block is now provided +				param_value_t::mValidated = false; +				setProvided(); +				param_value_t::clearValueName(); +			} +			else +			{ +				Param::enclosingBlock().paramChanged(*this, user_provided); +			} +		} + +	protected: + +		self_t& operator =(const self_t& other) +		{ +			param_value_t::operator =(other); +			Param::operator =(other); +			return *this; +		} + +		static bool mergeWith(Param& dst, const Param& src, bool overwrite) +		{ +			const self_t& src_typed_param = static_cast<const self_t&>(src); +			self_t& dst_typed_param = static_cast<self_t&>(dst); + +			if (src_typed_param.anyProvided()) +			{ +				if (dst_typed_param.mergeBlockParam(src_typed_param.isProvided(), dst_typed_param.isProvided(), param_value_t::selfBlockDescriptor(), src_typed_param, overwrite)) +				{ +					dst_typed_param.clearValueName(); +					dst_typed_param.setProvided(true); +					return true; +				} +			} +			return false; +		} +	}; + +	// container of non-block parameters +	template <typename VALUE_TYPE, typename NAME_VALUE_LOOKUP> +	class TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, false>  +	:	public Param +	{ +	public: +		typedef TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, false>		self_t; +		typedef ParamValue<VALUE_TYPE, NAME_VALUE_LOOKUP>					param_value_t; +		typedef typename std::vector<param_value_t>							container_t; +		typedef const container_t&											value_assignment_t; + +		typedef typename param_value_t::value_t								value_t; +		typedef NAME_VALUE_LOOKUP											name_value_lookup_t; +		 +		TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count)  +		:	Param(block_descriptor.mCurrentBlockPtr) +		{ +			std::copy(value.begin(), value.end(), std::back_inserter(mValues)); + +			if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) +			{ +				ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( +												block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +												&mergeWith, +												&deserializeParam, +												&serializeParam, +												validate_func, +												&inspectParam, +												min_count, max_count)); +				BaseBlock::addParam(block_descriptor, param_descriptor, name); +			} +		}  + +		bool isProvided() const { return Param::anyProvided(); } + +		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) +		{  +			self_t& typed_param = static_cast<self_t&>(param); +			value_t value; +			// no further names in stack, attempt to parse value now +			if (name_stack_range.first == name_stack_range.second) +			{ +				// attempt to read value directly +				if (parser.readValue(value)) +				{ +					typed_param.add(value); +					return true; +				} +				 +				// try to parse a known named value +				if(name_value_lookup_t::valueNamesExist()) +				{ +					// try to parse a known named value +					std::string name; +					if (parser.readValue(name)) +					{ +						// try to parse a per type named value +						if (name_value_lookup_t::getValueFromName(name, value)) +						{ +							typed_param.add(value); +							typed_param.mValues.back().setValueName(name); +							return true; +						} + +					} +				} +			} +			return false; +		} + +		static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) +		{ +			const self_t& typed_param = static_cast<const self_t&>(param); +			if (!typed_param.isProvided() || name_stack.empty()) return; + +			for (const_iterator it = typed_param.mValues.begin(), end_it = typed_param.mValues.end(); +				it != end_it; +				++it) +			{ +				std::string key = it->getValueName(); +				name_stack.back().second = true; + +				if(key.empty()) +				// not parsed via name values, write out value directly +				{ +					bool value_written = parser.writeValue(*it, name_stack); +					if (!value_written) +					{ +						std::string calculated_key = it->calcValueName(it->getValue()); +						if (!parser.writeValue(calculated_key, name_stack)) +						{ +							break; +						} +					} +				} +				else  +				{ +					if(!parser.writeValue(key, name_stack)) +					{ +						break; +					} +				} +			} +		} + +		static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) +		{ +			parser.inspectValue<VALUE_TYPE>(name_stack, min_count, max_count, NULL); +			if (name_value_lookup_t::getPossibleValues()) +			{ +				parser.inspectValue<std::string>(name_stack, min_count, max_count, name_value_lookup_t::getPossibleValues()); +			} +		} + +		void set(value_assignment_t val, bool flag_as_provided = true) +		{ +			mValues = val; +			setProvided(flag_as_provided); +		} + +		param_value_t& add() +		{ +			mValues.push_back(param_value_t(value_t())); +			Param::setProvided(); +			return mValues.back(); +		} + +		void add(const value_t& item) +		{ +			param_value_t param_value; +			param_value.setValue(item); +			mValues.push_back(param_value); +			setProvided(); +		} + +		void add(const typename name_value_lookup_t::name_t& name) +		{ +			value_t value; + +			// try to parse a per type named value +			if (name_value_lookup_t::getValueFromName(name, value)) +			{ +				add(value); +				mValues.back().setValueName(name); +			} +		} + +		// implicit conversion +		operator value_assignment_t() const { return mValues; }  +		// explicit conversion		 +		value_assignment_t operator()() const { return mValues; } + +		typedef typename container_t::iterator iterator; +		typedef typename container_t::const_iterator const_iterator; +		iterator begin() { return mValues.begin(); } +		iterator end() { return mValues.end(); } +		const_iterator begin() const { return mValues.begin(); } +		const_iterator end() const { return mValues.end(); } +		bool empty() const { return mValues.empty(); } +		size_t size() const { return mValues.size(); } + +		U32 numValidElements() const +		{ +			return mValues.size(); +		} + +	protected: +		static bool mergeWith(Param& dst, const Param& src, bool overwrite) +		{ +			const self_t& src_typed_param = static_cast<const self_t&>(src); +			self_t& dst_typed_param = static_cast<self_t&>(dst); + +			if (overwrite) +			{ +				std::copy(src_typed_param.begin(), src_typed_param.end(), std::back_inserter(dst_typed_param.mValues)); +			} +			else +			{ +				container_t new_values(src_typed_param.mValues); +				std::copy(dst_typed_param.begin(), dst_typed_param.end(), std::back_inserter(new_values)); +				std::swap(dst_typed_param.mValues, new_values); +			} + +			if (src_typed_param.begin() != src_typed_param.end()) +			{ +				dst_typed_param.setProvided(); +			} +			return true; +		} + +		container_t		mValues; +	}; + +	// container of block parameters +	template <typename VALUE_TYPE, typename NAME_VALUE_LOOKUP> +	class TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, true>  +	:	public Param +	{ +	public: +		typedef TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, true>	self_t; +		typedef ParamValue<VALUE_TYPE, NAME_VALUE_LOOKUP>				param_value_t; +		typedef typename std::vector<param_value_t>						container_t; +		typedef const container_t&										value_assignment_t; +		typedef typename param_value_t::value_t							value_t; +		typedef NAME_VALUE_LOOKUP										name_value_lookup_t; + +		TypedParam(BlockDescriptor& block_descriptor, const char* name, value_assignment_t value, ParamDescriptor::validation_func_t validate_func, S32 min_count, S32 max_count)  +		:	Param(block_descriptor.mCurrentBlockPtr) +		{ +			std::copy(value.begin(), value.end(), back_inserter(mValues)); + +			if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) +			{ +				ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( +												block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +												&mergeWith, +												&deserializeParam, +												&serializeParam, +												validate_func, +												&inspectParam, +												min_count, max_count)); +				BaseBlock::addParam(block_descriptor, param_descriptor, name); +			} +		}  + +		bool isProvided() const { return Param::anyProvided(); } + +		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name)  +		{  +			self_t& typed_param = static_cast<self_t&>(param); +			bool new_value = false; + +			if (new_name || typed_param.mValues.empty()) +			{ +				new_value = true; +				typed_param.mValues.push_back(value_t()); +			} + +			param_value_t& value = typed_param.mValues.back(); + +			// attempt to parse block... +			if(value.deserializeBlock(parser, name_stack_range, new_name)) +			{ +				typed_param.setProvided(); +				return true; +			} +			else if(name_value_lookup_t::valueNamesExist()) +			{ +				// try to parse a known named value +				std::string name; +				if (parser.readValue(name)) +				{ +					// try to parse a per type named value +					if (name_value_lookup_t::getValueFromName(name, value.getValue())) +					{ +						typed_param.mValues.back().setValueName(name); +						typed_param.setProvided(); +						return true; +					} + +				} +			} + +			if (new_value) +			{	// failed to parse new value, pop it off +				typed_param.mValues.pop_back(); +			} + +			return false; +		} + +		static void serializeParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, const Param* diff_param) +		{ +			const self_t& typed_param = static_cast<const self_t&>(param); +			if (!typed_param.isProvided() || name_stack.empty()) return; + +			for (const_iterator it = typed_param.mValues.begin(), end_it = typed_param.mValues.end(); +				it != end_it; +				++it) +			{ +				name_stack.back().second = true; + +				std::string key = it->getValueName(); +				if (!key.empty()) +				{ +					parser.writeValue(key, name_stack); +				} +				// Not parsed via named values, write out value directly +				// NOTE: currently we don't worry about removing default values in Multiple +				else  +				{ +					it->serializeBlock(parser, name_stack, NULL); +				} +			} +		} + +		static void inspectParam(const Param& param, Parser& parser, Parser::name_stack_t& name_stack, S32 min_count, S32 max_count) +		{ +			// I am a vector of blocks, so describe my contents recursively +			param_value_t(value_t()).inspectBlock(parser, name_stack, min_count, max_count); +		} + +		void set(value_assignment_t val, bool flag_as_provided = true) +		{ +			mValues = val; +			setProvided(flag_as_provided); +		} + +		param_value_t& add() +		{ +			mValues.push_back(value_t()); +			setProvided(); +			return mValues.back(); +		} + +		void add(const value_t& item) +		{ +			mValues.push_back(item); +			setProvided(); +		} + +		void add(const typename name_value_lookup_t::name_t& name) +		{ +			value_t value; + +			// try to parse a per type named value +			if (name_value_lookup_t::getValueFromName(name, value)) +			{ +				add(value); +				mValues.back().setValueName(name); +			} +		} + +		// implicit conversion +		operator value_assignment_t() const { return mValues; }  +		// explicit conversion +		value_assignment_t operator()() const { return mValues; } + +		typedef typename container_t::iterator iterator; +		typedef typename container_t::const_iterator const_iterator; +		iterator begin() { return mValues.begin(); } +		iterator end() { return mValues.end(); } +		const_iterator begin() const { return mValues.begin(); } +		const_iterator end() const { return mValues.end(); } +		bool empty() const { return mValues.empty(); } +		size_t size() const { return mValues.size(); } + +		U32 numValidElements() const +		{ +			U32 count = 0; +			for (const_iterator it = mValues.begin(), end_it = mValues.end(); +				it != end_it; +				++it) +			{ +				if(it->validateBlock(false)) count++; +			} +			return count; +		} + +	protected: + +		static bool mergeWith(Param& dst, const Param& src, bool overwrite) +		{ +			const self_t& src_typed_param = static_cast<const self_t&>(src); +			self_t& dst_typed_param = static_cast<self_t&>(dst); + +			if (overwrite) +			{ +				std::copy(src_typed_param.begin(), src_typed_param.end(), std::back_inserter(dst_typed_param.mValues)); +			} +			else +			{ +				container_t new_values(src_typed_param.mValues); +				std::copy(dst_typed_param.begin(), dst_typed_param.end(), std::back_inserter(new_values)); +				std::swap(dst_typed_param.mValues, new_values); +			} + +			if (src_typed_param.begin() != src_typed_param.end()) +			{ +				dst_typed_param.setProvided(); +			} + +			return true; +		} + +		container_t			mValues; +	}; + +	template <typename DERIVED_BLOCK, typename BASE_BLOCK = BaseBlock> +	class ChoiceBlock : public BASE_BLOCK +	{ +		typedef ChoiceBlock<DERIVED_BLOCK, BASE_BLOCK>	self_t; +		typedef ChoiceBlock<DERIVED_BLOCK, BASE_BLOCK>	enclosing_block_t; +		typedef BASE_BLOCK								base_block_t; +		 +		LOG_CLASS(self_t); +	public: +		// take all provided params from other and apply to self +		bool overwriteFrom(const self_t& other) +		{ +			return static_cast<DERIVED_BLOCK*>(this)->mergeBlock(selfBlockDescriptor(), other, true); +		} + +		// take all provided params that are not already provided, and apply to self +		bool fillFrom(const self_t& other) +		{ +			return static_cast<DERIVED_BLOCK*>(this)->mergeBlock(selfBlockDescriptor(), other, false); +		} + +		bool mergeBlockParam(bool source_provided, bool dest_provided, BlockDescriptor& block_data, const self_t& source, bool overwrite) +		{ +			bool source_override = source_provided && (overwrite || !dest_provided); + +			if (source_override || source.mCurChoice == mCurChoice) +			{ +				return mergeBlock(block_data, source, overwrite); +			} +			return false; +		} + +		// merge with other block +		bool mergeBlock(BlockDescriptor& block_data, const self_t& other, bool overwrite) +		{ +			mCurChoice = other.mCurChoice; +			return base_block_t::mergeBlock(selfBlockDescriptor(), other, overwrite); +		} + +		// clear out old choice when param has changed +		/*virtual*/ void paramChanged(const Param& changed_param, bool user_provided) +		{  +			param_handle_t changed_param_handle = base_block_t::getHandleFromParam(&changed_param); +			// if we have a new choice... +			if (changed_param_handle != mCurChoice) +			{ +				// clear provided flag on previous choice +				Param* previous_choice = base_block_t::getParamFromHandle(mCurChoice); +				if (previous_choice)  +				{ +					previous_choice->setProvided(false); +				} +				mCurChoice = changed_param_handle; +			} +			base_block_t::paramChanged(changed_param, user_provided); +		} + +		virtual const BlockDescriptor& mostDerivedBlockDescriptor() const { return selfBlockDescriptor(); } +		virtual BlockDescriptor& mostDerivedBlockDescriptor() { return selfBlockDescriptor(); } + +	protected: +		ChoiceBlock() +		:	mCurChoice(0) +		{ +			BaseBlock::init(selfBlockDescriptor(), base_block_t::selfBlockDescriptor(), sizeof(DERIVED_BLOCK)); +		} + +		// Alternatives are mutually exclusive wrt other Alternatives in the same block.   +		// One alternative in a block will always have isChosen() == true. +		// At most one alternative in a block will have isProvided() == true. +		template <typename T, typename NAME_VALUE_LOOKUP = TypeValues<T> > +		class Alternative : public TypedParam<T, NAME_VALUE_LOOKUP, false> +		{ +		public: +			friend class ChoiceBlock<DERIVED_BLOCK>; + +			typedef Alternative<T, NAME_VALUE_LOOKUP>									self_t; +			typedef TypedParam<T, NAME_VALUE_LOOKUP, false, IsBlock<ParamValue<T, NAME_VALUE_LOOKUP> >::value>		super_t; +			typedef typename super_t::value_assignment_t								value_assignment_t; + +			using super_t::operator =; + +			explicit Alternative(const char* name = "", value_assignment_t val = defaultValue<T>()) +			:	super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, val, NULL, 0, 1), +				mOriginalValue(val) +			{ +				// assign initial choice to first declared option +				DERIVED_BLOCK* blockp = ((DERIVED_BLOCK*)DERIVED_BLOCK::selfBlockDescriptor().mCurrentBlockPtr); +				if (LL_UNLIKELY(DERIVED_BLOCK::selfBlockDescriptor().mInitializationState == BlockDescriptor::INITIALIZING)) +				{ +					if(blockp->mCurChoice == 0) +					{ +						blockp->mCurChoice = Param::enclosingBlock().getHandleFromParam(this); +					} +				} +			} + +			void choose() +			{ +				static_cast<enclosing_block_t&>(Param::enclosingBlock()).paramChanged(*this, true); +			} + +			void chooseAs(value_assignment_t val) +			{ +				super_t::set(val); +			} + +			void operator =(value_assignment_t val) +			{ +				super_t::set(val); +			} + +			void operator()(typename super_t::value_assignment_t val)  +			{  +				super_t::set(val); +			} + +			operator value_assignment_t() const  +			{ +				return (*this)(); +			}  + +			value_assignment_t operator()() const  +			{  +				if (static_cast<enclosing_block_t&>(Param::enclosingBlock()).getCurrentChoice() == this) +				{ +					return super_t::getValue();  +				} +				return mOriginalValue; +			}  + +			bool isChosen() const +			{ +				return static_cast<enclosing_block_t&>(Param::enclosingBlock()).getCurrentChoice() == this; +			} +		 +		private: +			T			mOriginalValue; +		}; + +	protected: +		static BlockDescriptor& selfBlockDescriptor() +		{ +			static BlockDescriptor sBlockDescriptor; +			return sBlockDescriptor; +		} + +	private: +		param_handle_t	mCurChoice; + +		const Param* getCurrentChoice() const +		{ +			return base_block_t::getParamFromHandle(mCurChoice); +		} +	}; + +	template <typename DERIVED_BLOCK, typename BASE_BLOCK = BaseBlock> +	class Block  +	:	public BASE_BLOCK +	{ +		typedef Block<DERIVED_BLOCK, BASE_BLOCK>	self_t; +		typedef Block<DERIVED_BLOCK, BASE_BLOCK>	block_t; + +	public: +		typedef BASE_BLOCK base_block_t; + +		// take all provided params from other and apply to self +		bool overwriteFrom(const self_t& other) +		{ +			return static_cast<DERIVED_BLOCK*>(this)->mergeBlock(selfBlockDescriptor(), other, true); +		} + +		// take all provided params that are not already provided, and apply to self +		bool fillFrom(const self_t& other) +		{ +			return static_cast<DERIVED_BLOCK*>(this)->mergeBlock(selfBlockDescriptor(), other, false); +		} + +		virtual const BlockDescriptor& mostDerivedBlockDescriptor() const { return selfBlockDescriptor(); } +		virtual BlockDescriptor& mostDerivedBlockDescriptor() { return selfBlockDescriptor(); } + +	protected: +		Block() +		{ +			//#pragma message("Parsing LLInitParam::Block") +			BaseBlock::init(selfBlockDescriptor(), BASE_BLOCK::selfBlockDescriptor(), sizeof(DERIVED_BLOCK)); +		} + +		// +		// Nested classes for declaring parameters +		// +		template <typename T, typename NAME_VALUE_LOOKUP = TypeValues<T> > +		class Optional : public TypedParam<T, NAME_VALUE_LOOKUP, false> +		{ +		public: +			typedef TypedParam<T, NAME_VALUE_LOOKUP, false, IsBlock<ParamValue<T, NAME_VALUE_LOOKUP> >::value>		super_t; +			typedef typename super_t::value_assignment_t								value_assignment_t; + +			using super_t::operator(); +			using super_t::operator =; +			 +			explicit Optional(const char* name = "", value_assignment_t val = defaultValue<T>()) +			:	super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, val, NULL, 0, 1) +			{ +				//#pragma message("Parsing LLInitParam::Block::Optional") +			} + +			Optional& operator =(value_assignment_t val) +			{ +				set(val); +				return *this; +			} + +			DERIVED_BLOCK& operator()(value_assignment_t val) +			{ +				super_t::set(val); +				return static_cast<DERIVED_BLOCK&>(Param::enclosingBlock()); +			} +		}; + +		template <typename T, typename NAME_VALUE_LOOKUP = TypeValues<T> > +		class Mandatory : public TypedParam<T, NAME_VALUE_LOOKUP, false> +		{ +		public: +			typedef TypedParam<T, NAME_VALUE_LOOKUP, false, IsBlock<ParamValue<T, NAME_VALUE_LOOKUP> >::value>		super_t; +			typedef Mandatory<T, NAME_VALUE_LOOKUP>										self_t; +			typedef typename super_t::value_assignment_t								value_assignment_t; + +			using super_t::operator(); +			using super_t::operator =; + +			// mandatory parameters require a name to be parseable +			explicit Mandatory(const char* name = "", value_assignment_t val = defaultValue<T>()) +			:	super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, val, &validate, 1, 1) +			{} + +			Mandatory& operator =(value_assignment_t val) +			{ +				set(val); +				return *this; +			} + +			DERIVED_BLOCK& operator()(typename super_t::value_assignment_t val) +			{ +				super_t::set(val); +				return static_cast<DERIVED_BLOCK&>(Param::enclosingBlock()); +			} + +			static bool validate(const Param* p) +			{ +				// valid only if provided +				return static_cast<const self_t*>(p)->isProvided(); +			} + +		}; + +		template <typename T, typename RANGE = BaseBlock::AnyAmount, typename NAME_VALUE_LOOKUP = TypeValues<T> > +		class Multiple : public TypedParam<T, NAME_VALUE_LOOKUP, true> +		{ +		public: +			typedef TypedParam<T, NAME_VALUE_LOOKUP, true, IsBlock<ParamValue<T, NAME_VALUE_LOOKUP> >::value>	super_t; +			typedef Multiple<T, RANGE, NAME_VALUE_LOOKUP>							self_t; +			typedef typename super_t::container_t									container_t; +			typedef typename super_t::value_assignment_t							value_assignment_t; +			typedef typename super_t::iterator										iterator; +			typedef typename super_t::const_iterator								const_iterator; + +			explicit Multiple(const char* name = "") +			:	super_t(DERIVED_BLOCK::selfBlockDescriptor(), name, container_t(), &validate, RANGE::minCount, RANGE::maxCount) +			{} + +			Multiple& operator =(value_assignment_t val) +			{ +				set(val); +				return *this; +			} + +			DERIVED_BLOCK& operator()(typename super_t::value_assignment_t val) +			{ +				super_t::set(val); +				return static_cast<DERIVED_BLOCK&>(Param::enclosingBlock()); +			} + +			static bool validate(const Param* paramp)  +			{ +				U32 num_valid = ((super_t*)paramp)->numValidElements(); +				return RANGE::minCount <= num_valid && num_valid <= RANGE::maxCount; +			} +		}; + +		class Deprecated : public Param +		{ +		public: +			explicit Deprecated(const char* name) +			:	Param(DERIVED_BLOCK::selfBlockDescriptor().mCurrentBlockPtr) +			{ +				BlockDescriptor& block_descriptor = DERIVED_BLOCK::selfBlockDescriptor(); +				if (LL_UNLIKELY(block_descriptor.mInitializationState == BlockDescriptor::INITIALIZING)) +				{ +					ParamDescriptorPtr param_descriptor = ParamDescriptorPtr(new ParamDescriptor( +													block_descriptor.mCurrentBlockPtr->getHandleFromParam(this), +													NULL, +													&deserializeParam, +													NULL, +													NULL, +													NULL,  +													0, S32_MAX)); +					BaseBlock::addParam(block_descriptor, param_descriptor, name); +				} +			} +			 +			static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) +			{ +				if (name_stack_range.first == name_stack_range.second) +				{ +					//std::string message = llformat("Deprecated value %s ignored", getName().c_str()); +					//parser.parserWarning(message); +					return true; +				} + +				return false; +			} +		}; + +		// different semantics for documentation purposes, but functionally identical +		typedef Deprecated Ignored; + +	protected: +		static BlockDescriptor& selfBlockDescriptor() +		{ +			static BlockDescriptor sBlockDescriptor; +			return sBlockDescriptor; +		} + +		template <typename T, typename NAME_VALUE_LOOKUP, bool multiple, bool is_block> +		void changeDefault(TypedParam<T, NAME_VALUE_LOOKUP, multiple, is_block>& param,  +			typename TypedParam<T, NAME_VALUE_LOOKUP, multiple, is_block>::value_assignment_t value) +		{ +			if (!param.isProvided()) +			{ +				param.set(value, false); +			} +		} + +	}; +	 +	template <typename DERIVED_BLOCK, typename BASE_BLOCK = BaseBlock> +	class BatchBlock +	:	public Block<DERIVED_BLOCK, BASE_BLOCK> +	{ +	public: +		typedef BatchBlock<DERIVED_BLOCK, BASE_BLOCK> self_t; +		typedef Block<DERIVED_BLOCK, BASE_BLOCK> super_t; + +		BatchBlock() +		{} + +		bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name) +		{ +			if (new_name) +			{ +				// reset block +				*static_cast<DERIVED_BLOCK*>(this) = defaultBatchValue(); +			} +			return super_t::deserializeBlock(p, name_stack_range, new_name); +		} + +		bool mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) +		{ +			if (overwrite) +			{ +				*static_cast<DERIVED_BLOCK*>(this) = defaultBatchValue(); +				// merge individual parameters into destination +				return super_t::mergeBlock(super_t::selfBlockDescriptor(), other, overwrite); +			} +			return false; +		} +	protected: +		static const DERIVED_BLOCK& defaultBatchValue() +		{ +			static DERIVED_BLOCK default_value; +			return default_value; +		} +	}; + +	// FIXME: this specialization is not currently used, as it only matches against the BatchBlock base class +	// and not the derived class with the actual params +	template<typename DERIVED_BLOCK, +			typename BASE_BLOCK, +			typename NAME_VALUE_LOOKUP> +	class ParamValue <BatchBlock<DERIVED_BLOCK, BASE_BLOCK>, +					NAME_VALUE_LOOKUP, +					true> +	:	public NAME_VALUE_LOOKUP, +		protected BatchBlock<DERIVED_BLOCK, BASE_BLOCK> +	{ +	public: +		typedef BatchBlock<DERIVED_BLOCK, BASE_BLOCK> block_t; +		typedef const BatchBlock<DERIVED_BLOCK, BASE_BLOCK>&	value_assignment_t; +		typedef block_t value_t; + +		ParamValue() +		:	block_t(), +			mValidated(false) +		{} + +		ParamValue(value_assignment_t other) +		:	block_t(other), +			mValidated(false) +		{ +		} + +		void setValue(value_assignment_t val) +		{ +			*this = val; +		} + +		value_assignment_t getValue() const +		{ +			return *this; +		} + +		BatchBlock<DERIVED_BLOCK, BASE_BLOCK>& getValue() +		{ +			return *this; +		} + +		operator value_assignment_t() const +		{ +			return *this; +		} + +		value_assignment_t operator()() const +		{ +			return *this; +		} + +	protected: +		mutable bool 	mValidated; // lazy validation flag +	}; + +	template<typename T, bool IS_BLOCK> +	class ParamValue <BaseBlock::Lazy<T>, +					TypeValues<T>, +					IS_BLOCK> +	:	public IsBlock<T>::base_class_t +	{ +	public: +		typedef ParamValue <BaseBlock::Lazy<T>, TypeValues<T>, false> self_t; +		typedef const T& value_assignment_t; +		typedef T value_t; +	 +		ParamValue() +		:	mValue(), +			mValidated(false) +		{} + +		ParamValue(value_assignment_t other) +		:	mValue(other), +			mValidated(false) +		{} + +		void setValue(value_assignment_t val) +		{ +			mValue.set(val); +		} + +		value_assignment_t getValue() const +		{ +			return mValue.get(); +		} + +		T& getValue() +		{ +			return mValue.get(); +		} + +		operator value_assignment_t() const +		{ +			return mValue.get(); +		} + +		value_assignment_t operator()() const +		{ +			return mValue.get(); +		} + +		bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name) +		{ +			return mValue.get().deserializeBlock(p, name_stack_range, new_name); +		} + +		void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const +		{ +			if (mValue.empty()) return; +			 +			mValue.get().serializeBlock(p, name_stack, diff_block); +		} + +		bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const +		{ +			if (mValue.empty()) return false; + +			return mValue.get().inspectBlock(p, name_stack, min_count, max_count); +		} + +	protected: +		mutable bool 	mValidated; // lazy validation flag + +	private: +		BaseBlock::Lazy<T>	mValue; +	}; + +	template <> +	class ParamValue <LLSD, +					TypeValues<LLSD>, +					false> +	:	public TypeValues<LLSD>, +		public BaseBlock +	{ +	public: +		typedef ParamValue<LLSD, TypeValues<LLSD>, false> self_t; +		typedef const LLSD&	value_assignment_t; + +		ParamValue() +		:	mValidated(false) +		{} + +		ParamValue(value_assignment_t other) +		:	mValue(other), +			mValidated(false) +		{} + +		void setValue(value_assignment_t val) { mValue = val; } + +		value_assignment_t getValue() const { return mValue; } +		LLSD& getValue() { return mValue; } + +		operator value_assignment_t() const { return mValue; } +		value_assignment_t operator()() const { return mValue; } +		 + +		// block param interface +		LL_COMMON_API bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); +		LL_COMMON_API void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const; +		bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const +		{ +			//TODO: implement LLSD params as schema type Any +			return true; +		} + +	protected: +		mutable bool 	mValidated; // lazy validation flag + +	private: +		static void serializeElement(Parser& p, const LLSD& sd, Parser::name_stack_t& name_stack); + +		LLSD mValue; +	}; + +	template<typename T> +	class CustomParamValue +	:	public Block<ParamValue<T, TypeValues<T> > >, +		public TypeValues<T> +	{ +	public: +		typedef enum e_value_age +		{	 +			VALUE_NEEDS_UPDATE,		// mValue needs to be refreshed from the block parameters +			VALUE_AUTHORITATIVE,	// mValue holds the authoritative value (which has been replicated to the block parameters via updateBlockFromValue) +			BLOCK_AUTHORITATIVE		// mValue is derived from the block parameters, which are authoritative +		} EValueAge; + +		typedef ParamValue<T, TypeValues<T> >	derived_t; +		typedef CustomParamValue<T>				self_t; +		typedef Block<derived_t>				block_t; +		typedef const T&						value_assignment_t; +		typedef T								value_t; + + +		CustomParamValue(const T& value = T()) +		:	mValue(value), +			mValueAge(VALUE_AUTHORITATIVE), +			mValidated(false) +		{} + +		bool deserializeBlock(Parser& parser, Parser::name_stack_range_t name_stack_range, bool new_name) +		{ +			derived_t& typed_param = static_cast<derived_t&>(*this); +			// try to parse direct value T +			if (name_stack_range.first == name_stack_range.second) +			{ +				if(parser.readValue(typed_param.mValue)) +				{ +					typed_param.mValueAge = VALUE_AUTHORITATIVE; +					typed_param.updateBlockFromValue(false); + +					typed_param.clearValueName(); + +					return true; +				} +			} + +			// fall back on parsing block components for T +			return typed_param.BaseBlock::deserializeBlock(parser, name_stack_range, new_name); +		} + +		void serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const +		{ +			const derived_t& typed_param = static_cast<const derived_t&>(*this); +			const derived_t* diff_param = static_cast<const derived_t*>(diff_block); +			 +			std::string key = typed_param.getValueName(); + +			// first try to write out name of name/value pair +			if (!key.empty()) +			{ +				if (!diff_param || !ParamCompare<std::string>::equals(diff_param->getValueName(), key)) +				{ +					parser.writeValue(key, name_stack); +				} +			} +			// then try to serialize value directly +			else if (!diff_param || !ParamCompare<T>::equals(typed_param.getValue(), diff_param->getValue())) +            { +				 +				if (!parser.writeValue(typed_param.getValue(), name_stack))  +				{ +					//RN: *always* serialize provided components of BlockValue (don't pass diff_param on), +					// since these tend to be viewed as the constructor arguments for the value T.  It seems +					// cleaner to treat the uniqueness of a BlockValue according to the generated value, and +					// not the individual components.  This way <color red="0" green="1" blue="0"/> will not +					// be exported as <color green="1"/>, since it was probably the intent of the user to  +					// be specific about the RGB color values.  This also fixes an issue where we distinguish +					// between rect.left not being provided and rect.left being explicitly set to 0 (same as default) + +					if (typed_param.mValueAge == VALUE_AUTHORITATIVE) +					{ +						// if the value is authoritative but the parser doesn't accept the value type +						// go ahead and make a copy, and splat the value out to its component params +						// and serialize those params +						derived_t copy(typed_param); +						copy.updateBlockFromValue(true); +						copy.block_t::serializeBlock(parser, name_stack, NULL); +					} +					else +					{ +						block_t::serializeBlock(parser, name_stack, NULL); +					} +				} +			} +		} + +		bool inspectBlock(Parser& parser, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const +		{ +			// first, inspect with actual type... +			parser.inspectValue<T>(name_stack, min_count, max_count, NULL); +			if (TypeValues<T>::getPossibleValues()) +			{ +				//...then inspect with possible string values... +				parser.inspectValue<std::string>(name_stack, min_count, max_count, TypeValues<T>::getPossibleValues()); +			} +			// then recursively inspect contents... +			return block_t::inspectBlock(parser, name_stack, min_count, max_count); +		} + +		bool validateBlock(bool emit_errors = true) const +		{ +			if (mValueAge == VALUE_NEEDS_UPDATE) +			{ +				if (block_t::validateBlock(emit_errors)) +				{ +					// clear stale keyword associated with old value +					TypeValues<T>::clearValueName(); +					mValueAge = BLOCK_AUTHORITATIVE; +					static_cast<derived_t*>(const_cast<self_t*>(this))->updateValueFromBlock(); +					return true; +				} +				else +				{ +					//block value incomplete, so not considered provided +					// will attempt to revalidate on next call to isProvided() +					return false;   +				} +			} +			else +			{ +				// we have a valid value in hand +				return true; +			} +		} + + 		// propagate change status up to enclosing block +		/*virtual*/ void paramChanged(const Param& changed_param, bool user_provided) +		{  +			BaseBlock::paramChanged(changed_param, user_provided); +			if (user_provided) +			{ +				// a parameter changed, so our value is out of date +				mValueAge = VALUE_NEEDS_UPDATE; +			} +		} +			 +		void setValue(value_assignment_t val) +		{ +			derived_t& typed_param = static_cast<derived_t&>(*this); +			// set param version number to be up to date, so we ignore block contents +			mValueAge = VALUE_AUTHORITATIVE; +			mValue = val; +			typed_param.clearValueName(); +			static_cast<derived_t*>(this)->updateBlockFromValue(false); +		} + +		value_assignment_t getValue() const +		{ +			validateBlock(true); +			return mValue; +		} + +		T& getValue()  +		{ +			validateBlock(true); +			return mValue; +		} + +		operator value_assignment_t() const +		{ +			return getValue(); +		} + +		value_assignment_t operator()() const +		{ +			return getValue(); +		} + +	protected: + +		// use this from within updateValueFromBlock() to set the value without making it authoritative +		void updateValue(value_assignment_t value) +		{ +			mValue = value; +		} + +		bool mergeBlockParam(bool source_provided, bool dst_provided, BlockDescriptor& block_data, const BaseBlock& source, bool overwrite) +		{ +			bool source_override = source_provided && (overwrite || !dst_provided); + +			const derived_t& src_typed_param = static_cast<const derived_t&>(source); + +			if (source_override && src_typed_param.mValueAge == VALUE_AUTHORITATIVE) +			{ +				// copy value over +				setValue(src_typed_param.getValue()); +				return true; +			} +			// merge individual parameters into destination +			if (mValueAge == VALUE_AUTHORITATIVE) +			{ +				static_cast<derived_t*>(this)->updateBlockFromValue(dst_provided); +			} +			return mergeBlock(block_data, source, overwrite); +		} + +		bool mergeBlock(BlockDescriptor& block_data, const BaseBlock& source, bool overwrite) +		{ +			return block_t::mergeBlock(block_data, source, overwrite); +		} + +		mutable bool 		mValidated; // lazy validation flag + +	private: +		mutable T			mValue; +		mutable EValueAge	mValueAge; +	}; +} + + +#endif // LL_LLPARAM_H diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp new file mode 100644 index 0000000000..7ccbdeed01 --- /dev/null +++ b/indra/llcommon/llprocess.cpp @@ -0,0 +1,642 @@ +/**  + * @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 "llsdserialize.h" +#include "llsingleton.h" +#include "llstring.h" +#include "stringize.h" +#include "llapr.h" +#include "apr_signal.h" + +#include <boost/foreach.hpp> +#include <iostream> +#include <stdexcept> + +static std::string empty; +static LLProcess::Status interpret_status(int status); + +/// Need an exception to avoid constructing an invalid LLProcess object, but +/// internal use only +struct LLProcessError: public std::runtime_error +{ +	LLProcessError(const std::string& msg): std::runtime_error(msg) {} +}; + +LLProcessPtr LLProcess::create(const LLSDOrParams& params) +{ +	try +	{ +		return LLProcessPtr(new LLProcess(params)); +	} +	catch (const LLProcessError& e) +	{ +		LL_WARNS("LLProcess") << e.what() << LL_ENDL; +		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) +{ +	if (! params.validateBlock(true)) +	{ +		throw LLProcessError(STRINGIZE("not launched: failed parameter validation\n" +									   << LLSDNotationStreamer(params))); +	} + +	apr_procattr_t *procattr = NULL; +	chkapr(apr_procattr_create(&procattr, gAPRPoolp)); + +	// 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). 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. +	// 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). +//	chkapr(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)); +	chkapr(apr_procattr_io_set(procattr, APR_NO_PIPE, APR_NO_PIPE, APR_NO_PIPE)); + +	// 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, params.autokill? 0 : 1)); + +	if (params.autokill) +	{ +#if defined(APR_HAS_PROCATTR_AUTOKILL_SET) +		apr_status_t ok = apr_procattr_autokill_set(procattr, 1); +# if LL_WINDOWS +		// As of 2012-02-02, we only expect this to be implemented on Windows. +		// Avoid spamming the log with warnings we fully expect. +		ll_apr_warn_status(ok); +# endif // LL_WINDOWS +#else +		LL_WARNS("LLProcess") << "This version of APR lacks Linden apr_procattr_autokill_set() extension" << LL_ENDL; +#endif +	} + +	// Have to instantiate named std::strings for string params items so their +	// c_str() values persist. +	std::string cwd(params.cwd); +	if (! cwd.empty()) +	{ +		chkapr(apr_procattr_dir_set(procattr, cwd.c_str())); +	} + +	// create an argv vector for the child process +	std::vector<const char*> argv; + +	// add the executable path +	std::string executable(params.executable); +	argv.push_back(executable.c_str()); + +	// and any arguments +	std::vector<std::string> args(params.args.begin(), params.args.end()); +	BOOST_FOREACH(const std::string& arg, 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. +	chkapr(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, gAPRPoolp));     + +	// arrange to call status_callback() +	apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, +								  gAPRPoolp); +	mStatus.mState = RUNNING; + +	mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcess.pid << ')'); +	LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcess.pid << ")" << 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 (params.autokill) +	{ +		// 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 wait 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(); +	} +} + +LLProcess::~LLProcess() +{ +	// 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); +	} + +	if (mAutokill) +	{ +		kill("destructor"); +	} +} + +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(); +} + +bool LLProcess::isRunning(void) +{ +	return getStatus().mState == RUNNING; +} + +LLProcess::Status LLProcess::getStatus() +{ +	// Only when mState is RUNNING might the status change dynamically. For +	// any other value, pointless to attempt to update status: it won't +	// change. +	if (mStatus.mState == RUNNING) +	{ +		// Tell APR to sense whether the child 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 if I +		// knew how. As it is, each call to this method will call callbacks +		// for ALL still-running child processes. Sigh... +		apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); +	} + +	return mStatus; +} + +std::string LLProcess::getStatusString() +{ +	return getStatusString(getStatus()); +} + +std::string LLProcess::getStatusString(const Status& status) +{ +	return getStatusString(mDesc, status); +} + +//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; +		BOOST_FOREACH(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); +	// 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; + +//	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; +} + +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::ostream& operator<<(std::ostream& out, const LLProcess::Params& params) +{ +	std::string cwd(params.cwd); +	if (! cwd.empty()) +	{ +		out << "cd " << LLStringUtil::quote(cwd) << ": "; +	} +	out << LLStringUtil::quote(params.executable); +	BOOST_FOREACH(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) +{ +	int result = GetLastError(); + +	LPTSTR error_str = 0; +	if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, +					   NULL, +					   result, +					   0, +					   (LPTSTR)&error_str, +					   0, +					   NULL) +		!= 0)  +	{ +        // convert from wide-char string to multi-byte string +		char message[256]; +		wcstombs(message, error_str, sizeof(message)); +		message[sizeof(message)-1] = 0; +		LocalFree(error_str); +		// convert to std::string to trim trailing whitespace +		std::string mbsstr(message); +		mbsstr.erase(mbsstr.find_last_not_of(" \t\r\n")); +		return STRINGIZE(operation << " failed (" << result << "): " << mbsstr); +	} +	return STRINGIZE(operation << " failed (" << result +					 << "), but FormatMessage() did not explain"); +} + +/***************************************************************************** +*   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; +} + +/*==========================================================================*| +static std::list<pid_t> sZombies; + +void LLProcess::orphan(void) +{ +	// Disassociate the process from this object +	if(mProcessID != 0) +	{	 +		// We may still need to reap the process's zombie eventually +		sZombies.push_back(mProcessID); +	 +		mProcessID = 0; +	} +} + +// static  +void LLProcess::reap(void) +{ +	// Attempt to real all saved process ID's. +	 +	std::list<pid_t>::iterator iter = sZombies.begin(); +	while(iter != sZombies.end()) +	{ +		if(reap_pid(*iter)) +		{ +			iter = sZombies.erase(iter); +		} +		else +		{ +			iter++; +		} +	} +} +|*==========================================================================*/ + +#endif  // Posix diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h new file mode 100644 index 0000000000..0de033c15b --- /dev/null +++ b/indra/llcommon/llprocess.h @@ -0,0 +1,207 @@ +/**  + * @file llprocess.h + * @brief Utility class for launching, terminating, and tracking child 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$ + */ +  +#ifndef LL_LLPROCESS_H +#define LL_LLPROCESS_H + +#include "llinitparam.h" +#include "llsdparam.h" +#include "apr_thread_proc.h" +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> +#include <iosfwd>                   // std::ostream + +#if LL_WINDOWS +#define WIN32_LEAN_AND_MEAN +#include <windows.h>                // HANDLE (eye roll) +#elif LL_LINUX +#if defined(Status) +#undef Status +#endif +#endif + +class LLProcess; +/// LLProcess instances are created on the heap by static factory methods and +/// managed by ref-counted pointers. +typedef boost::shared_ptr<LLProcess> LLProcessPtr; + +/** + *	LLProcess handles launching external processes with specified command line arguments. + *	It also keeps track of whether the process is still running, and can kill it if required. +*/ +class LL_COMMON_API LLProcess: public boost::noncopyable +{ +	LOG_CLASS(LLProcess); +public: +	/// Param block definition +	struct Params: public LLInitParam::Block<Params> +	{ +		Params(): +			executable("executable"), +			args("args"), +			cwd("cwd"), +			autokill("autokill", true) +		{} + +		/// pathname of executable +		Mandatory<std::string> executable; +		/** +		 * zero or more additional command-line arguments. Arguments are +		 * passed through as exactly as we can manage, whitespace and all. +		 * @note On Windows we manage this by implicitly double-quoting each +		 * argument while assembling the command line. BUT if a given argument +		 * is already double-quoted, we don't double-quote it again. Try to +		 * avoid making use of this, though, as on Mac and Linux explicitly +		 * double-quoted args will be passed to the child process including +		 * the double quotes. +		 */ +		Multiple<std::string> args; +		/// current working directory, if need it changed +		Optional<std::string> cwd; +		/// implicitly kill process on destruction of LLProcess object +		Optional<bool> autokill; +	}; +	typedef LLSDParamAdapter<Params> LLSDOrParams; + +	/** +	 * Factory accepting either plain LLSD::Map or Params block. +	 * MAY RETURN DEFAULT-CONSTRUCTED LLProcessPtr if params invalid! +	 * +	 * Redundant with Params definition above? +	 * +	 * executable (required, string):				executable pathname +	 * args		  (optional, string array):			extra command-line arguments +	 * cwd		  (optional, string, dft no chdir): change to this directory before executing +	 * autokill	  (optional, bool, dft true):		implicit kill() on ~LLProcess +	 */ +	static LLProcessPtr create(const LLSDOrParams& params); +	virtual ~LLProcess(); + +	// isRunning() isn't const because, when child terminates, it sets stored +	// Status +	bool isRunning(void); + +	/** +	 * State of child process +	 */ +	enum state +	{ +		UNSTARTED,					///< initial value, invisible to consumer +		RUNNING,					///< child process launched +		EXITED,						///< child process terminated voluntarily +		KILLED						///< child process terminated involuntarily +	}; + +	/** +	 * Status info +	 */ +	struct Status +	{ +		Status(): +			mState(UNSTARTED), +			mData(0) +		{} + +		state mState;				///< @see state +		/** +		 * - for mState == EXITED: mData is exit() code +		 * - for mState == KILLED: mData is signal number (Posix) +		 * - otherwise: mData is undefined +		 */ +		int mData; +	}; + +	/// Status query +	Status getStatus(); +	/// English Status string query, for logging etc. +	std::string getStatusString(); +	/// English Status string query for previously-captured Status +	std::string getStatusString(const Status& status); +	/// static English Status string query +	static std::string getStatusString(const std::string& desc, const Status& status); + +	// Attempt to kill the process -- returns true if the process is no longer running when it returns. +	// Note that even if this returns false, the process may exit some time after it's called. +	bool kill(const std::string& who=""); + +#if LL_WINDOWS +	typedef int id;                 ///< as returned by getProcessID() +	typedef HANDLE handle;          ///< as returned by getProcessHandle() +#else +	typedef pid_t id; +	typedef pid_t handle; +#endif +	/** +	 * Get an int-like id value. This is primarily intended for a human reader +	 * to differentiate processes. +	 */ +	id getProcessID() const; +	/** +	 * Get a "handle" of a kind that you might pass to platform-specific API +	 * functions to engage features not directly supported by LLProcess. +	 */ +	handle getProcessHandle() const; + +	/** +	 * Test if a process (@c handle obtained from getProcessHandle()) is still +	 * running. Return same nonzero @c handle value if still running, else +	 * zero, so you can test it like a bool. But if you want to update a +	 * stored variable as a side effect, you can write code like this: +	 * @code +	 * hchild = LLProcess::isRunning(hchild); +	 * @endcode +	 * @note This method is intended as a unit-test hook, not as the first of +	 * a whole set of operations supported on freestanding @c handle values. +	 * New functionality should be added as nonstatic members operating on +	 * the same data as getProcessHandle(). +	 * +	 * In particular, if child termination is detected by static isRunning() +	 * rather than by nonstatic isRunning(), the LLProcess object won't be +	 * aware of the child's changed status and may encounter OS errors trying +	 * to obtain it. static isRunning() is only intended for after the +	 * launching LLProcess object has been destroyed. +	 */ +	static handle isRunning(handle, const std::string& desc=""); + +private: +	/// constructor is private: use create() instead +	LLProcess(const LLSDOrParams& params); +	void autokill(); +	// Classic-C-style APR callback +	static void status_callback(int reason, void* data, int status); +	// Object-oriented callback +	void handle_status(int reason, int status); + +	std::string mDesc; +	apr_proc_t mProcess; +	bool mAutokill; +	Status mStatus; +}; + +/// for logging +LL_COMMON_API std::ostream& operator<<(std::ostream&, const LLProcess::Params&); + +#endif // LL_LLPROCESS_H diff --git a/indra/llcommon/llprocesslauncher.cpp b/indra/llcommon/llprocesslauncher.cpp deleted file mode 100644 index 10950181fd..0000000000 --- a/indra/llcommon/llprocesslauncher.cpp +++ /dev/null @@ -1,357 +0,0 @@ -/**  - * @file llprocesslauncher.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 "llprocesslauncher.h" - -#include <iostream> -#if LL_DARWIN || LL_LINUX -// not required or present on Win32 -#include <sys/wait.h> -#endif - -LLProcessLauncher::LLProcessLauncher() -{ -#if LL_WINDOWS -	mProcessHandle = 0; -#else -	mProcessID = 0; -#endif -} - -LLProcessLauncher::~LLProcessLauncher() -{ -	kill(); -} - -void LLProcessLauncher::setExecutable(const std::string &executable) -{ -	mExecutable = executable; -} - -void LLProcessLauncher::setWorkingDirectory(const std::string &dir) -{ -	mWorkingDir = dir; -} - -const std::string& LLProcessLauncher::getExecutable() const -{ -	return mExecutable; -} - -void LLProcessLauncher::clearArguments() -{ -	mLaunchArguments.clear(); -} - -void LLProcessLauncher::addArgument(const std::string &arg) -{ -	mLaunchArguments.push_back(arg); -} - -void LLProcessLauncher::addArgument(const char *arg) -{ -	mLaunchArguments.push_back(std::string(arg)); -} - -#if LL_WINDOWS - -int LLProcessLauncher::launch(void) -{ -	// If there was already a process associated with this object, kill it. -	kill(); -	orphan(); - -	int result = 0; -	 -	PROCESS_INFORMATION pinfo; -	STARTUPINFOA sinfo; -	memset(&sinfo, 0, sizeof(sinfo)); -	 -	std::string args = mExecutable; -	for(int i = 0; i < (int)mLaunchArguments.size(); i++) -	{ -		args += " "; -		args += mLaunchArguments[i]; -	} -	 -	// So retarded.  Windows requires that the second parameter to CreateProcessA be a writable (non-const) string... -	char *args2 = new char[args.size() + 1]; -	strcpy(args2, args.c_str()); - -	const char * working_directory = 0; -	if(!mWorkingDir.empty()) working_directory = mWorkingDir.c_str(); -	if( ! CreateProcessA( NULL, args2, NULL, NULL, FALSE, 0, NULL, working_directory, &sinfo, &pinfo ) ) -	{ -		result = GetLastError(); - -		LPTSTR error_str = 0; -		if( -			FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, -				NULL, -				result, -				0, -				(LPTSTR)&error_str, -				0, -				NULL)  -			!= 0)  -		{ -			char message[256]; -			wcstombs(message, error_str, 256); -			message[255] = 0; -			llwarns << "CreateProcessA failed: " << message << llendl; -			LocalFree(error_str); -		} - -		if(result == 0) -		{ -			// Make absolutely certain we return a non-zero value on failure. -			result = -1; -		} -	} -	else -	{ -		// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on -		// CloseHandle(pinfo.hProcess); // stops leaks - nothing else -		mProcessHandle = pinfo.hProcess; -		CloseHandle(pinfo.hThread); // stops leaks - nothing else -	}		 -	 -	delete[] args2; -	 -	return result; -} - -bool LLProcessLauncher::isRunning(void) -{ -	if(mProcessHandle != 0)		 -	{ -		DWORD waitresult = WaitForSingleObject(mProcessHandle, 0); -		if(waitresult == WAIT_OBJECT_0) -		{ -			// the process has completed. -			mProcessHandle = 0; -		}			 -	} - -	return (mProcessHandle != 0); -} -bool LLProcessLauncher::kill(void) -{ -	bool result = true; -	 -	if(mProcessHandle != 0) -	{ -		TerminateProcess(mProcessHandle,0); - -		if(isRunning()) -		{ -			result = false; -		} -	} -	 -	return result; -} - -void LLProcessLauncher::orphan(void) -{ -	// Forget about the process -	mProcessHandle = 0; -} - -// static  -void LLProcessLauncher::reap(void) -{ -	// No actions necessary on Windows. -} - -#else // Mac and linux - -#include <signal.h> -#include <fcntl.h> -#include <errno.h> - -static std::list<pid_t> sZombies; - -// 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) -{ -	bool result = false; -	 -	pid_t wait_result = ::waitpid(pid, NULL, WNOHANG); -	if(wait_result == pid) -	{ -		result = true; -	} -	else if(wait_result == -1) -	{ -		if(errno == ECHILD) -		{ -			// No such process -- this may mean we're ignoring SIGCHILD. -			result = true; -		} -	} -	 -	return result; -} - -int LLProcessLauncher::launch(void) -{ -	// If there was already a process associated with this object, kill it. -	kill(); -	orphan(); -	 -	int result = 0; -	int current_wd = -1; -	 -	// create an argv vector for the child process -	const char ** fake_argv = new const char *[mLaunchArguments.size() + 2];  // 1 for the executable path, 1 for the NULL terminator - -	int i = 0; -	 -	// add the executable path -	fake_argv[i++] = mExecutable.c_str(); -	 -	// and any arguments -	for(int j=0; j < mLaunchArguments.size(); j++) -		fake_argv[i++] = mLaunchArguments[j].c_str(); -	 -	// terminate with a null pointer -	fake_argv[i] = NULL; -	 -	if(!mWorkingDir.empty()) -	{ -		// save the current working directory -		current_wd = ::open(".", O_RDONLY); -	 -		// and change to the one the child will be executed in -		if (::chdir(mWorkingDir.c_str())) -		{ -			// chdir failed -		} -	} -		 - 	// flush all buffers before the child inherits them - 	::fflush(NULL); - -	pid_t id = vfork(); -	if(id == 0) -	{ -		// child process -		 -		::execv(mExecutable.c_str(), (char * const *)fake_argv); -		 -		// If we reach this point, the exec failed. -		// Use _exit() instead of exit() per the vfork man page. -		_exit(0); -	} - -	// parent process -	 -	if(current_wd >= 0) -	{ -		// restore the previous working directory -		if (::fchdir(current_wd)) -		{ -			// chdir failed -		} -		::close(current_wd); -	} -	 -	delete[] fake_argv; -	 -	mProcessID = id; - -	return result; -} - -bool LLProcessLauncher::isRunning(void) -{ -	if(mProcessID != 0) -	{ -		// Check whether the process has exited, and reap it if it has. -		if(reap_pid(mProcessID)) -		{ -			// the process has exited. -			mProcessID = 0; -		} -	} -	 -	return (mProcessID != 0); -} - -bool LLProcessLauncher::kill(void) -{ -	bool result = true; -	 -	if(mProcessID != 0) -	{ -		// Try to kill the process.  We'll do approximately the same thing whether the kill returns an error or not, so we ignore the result. -		(void)::kill(mProcessID, SIGTERM); -		 -		// This will have the side-effect of reaping the zombie if the process has exited. -		if(isRunning()) -		{ -			result = false; -		} -	} -	 -	return result; -} - -void LLProcessLauncher::orphan(void) -{ -	// Disassociate the process from this object -	if(mProcessID != 0) -	{	 -		// We may still need to reap the process's zombie eventually -		sZombies.push_back(mProcessID); -	 -		mProcessID = 0; -	} -} - -// static  -void LLProcessLauncher::reap(void) -{ -	// Attempt to real all saved process ID's. -	 -	std::list<pid_t>::iterator iter = sZombies.begin(); -	while(iter != sZombies.end()) -	{ -		if(reap_pid(*iter)) -		{ -			iter = sZombies.erase(iter); -		} -		else -		{ -			iter++; -		} -	} -} - -#endif diff --git a/indra/llcommon/llprocesslauncher.h b/indra/llcommon/llprocesslauncher.h deleted file mode 100644 index 954c249147..0000000000 --- a/indra/llcommon/llprocesslauncher.h +++ /dev/null @@ -1,90 +0,0 @@ -/**  - * @file llprocesslauncher.h - * @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$ - */ -  -#ifndef LL_LLPROCESSLAUNCHER_H -#define LL_LLPROCESSLAUNCHER_H - -#if LL_WINDOWS -#include <windows.h> -#endif - - -/* -	LLProcessLauncher handles launching external processes with specified command line arguments. -	It also keeps track of whether the process is still running, and can kill it if required. -*/ - -class LL_COMMON_API LLProcessLauncher -{ -	LOG_CLASS(LLProcessLauncher); -public: -	LLProcessLauncher(); -	virtual ~LLProcessLauncher(); -	 -	void setExecutable(const std::string &executable); -	void setWorkingDirectory(const std::string &dir); - -	const std::string& getExecutable() const; - -	void clearArguments(); -	void addArgument(const std::string &arg); -	void addArgument(const char *arg); -		 -	int launch(void); -	bool isRunning(void); -	 -	// Attempt to kill the process -- returns true if the process is no longer running when it returns. -	// Note that even if this returns false, the process may exit some time after it's called. -	bool kill(void); -	 -	// Use this if you want the external process to continue execution after the LLProcessLauncher instance controlling it is deleted. -	// Normally, the destructor will attempt to kill the process and wait for termination. -	// This should only be used if the viewer is about to exit -- otherwise, the child process will become a zombie after it exits. -	void orphan(void);	 -	 -	// This needs to be called periodically on Mac/Linux to clean up zombie processes. -	static void reap(void); -	 -	// Accessors for platform-specific process ID -#if LL_WINDOWS -	HANDLE getProcessHandle() { return mProcessHandle; }; -#else -	pid_t getProcessID() { return mProcessID; }; -#endif	 -	 -private: -	std::string mExecutable; -	std::string mWorkingDir; -	std::vector<std::string> mLaunchArguments; -	 -#if LL_WINDOWS -	HANDLE mProcessHandle; -#else -	pid_t mProcessID; -#endif -}; - -#endif // LL_LLPROCESSLAUNCHER_H diff --git a/indra/llcommon/llregistry.h b/indra/llcommon/llregistry.h new file mode 100644 index 0000000000..36ce6a97b7 --- /dev/null +++ b/indra/llcommon/llregistry.h @@ -0,0 +1,351 @@ +/**  + * @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 <boost/type_traits.hpp> +#include "llsingleton.h" + +template <typename T> +class LLRegistryDefaultComparator +{ +	bool operator()(const T& lhs, const T& rhs) { return lhs < rhs; } +}; + +template <typename KEY, typename VALUE, typename COMPARATOR = LLRegistryDefaultComparator<KEY> > +class LLRegistry +{ +public: +	typedef LLRegistry<KEY, VALUE, COMPARATOR>											registry_t; +	typedef typename boost::add_reference<typename boost::add_const<KEY>::type>::type	ref_const_key_t; +	typedef typename boost::add_reference<typename boost::add_const<VALUE>::type>::type	ref_const_value_t; +	typedef typename boost::add_reference<VALUE>::type									ref_value_t; +	typedef typename boost::add_pointer<typename boost::add_const<VALUE>::type>::type	ptr_const_value_t; +	typedef typename boost::add_pointer<VALUE>::type									ptr_value_t; + +	class Registrar +	{ +		friend class LLRegistry<KEY, VALUE, COMPARATOR>; +	public: +		typedef typename std::map<KEY, VALUE> registry_map_t; + +		bool add(ref_const_key_t key, ref_const_value_t value) +		{ +			if (mMap.insert(std::make_pair(key, value)).second == false) +			{ +				llwarns << "Tried to register " << key << " but it was already registered!" << llendl; +				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(scope_list_iterator_t it = mActiveScopes.begin(); +			it != mActiveScopes.end(); +			++it) +		{ +			ptr_value_t valuep = (*it)->getValue(key); +			if (valuep != NULL) return valuep; +		} +		return mDefaultRegistrar.getValue(key); +	} + +	ptr_const_value_t getValue(ref_const_key_t key) const +	{ +		for(scope_list_const_iterator_t it = mActiveScopes.begin(); +			it != mActiveScopes.end(); +			++it) +		{ +			ptr_value_t valuep = (*it)->getValue(key); +			if (valuep != NULL) return valuep; +		} +		return mDefaultRegistrar.getValue(key); +	} + +	bool exists(ref_const_key_t key) const +	{ +		for(scope_list_const_iterator_t it = mActiveScopes.begin(); +			it != mActiveScopes.end(); +			++it) +		{ +			if ((*it)->exists(key)) return true; +		} + +		return mDefaultRegistrar.exists(key); +	} + +	bool empty() const +	{ +		for(scope_list_const_iterator_t it = mActiveScopes.begin(); +			it != mActiveScopes.end(); +			++it) +		{ +			if (!(*it)->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> +{ +	friend class LLSingleton<DERIVED_TYPE>; +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::destroyed()) +			{ +				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) +		{ +			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/llsdparam.cpp b/indra/llcommon/llsdparam.cpp new file mode 100644 index 0000000000..0e29873bb0 --- /dev/null +++ b/indra/llcommon/llsdparam.cpp @@ -0,0 +1,342 @@ +/**  + * @file llsdparam.cpp + * @brief parameter block abstraction for creating complex objects and  + * parsing construction parameters from xml and LLSD + * + * $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" + +// Project includes +#include "llsdparam.h" +#include "llsdutil.h" + +static 	LLInitParam::Parser::parser_read_func_map_t sReadFuncs; +static 	LLInitParam::Parser::parser_write_func_map_t sWriteFuncs; +static 	LLInitParam::Parser::parser_inspect_func_map_t sInspectFuncs; +static const LLSD NO_VALUE_MARKER; + +LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR("LLSD to LLInitParam conversion"); + +// +// LLParamSDParser +// +LLParamSDParser::LLParamSDParser() +: Parser(sReadFuncs, sWriteFuncs, sInspectFuncs) +{ +	using boost::bind; + +	if (sReadFuncs.empty()) +	{ +		registerParserFuncs<LLInitParam::Flag>(readFlag, &LLParamSDParser::writeFlag); +		registerParserFuncs<S32>(readS32, &LLParamSDParser::writeTypedValue<S32>); +		registerParserFuncs<U32>(readU32, &LLParamSDParser::writeU32Param); +		registerParserFuncs<F32>(readF32, &LLParamSDParser::writeTypedValue<F32>); +		registerParserFuncs<F64>(readF64, &LLParamSDParser::writeTypedValue<F64>); +		registerParserFuncs<bool>(readBool, &LLParamSDParser::writeTypedValue<bool>); +		registerParserFuncs<std::string>(readString, &LLParamSDParser::writeTypedValue<std::string>); +		registerParserFuncs<LLUUID>(readUUID, &LLParamSDParser::writeTypedValue<LLUUID>); +		registerParserFuncs<LLDate>(readDate, &LLParamSDParser::writeTypedValue<LLDate>); +		registerParserFuncs<LLURI>(readURI, &LLParamSDParser::writeTypedValue<LLURI>); +		registerParserFuncs<LLSD>(readSD, &LLParamSDParser::writeTypedValue<LLSD>); +	} +} + +// special case handling of U32 due to ambiguous LLSD::assign overload +bool LLParamSDParser::writeU32Param(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) +{ +	LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser); +	if (!sdparser.mWriteRootSD) return false; +	 +	parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); +	LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); +	sd_to_write.assign((S32)*((const U32*)val_ptr)); + +	return true; +} + +bool LLParamSDParser::writeFlag(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) +{ +	LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser); +	if (!sdparser.mWriteRootSD) return false; + +	parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end()); +	LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); + +	return true; +} + +void LLParamSDParser::submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack) +{ +	mCurReadSD = &sd; +	block.submitValue(name_stack, *this); +} + +void LLParamSDParser::readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent) +{ +	mCurReadSD = NULL; +	mNameStack.clear(); +	setParseSilently(silent); + +	LLParamSDParserUtilities::readSDValues(boost::bind(&LLParamSDParser::submit, this, boost::ref(block), _1, _2), sd, mNameStack); +	//readSDValues(sd, block); +} + +void LLParamSDParser::writeSD(LLSD& sd, const LLInitParam::BaseBlock& block) +{ +	mNameStack.clear(); +	mWriteRootSD = &sd; + +	name_stack_t name_stack; +	block.serializeBlock(*this, name_stack); +} + +/*virtual*/ std::string LLParamSDParser::getCurrentElementName() +{ +	std::string full_name = "sd"; +	for (name_stack_t::iterator it = mNameStack.begin();	 +		it != mNameStack.end(); +		++it) +	{ +		full_name += llformat("[%s]", it->first.c_str()); +	} + +	return full_name; +} + + +bool LLParamSDParser::readFlag(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); +	return self.mCurReadSD == &NO_VALUE_MARKER; +} + + +bool LLParamSDParser::readS32(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +    *((S32*)val_ptr) = self.mCurReadSD->asInteger(); +    return true; +} + +bool LLParamSDParser::readU32(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +    *((U32*)val_ptr) = self.mCurReadSD->asInteger(); +    return true; +} + +bool LLParamSDParser::readF32(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +    *((F32*)val_ptr) = self.mCurReadSD->asReal(); +    return true; +} + +bool LLParamSDParser::readF64(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +    *((F64*)val_ptr) = self.mCurReadSD->asReal(); +    return true; +} + +bool LLParamSDParser::readBool(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +    *((bool*)val_ptr) = self.mCurReadSD->asBoolean(); +    return true; +} + +bool LLParamSDParser::readString(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +	*((std::string*)val_ptr) = self.mCurReadSD->asString(); +    return true; +} + +bool LLParamSDParser::readUUID(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +	*((LLUUID*)val_ptr) = self.mCurReadSD->asUUID(); +    return true; +} + +bool LLParamSDParser::readDate(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +	*((LLDate*)val_ptr) = self.mCurReadSD->asDate(); +    return true; +} + +bool LLParamSDParser::readURI(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +	*((LLURI*)val_ptr) = self.mCurReadSD->asURI(); +    return true; +} + +bool LLParamSDParser::readSD(Parser& parser, void* val_ptr) +{ +	LLParamSDParser& self = static_cast<LLParamSDParser&>(parser); + +	*((LLSD*)val_ptr) = *self.mCurReadSD; +    return true; +} + +// static +LLSD& LLParamSDParserUtilities::getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range) +{ +	LLSD* sd_to_write = &input; +	 +	for (LLInitParam::Parser::name_stack_t::iterator it = name_stack_range.first; +		it != name_stack_range.second; +		++it) +	{ +		bool new_traversal = it->second; + +		LLSD* child_sd = it->first.empty() ? sd_to_write : &(*sd_to_write)[it->first]; + +		if (child_sd->isArray()) +		{ +			if (new_traversal) +			{ +				// write to new element at end +				sd_to_write = &(*child_sd)[child_sd->size()]; +			} +			else +			{ +				// write to last of existing elements, or first element if empty +				sd_to_write = &(*child_sd)[llmax(0, child_sd->size() - 1)]; +			} +		} +		else +		{ +			if (new_traversal  +				&& child_sd->isDefined()  +				&& !child_sd->isArray()) +			{ +				// copy child contents into first element of an array +				LLSD new_array = LLSD::emptyArray(); +				new_array.append(*child_sd); +				// assign array to slot that previously held the single value +				*child_sd = new_array; +				// return next element in that array +				sd_to_write = &((*child_sd)[1]); +			} +			else +			{ +				sd_to_write = child_sd; +			} +		} +		it->second = false; +	} +	 +	return *sd_to_write; +} + +//static +void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack) +{ +	if (sd.isMap()) +	{ +		for (LLSD::map_const_iterator it = sd.beginMap(); +			it != sd.endMap(); +			++it) +		{ +			stack.push_back(make_pair(it->first, true)); +			readSDValues(cb, it->second, stack); +			stack.pop_back(); +		} +	} +	else if (sd.isArray()) +	{ +		for (LLSD::array_const_iterator it = sd.beginArray(); +			it != sd.endArray(); +			++it) +		{ +			stack.back().second = true; +			readSDValues(cb, *it, stack); +		} +	} +	else if (sd.isUndefined()) +	{ +		if (!cb.empty()) +		{ +			cb(NO_VALUE_MARKER, stack); +		} +	} +	else +	{ +		if (!cb.empty()) +		{ +			cb(sd, stack); +		} +	} +} + +//static +void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd) +{ +	LLInitParam::Parser::name_stack_t stack = LLInitParam::Parser::name_stack_t(); +	readSDValues(cb, sd, stack); +} +namespace LLInitParam +{ +	// LLSD specialization +	// block param interface +	bool ParamValue<LLSD, TypeValues<LLSD>, false>::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name) +	{ +		LLSD& sd = LLParamSDParserUtilities::getSDWriteNode(mValue, name_stack); + +		LLSD::String string; + +		if (p.readValue<LLSD::String>(string)) +		{ +			sd = string; +			return true; +		} +		return false; +	} + +	//static +	void ParamValue<LLSD, TypeValues<LLSD>, false>::serializeElement(Parser& p, const LLSD& sd, Parser::name_stack_t& name_stack) +	{ +		p.writeValue<LLSD::String>(sd.asString(), name_stack); +	} + +	void ParamValue<LLSD, TypeValues<LLSD>, false>::serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block) const +	{ +		// read from LLSD value and serialize out to parser (which could be LLSD, XUI, etc) +		Parser::name_stack_t stack; +		LLParamSDParserUtilities::readSDValues(boost::bind(&serializeElement, boost::ref(p), _1, _2), mValue, stack); +	} +} diff --git a/indra/llcommon/llsdparam.h b/indra/llcommon/llsdparam.h new file mode 100644 index 0000000000..6ef5debd7b --- /dev/null +++ b/indra/llcommon/llsdparam.h @@ -0,0 +1,126 @@ +/**  + * @file llsdparam.h + * @brief parameter block abstraction for creating complex objects and  + * parsing construction parameters from xml and LLSD + * + * $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$ + */ + +#ifndef LL_LLSDPARAM_H +#define LL_LLSDPARAM_H + +#include "llinitparam.h" +#include "boost/function.hpp" + +struct LL_COMMON_API LLParamSDParserUtilities +{ +	static LLSD& getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range); + +	typedef boost::function<void (const LLSD&, LLInitParam::Parser::name_stack_t&)> read_sd_cb_t; +	static void readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack); +	static void readSDValues(read_sd_cb_t cb, const LLSD& sd); +}; + +class LL_COMMON_API LLParamSDParser  +:	public LLInitParam::Parser +{ +LOG_CLASS(LLParamSDParser); + +typedef LLInitParam::Parser parser_t; + +public: +	LLParamSDParser(); +	void readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent = false); +	void writeSD(LLSD& sd, const LLInitParam::BaseBlock& block); + +	/*virtual*/ std::string getCurrentElementName(); + +private: +	void submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack); + +	template<typename T> +	static bool writeTypedValue(Parser& parser, const void* val_ptr, parser_t::name_stack_t& name_stack) +	{ +		LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser); +		if (!sdparser.mWriteRootSD) return false; +		 +		LLInitParam::Parser::name_stack_range_t range(name_stack.begin(), name_stack.end()); +		LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range); + +		sd_to_write.assign(*((const T*)val_ptr)); +		return true; +	} + +	static bool writeU32Param(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); +	static bool writeFlag(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack); + +	static bool readFlag(Parser& parser, void* val_ptr); +	static bool readS32(Parser& parser, void* val_ptr); +	static bool readU32(Parser& parser, void* val_ptr); +	static bool readF32(Parser& parser, void* val_ptr); +	static bool readF64(Parser& parser, void* val_ptr); +	static bool readBool(Parser& parser, void* val_ptr); +	static bool readString(Parser& parser, void* val_ptr); +	static bool readUUID(Parser& parser, void* val_ptr); +	static bool readDate(Parser& parser, void* val_ptr); +	static bool readURI(Parser& parser, void* val_ptr); +	static bool readSD(Parser& parser, void* val_ptr); + +	Parser::name_stack_t	mNameStack; +	const LLSD*				mCurReadSD; +	LLSD*					mWriteRootSD; +	LLSD*					mCurWriteSD; +}; + + +extern LL_COMMON_API LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR; +template<typename T> +class LLSDParamAdapter : public T +{ +public: +	LLSDParamAdapter() {} +	LLSDParamAdapter(const LLSD& sd) +	{ +		LLFastTimer _(FTM_SD_PARAM_ADAPTOR); +		LLParamSDParser parser; +		// don't spam for implicit parsing of LLSD, as we want to allow arbitrary freeform data and ignore most of it +		bool parse_silently = true; +		parser.readSD(sd, *this, parse_silently); +	} + +	operator LLSD() const +	{ +		LLParamSDParser parser; +		LLSD sd; +		parser.writeSD(sd, *this); +		return sd; +	} +		 +	LLSDParamAdapter(const T& val) +	: T(val) +	{ +		T::operator=(val); +	} +}; + +#endif // LL_LLSDPARAM_H + diff --git a/indra/llcommon/llstreamqueue.cpp b/indra/llcommon/llstreamqueue.cpp new file mode 100644 index 0000000000..1116a2b6a2 --- /dev/null +++ b/indra/llcommon/llstreamqueue.cpp @@ -0,0 +1,24 @@ +/** + * @file   llstreamqueue.cpp + * @author Nat Goodspeed + * @date   2012-01-05 + * @brief  Implementation for llstreamqueue. + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llstreamqueue.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +// As of this writing, llstreamqueue.h is entirely template-based, therefore +// we don't strictly need a corresponding .cpp file. However, our CMake test +// macro assumes one. Here it is. +bool llstreamqueue_cpp_ignored = true; diff --git a/indra/llcommon/llstreamqueue.h b/indra/llcommon/llstreamqueue.h new file mode 100644 index 0000000000..0726bad175 --- /dev/null +++ b/indra/llcommon/llstreamqueue.h @@ -0,0 +1,240 @@ +/** + * @file   llstreamqueue.h + * @author Nat Goodspeed + * @date   2012-01-04 + * @brief  Definition of LLStreamQueue + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLSTREAMQUEUE_H) +#define LL_LLSTREAMQUEUE_H + +#include <string> +#include <list> +#include <iosfwd>                   // std::streamsize +#include <boost/iostreams/categories.hpp> + +/** + * This class is a growable buffer between a producer and consumer. It serves + * as a queue usable with Boost.Iostreams -- hence, a "stream queue." + * + * This is especially useful for buffering nonblocking I/O. For instance, we + * want application logic to be able to serialize LLSD to a std::ostream. We + * may write more data than the destination pipe can handle all at once, but + * it's imperative NOT to block the application-level serialization call. So + * we buffer it instead. Successive frames can try nonblocking writes to the + * destination pipe until all buffered data has been sent. + * + * Similarly, we want application logic be able to deserialize LLSD from a + * std::istream. Again, we must not block that deserialize call waiting for + * more data to arrive from the input pipe! Instead we build up a buffer over + * a number of frames, using successive nonblocking reads, until we have + * "enough" data to be able to present it through a std::istream. + * + * @note The use cases for this class overlap somewhat with those for the + * LLIOPipe/LLPumpIO hierarchies, and indeed we considered using those. This + * class has two virtues over the older machinery: + * + * # It's vastly simpler -- way fewer concepts. It's not clear to me whether + *   there were ever LLIOPipe/etc. use cases that demanded all the fanciness + *   rolled in, or whether they were simply overdesigned. In any case, no + *   remaining Lindens will admit to familiarity with those classes -- and + *   they're sufficiently obtuse that it would take considerable learning + *   curve to figure out how to use them properly. The bottom line is that + *   current management is not keen on any more engineers climbing that curve. + * # This class is designed around available components such as std::string, + *   std::list, Boost.Iostreams. There's less proprietary code. + */ +template <typename Ch> +class LLGenericStreamQueue +{ +public: +    LLGenericStreamQueue(): +        mSize(0), +        mClosed(false) +    {} + +    /** +     * Boost.Iostreams Source Device facade for use with other Boost.Iostreams +     * functionality. LLGenericStreamQueue doesn't quite fit any of the Boost +     * 1.48 Iostreams concepts; instead it behaves as both a Sink and a +     * Source. This is its Source facade. +     */ +    struct Source +    { +        typedef Ch char_type; +        typedef boost::iostreams::source_tag category; + +        /// Bind the underlying LLGenericStreamQueue +        Source(LLGenericStreamQueue& sq): +            mStreamQueue(sq) +        {} + +        // Read up to n characters from the underlying data source into the +        // buffer s, returning the number of characters read; return -1 to +        // indicate EOF +        std::streamsize read(Ch* s, std::streamsize n) +        { +            return mStreamQueue.read(s, n); +        } + +        LLGenericStreamQueue& mStreamQueue; +    }; + +    /** +     * Boost.Iostreams Sink Device facade for use with other Boost.Iostreams +     * functionality. LLGenericStreamQueue doesn't quite fit any of the Boost +     * 1.48 Iostreams concepts; instead it behaves as both a Sink and a +     * Source. This is its Sink facade. +     */ +    struct Sink +    { +        typedef Ch char_type; +        typedef boost::iostreams::sink_tag category; + +        /// Bind the underlying LLGenericStreamQueue +        Sink(LLGenericStreamQueue& sq): +            mStreamQueue(sq) +        {} + +        /// Write up to n characters from the buffer s to the output sequence, +        /// returning the number of characters written +        std::streamsize write(const Ch* s, std::streamsize n) +        { +            return mStreamQueue.write(s, n); +        } + +        /// Send EOF to consumer +        void close() +        { +            mStreamQueue.close(); +        } + +        LLGenericStreamQueue& mStreamQueue; +    }; + +    /// Present Boost.Iostreams Source facade +    Source asSource() { return Source(*this); } +    /// Present Boost.Iostreams Sink facade +    Sink   asSink()   { return Sink(*this); } + +    /// append data to buffer +    std::streamsize write(const Ch* s, std::streamsize n) +    { +        // Unclear how often we might be asked to write 0 bytes -- perhaps a +        // naive caller responding to an unready nonblocking read. But if we +        // do get such a call, don't add a completely empty BufferList entry. +        if (n == 0) +            return n; +        // We could implement this using a single std::string object, a la +        // ostringstream. But the trouble with appending to a string is that +        // you might have to recopy all previous contents to grow its size. If +        // we want this to scale to large data volumes, better to allocate +        // individual pieces. +        mBuffer.push_back(string(s, n)); +        mSize += n; +        return n; +    } + +    /** +     * Inform this LLGenericStreamQueue that no further data are forthcoming. +     * For our purposes, close() is strictly a producer-side operation; +     * there's little point in closing the consumer side. +     */ +    void close() +    { +        mClosed = true; +    } + +    /// consume data from buffer +    std::streamsize read(Ch* s, std::streamsize n) +    { +        // read() is actually a convenience method for peek() followed by +        // skip(). +        std::streamsize got(peek(s, n)); +        // We can only skip() as many characters as we can peek(); ignore +        // skip() return here. +        skip(n); +        return got; +    } + +    /// Retrieve data from buffer without consuming. Like read(), return -1 on +    /// EOF. +    std::streamsize peek(Ch* s, std::streamsize n) const; + +    /// Consume data from buffer without retrieving. Unlike read() and peek(), +    /// at EOF we simply skip 0 characters. +    std::streamsize skip(std::streamsize n); + +    /// How many characters do we currently have buffered? +    std::streamsize size() const +    { +        return mSize; +    } + +private: +    typedef std::basic_string<Ch> string; +    typedef std::list<string> BufferList; +    BufferList mBuffer; +    std::streamsize mSize; +    bool mClosed; +}; + +template <typename Ch> +std::streamsize LLGenericStreamQueue<Ch>::peek(Ch* s, std::streamsize n) const +{ +    // Here we may have to build up 'n' characters from an arbitrary +    // number of individual BufferList entries. +    typename BufferList::const_iterator bli(mBuffer.begin()), blend(mBuffer.end()); +    // Indicate EOF if producer has closed the pipe AND we've exhausted +    // all previously-buffered data. +    if (mClosed && bli == blend) +    { +        return -1; +    } +    // Here either producer hasn't yet closed, or we haven't yet exhausted +    // remaining data. +    std::streamsize needed(n), got(0); +    // Loop until either we run out of BufferList entries or we've +    // completely satisfied the request. +    for ( ; bli != blend && needed; ++bli) +    { +        std::streamsize chunk(std::min(needed, std::streamsize(bli->length()))); +        std::copy(bli->begin(), bli->begin() + chunk, s); +        needed -= chunk; +        s      += chunk; +        got    += chunk; +    } +    return got; +} + +template <typename Ch> +std::streamsize LLGenericStreamQueue<Ch>::skip(std::streamsize n) +{ +    typename BufferList::iterator bli(mBuffer.begin()), blend(mBuffer.end()); +    std::streamsize toskip(n), skipped(0); +    while (bli != blend && toskip >= bli->length()) +    { +        std::streamsize chunk(bli->length()); +        typename BufferList::iterator zap(bli++); +        mBuffer.erase(zap); +        mSize   -= chunk; +        toskip  -= chunk; +        skipped += chunk; +    } +    if (bli != blend && toskip) +    { +        bli->erase(bli->begin(), bli->begin() + toskip); +        mSize   -= toskip; +        skipped += toskip; +    } +    return skipped; +} + +typedef LLGenericStreamQueue<char>    LLStreamQueue; +typedef LLGenericStreamQueue<wchar_t> LLWStreamQueue; + +#endif /* ! defined(LL_LLSTREAMQUEUE_H) */ diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 7e41e787b5..7b24b5e279 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -237,40 +237,41 @@ private:  	static std::string sLocale;  public: -	typedef typename std::basic_string<T>::size_type size_type; +	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 std::basic_string<T> null; +	static const string_type null;  	typedef std::map<LLFormatMapString, LLFormatMapString> format_map_t; -	LL_COMMON_API static void getTokens(const std::basic_string<T>& instr, std::vector<std::basic_string<T> >& tokens, const std::basic_string<T>& delims); -	LL_COMMON_API static void formatNumber(std::basic_string<T>& numStr, std::basic_string<T> decimals); -	LL_COMMON_API static bool formatDatetime(std::basic_string<T>& replacement, std::basic_string<T> token, std::basic_string<T> param, S32 secFromEpoch); -	LL_COMMON_API static S32 format(std::basic_string<T>& s, const format_map_t& substitutions); -	LL_COMMON_API static S32 format(std::basic_string<T>& s, const LLSD& substitutions); -	LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const format_map_t& substitutions); -	LL_COMMON_API static bool simpleReplacement(std::basic_string<T>& replacement, std::basic_string<T> token, const LLSD& substitutions); +	LL_COMMON_API static void getTokens(const string_type& instr, std::vector<string_type >& tokens, const string_type& delims); +	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 std::basic_string<T>& string, size_type i) +	static bool isValidIndex(const string_type& string, size_type i)  	{  		return !string.empty() && (0 <= i) && (i <= string.size());  	} -	static void	trimHead(std::basic_string<T>& string); -	static void	trimTail(std::basic_string<T>& string); -	static void	trim(std::basic_string<T>& string)	{ trimHead(string); trimTail(string); } -	static void truncate(std::basic_string<T>& string, size_type count); +	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(std::basic_string<T>& string); -	static void	toLower(std::basic_string<T>& string); +	static void	toUpper(string_type& string); +	static void	toLower(string_type& string);  	// True if this is the head of s. -	static BOOL	isHead( const std::basic_string<T>& string, const T* s );  +	static BOOL	isHead( const string_type& string, const T* s );   	/**  	 * @brief Returns true if string starts with substr @@ -278,8 +279,8 @@ public:  	 * If etither string or substr are empty, this method returns false.  	 */  	static bool startsWith( -		const std::basic_string<T>& string, -		const std::basic_string<T>& substr); +		const string_type& string, +		const string_type& substr);  	/**  	 * @brief Returns true if string ends in substr @@ -287,19 +288,32 @@ public:  	 * If etither string or substr are empty, this method returns false.  	 */  	static bool endsWith( -		const std::basic_string<T>& string, -		const std::basic_string<T>& substr); +		const string_type& string, +		const string_type& substr); -	static void	addCRLF(std::basic_string<T>& string); -	static void	removeCRLF(std::basic_string<T>& string); +	static void	addCRLF(string_type& string); +	static void	removeCRLF(string_type& string); -	static void	replaceTabsWithSpaces( std::basic_string<T>& string, size_type spaces_per_tab ); -	static void	replaceNonstandardASCII( std::basic_string<T>& string, T replacement ); -	static void	replaceChar( std::basic_string<T>& string, T target, T replacement ); -	static void replaceString( std::basic_string<T>& string, std::basic_string<T> target, std::basic_string<T> replacement ); +	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 BOOL	containsNonprintable(const std::basic_string<T>& string); -	static void	stripNonprintable(std::basic_string<T>& string); +	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 @@ -308,18 +322,18 @@ public:  	 * The 2 and 4 byte std::string probably work, so LLWStringUtil::_makeASCII  	 * should work.  	 */ -	static void _makeASCII(std::basic_string<T>& string); +	static void _makeASCII(string_type& string);  	// Conversion to other data types -	static BOOL	convertToBOOL(const std::basic_string<T>& string, BOOL& value); -	static BOOL	convertToU8(const std::basic_string<T>& string, U8& value); -	static BOOL	convertToS8(const std::basic_string<T>& string, S8& value); -	static BOOL	convertToS16(const std::basic_string<T>& string, S16& value); -	static BOOL	convertToU16(const std::basic_string<T>& string, U16& value); -	static BOOL	convertToU32(const std::basic_string<T>& string, U32& value); -	static BOOL	convertToS32(const std::basic_string<T>& string, S32& value); -	static BOOL	convertToF32(const std::basic_string<T>& string, F32& value); -	static BOOL	convertToF64(const std::basic_string<T>& string, F64& value); +	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 @@ -327,24 +341,24 @@ public:  	// Like strcmp but also handles empty strings. Uses  	// current locale.  	static S32		compareStrings(const T* lhs, const T* rhs); -	static S32		compareStrings(const std::basic_string<T>& lhs, const std::basic_string<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 std::basic_string<T>& lhs, const std::basic_string<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 std::basic_string<T>& a, const std::basic_string<T>& b); +	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 std::basic_string<T>& a, const std::basic_string<T>& b); +	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 std::basic_string<T>& a, const std::basic_string<T>& b ); +	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 @@ -352,7 +366,7 @@ public:  	static void		copy(T* dst, const T* src, size_type dst_size);  	// Copies src into dst at a given offset.   -	static void		copyInto(std::basic_string<T>& dst, const std::basic_string<T>& src, size_type 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); } @@ -362,7 +376,7 @@ public:  #endif  private: -	LL_COMMON_API static size_type getSubstitution(const std::basic_string<T>& instr, size_type& start, std::vector<std::basic_string<T> >& tokens); +	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; @@ -669,7 +683,7 @@ S32 LLStringUtilBase<T>::compareStrings(const T* lhs, const T* rhs)  //static   template<class T>  -S32 LLStringUtilBase<T>::compareStrings(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs) +S32 LLStringUtilBase<T>::compareStrings(const string_type& lhs, const string_type& rhs)  {  	return LLStringOps::collate(lhs.c_str(), rhs.c_str());  } @@ -695,8 +709,8 @@ S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs )  	}  	else  	{ -		std::basic_string<T> lhs_string(lhs); -		std::basic_string<T> rhs_string(rhs); +		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()); @@ -706,10 +720,10 @@ S32 LLStringUtilBase<T>::compareInsensitive(const T* lhs, const T* rhs )  //static   template<class T>  -S32 LLStringUtilBase<T>::compareInsensitive(const std::basic_string<T>& lhs, const std::basic_string<T>& rhs) +S32 LLStringUtilBase<T>::compareInsensitive(const string_type& lhs, const string_type& rhs)  { -	std::basic_string<T> lhs_string(lhs); -	std::basic_string<T> rhs_string(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()); @@ -720,7 +734,7 @@ S32 LLStringUtilBase<T>::compareInsensitive(const std::basic_string<T>& lhs, con  //static   template<class T> -S32 LLStringUtilBase<T>::compareDict(const std::basic_string<T>& astr, const std::basic_string<T>& bstr) +S32 LLStringUtilBase<T>::compareDict(const string_type& astr, const string_type& bstr)  {  	const T* a = astr.c_str();  	const T* b = bstr.c_str(); @@ -761,7 +775,7 @@ S32 LLStringUtilBase<T>::compareDict(const std::basic_string<T>& astr, const std  // static  template<class T> -S32 LLStringUtilBase<T>::compareDictInsensitive(const std::basic_string<T>& astr, const std::basic_string<T>& bstr) +S32 LLStringUtilBase<T>::compareDictInsensitive(const string_type& astr, const string_type& bstr)  {  	const T* a = astr.c_str();  	const T* b = bstr.c_str(); @@ -796,7 +810,7 @@ S32 LLStringUtilBase<T>::compareDictInsensitive(const std::basic_string<T>& astr  // Puts compareDict() in a form appropriate for LL container classes to use for sorting.  // static   template<class T>  -BOOL LLStringUtilBase<T>::precedesDict( const std::basic_string<T>& a, const std::basic_string<T>& b ) +BOOL LLStringUtilBase<T>::precedesDict( const string_type& a, const string_type& b )  {  	if( a.size() && b.size() )  	{ @@ -810,7 +824,7 @@ BOOL LLStringUtilBase<T>::precedesDict( const std::basic_string<T>& a, const std  //static  template<class T>  -void LLStringUtilBase<T>::toUpper(std::basic_string<T>& string)	 +void LLStringUtilBase<T>::toUpper(string_type& string)	  {   	if( !string.empty() )  	{  @@ -824,7 +838,7 @@ void LLStringUtilBase<T>::toUpper(std::basic_string<T>& string)  //static  template<class T>  -void LLStringUtilBase<T>::toLower(std::basic_string<T>& string) +void LLStringUtilBase<T>::toLower(string_type& string)  {   	if( !string.empty() )  	{  @@ -838,7 +852,7 @@ void LLStringUtilBase<T>::toLower(std::basic_string<T>& string)  //static  template<class T>  -void LLStringUtilBase<T>::trimHead(std::basic_string<T>& string) +void LLStringUtilBase<T>::trimHead(string_type& string)  {			  	if( !string.empty() )  	{ @@ -853,7 +867,7 @@ void LLStringUtilBase<T>::trimHead(std::basic_string<T>& string)  //static  template<class T>  -void LLStringUtilBase<T>::trimTail(std::basic_string<T>& string) +void LLStringUtilBase<T>::trimTail(string_type& string)  {			  	if( string.size() )  	{ @@ -872,7 +886,7 @@ void LLStringUtilBase<T>::trimTail(std::basic_string<T>& string)  // Replace line feeds with carriage return-line feed pairs.  //static  template<class T> -void LLStringUtilBase<T>::addCRLF(std::basic_string<T>& string) +void LLStringUtilBase<T>::addCRLF(string_type& string)  {  	const T LF = 10;  	const T CR = 13; @@ -914,7 +928,7 @@ void LLStringUtilBase<T>::addCRLF(std::basic_string<T>& string)  // Remove all carriage returns  //static  template<class T>  -void LLStringUtilBase<T>::removeCRLF(std::basic_string<T>& string) +void LLStringUtilBase<T>::removeCRLF(string_type& string)  {  	const T CR = 13; @@ -935,10 +949,10 @@ void LLStringUtilBase<T>::removeCRLF(std::basic_string<T>& string)  //static  template<class T>  -void LLStringUtilBase<T>::replaceChar( std::basic_string<T>& string, T target, T replacement ) +void LLStringUtilBase<T>::replaceChar( string_type& string, T target, T replacement )  {  	size_type found_pos = 0; -	while( (found_pos = string.find(target, found_pos)) != std::basic_string<T>::npos )  +	while( (found_pos = string.find(target, found_pos)) != string_type::npos )   	{  		string[found_pos] = replacement;  		found_pos++; // avoid infinite defeat if target == replacement @@ -947,10 +961,10 @@ void LLStringUtilBase<T>::replaceChar( std::basic_string<T>& string, T target, T  //static  template<class T>  -void LLStringUtilBase<T>::replaceString( std::basic_string<T>& string, std::basic_string<T> target, std::basic_string<T> replacement ) +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)) != std::basic_string<T>::npos ) +	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 @@ -959,7 +973,7 @@ void LLStringUtilBase<T>::replaceString( std::basic_string<T>& string, std::basi  //static  template<class T>  -void LLStringUtilBase<T>::replaceNonstandardASCII( std::basic_string<T>& string, T replacement ) +void LLStringUtilBase<T>::replaceNonstandardASCII( string_type& string, T replacement )  {  	const char LF = 10;  	const S8 MIN = 32; @@ -979,12 +993,12 @@ void LLStringUtilBase<T>::replaceNonstandardASCII( std::basic_string<T>& string,  //static  template<class T>  -void LLStringUtilBase<T>::replaceTabsWithSpaces( std::basic_string<T>& str, size_type spaces_per_tab ) +void LLStringUtilBase<T>::replaceTabsWithSpaces( string_type& str, size_type spaces_per_tab )  {  	const T TAB = '\t';  	const T SPACE = ' '; -	std::basic_string<T> out_str; +	string_type out_str;  	// Replace tabs with spaces  	for (size_type i = 0; i < str.length(); i++)  	{ @@ -1003,7 +1017,7 @@ void LLStringUtilBase<T>::replaceTabsWithSpaces( std::basic_string<T>& str, size  //static  template<class T>  -BOOL LLStringUtilBase<T>::containsNonprintable(const std::basic_string<T>& string) +BOOL LLStringUtilBase<T>::containsNonprintable(const string_type& string)  {  	const char MIN = 32;  	BOOL rv = FALSE; @@ -1020,7 +1034,7 @@ BOOL LLStringUtilBase<T>::containsNonprintable(const std::basic_string<T>& strin  //static  template<class T>  -void LLStringUtilBase<T>::stripNonprintable(std::basic_string<T>& string) +void LLStringUtilBase<T>::stripNonprintable(string_type& string)  {  	const char MIN = 32;  	size_type j = 0; @@ -1051,8 +1065,43 @@ void LLStringUtilBase<T>::stripNonprintable(std::basic_string<T>& string)  	delete []c_string;  } +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(std::basic_string<T>& string) +void LLStringUtilBase<T>::_makeASCII(string_type& string)  {  	// Replace non-ASCII chars with LL_UNKNOWN_CHAR  	for (size_type i = 0; i < string.length(); i++) @@ -1082,7 +1131,7 @@ void LLStringUtilBase<T>::copy( T* dst, const T* src, size_type dst_size )  // static  template<class T>  -void LLStringUtilBase<T>::copyInto(std::basic_string<T>& dst, const std::basic_string<T>& src, size_type offset) +void LLStringUtilBase<T>::copyInto(string_type& dst, const string_type& src, size_type offset)  {  	if ( offset == dst.length() )  	{ @@ -1092,7 +1141,7 @@ void LLStringUtilBase<T>::copyInto(std::basic_string<T>& dst, const std::basic_s  	}  	else  	{ -		std::basic_string<T> tail = dst.substr(offset); +		string_type tail = dst.substr(offset);  		dst = dst.substr(0, offset);  		dst += src; @@ -1103,7 +1152,7 @@ void LLStringUtilBase<T>::copyInto(std::basic_string<T>& dst, const std::basic_s  // True if this is the head of s.  //static  template<class T>  -BOOL LLStringUtilBase<T>::isHead( const std::basic_string<T>& string, const T* s )  +BOOL LLStringUtilBase<T>::isHead( const string_type& string, const T* s )   {   	if( string.empty() )  	{ @@ -1119,8 +1168,8 @@ BOOL LLStringUtilBase<T>::isHead( const std::basic_string<T>& string, const T* s  // static  template<class T>   bool LLStringUtilBase<T>::startsWith( -	const std::basic_string<T>& string, -	const std::basic_string<T>& substr) +	const string_type& string, +	const string_type& substr)  {  	if(string.empty() || (substr.empty())) return false;  	if(0 == string.find(substr)) return true; @@ -1130,8 +1179,8 @@ bool LLStringUtilBase<T>::startsWith(  // static  template<class T>   bool LLStringUtilBase<T>::endsWith( -	const std::basic_string<T>& string, -	const std::basic_string<T>& substr) +	const string_type& string, +	const string_type& substr)  {  	if(string.empty() || (substr.empty())) return false;  	std::string::size_type idx = string.rfind(substr); @@ -1141,14 +1190,14 @@ bool LLStringUtilBase<T>::endsWith(  template<class T>  -BOOL LLStringUtilBase<T>::convertToBOOL(const std::basic_string<T>& string, BOOL& value) +BOOL LLStringUtilBase<T>::convertToBOOL(const string_type& string, BOOL& value)  {  	if( string.empty() )  	{  		return FALSE;  	} -	std::basic_string<T> temp( string ); +	string_type temp( string );  	trim(temp);  	if(   		(temp == "1") ||  @@ -1178,7 +1227,7 @@ BOOL LLStringUtilBase<T>::convertToBOOL(const std::basic_string<T>& string, BOOL  }  template<class T>  -BOOL LLStringUtilBase<T>::convertToU8(const std::basic_string<T>& string, U8& value)  +BOOL LLStringUtilBase<T>::convertToU8(const string_type& string, U8& value)   {  	S32 value32 = 0;  	BOOL success = convertToS32(string, value32); @@ -1191,7 +1240,7 @@ BOOL LLStringUtilBase<T>::convertToU8(const std::basic_string<T>& string, U8& va  }  template<class T>  -BOOL LLStringUtilBase<T>::convertToS8(const std::basic_string<T>& string, S8& value)  +BOOL LLStringUtilBase<T>::convertToS8(const string_type& string, S8& value)   {  	S32 value32 = 0;  	BOOL success = convertToS32(string, value32); @@ -1204,7 +1253,7 @@ BOOL LLStringUtilBase<T>::convertToS8(const std::basic_string<T>& string, S8& va  }  template<class T>  -BOOL LLStringUtilBase<T>::convertToS16(const std::basic_string<T>& string, S16& value)  +BOOL LLStringUtilBase<T>::convertToS16(const string_type& string, S16& value)   {  	S32 value32 = 0;  	BOOL success = convertToS32(string, value32); @@ -1217,7 +1266,7 @@ BOOL LLStringUtilBase<T>::convertToS16(const std::basic_string<T>& string, S16&  }  template<class T>  -BOOL LLStringUtilBase<T>::convertToU16(const std::basic_string<T>& string, U16& value)  +BOOL LLStringUtilBase<T>::convertToU16(const string_type& string, U16& value)   {  	S32 value32 = 0;  	BOOL success = convertToS32(string, value32); @@ -1230,17 +1279,17 @@ BOOL LLStringUtilBase<T>::convertToU16(const std::basic_string<T>& string, U16&  }  template<class T>  -BOOL LLStringUtilBase<T>::convertToU32(const std::basic_string<T>& string, U32& value)  +BOOL LLStringUtilBase<T>::convertToU32(const string_type& string, U32& value)   {  	if( string.empty() )  	{  		return FALSE;  	} -	std::basic_string<T> temp( string ); +	string_type temp( string );  	trim(temp);  	U32 v; -	std::basic_istringstream<T> i_stream((std::basic_string<T>)temp); +	std::basic_istringstream<T> i_stream((string_type)temp);  	if(i_stream >> v)  	{  		value = v; @@ -1250,17 +1299,17 @@ BOOL LLStringUtilBase<T>::convertToU32(const std::basic_string<T>& string, U32&  }  template<class T>  -BOOL LLStringUtilBase<T>::convertToS32(const std::basic_string<T>& string, S32& value)  +BOOL LLStringUtilBase<T>::convertToS32(const string_type& string, S32& value)   {  	if( string.empty() )  	{  		return FALSE;  	} -	std::basic_string<T> temp( string ); +	string_type temp( string );  	trim(temp);  	S32 v; -	std::basic_istringstream<T> i_stream((std::basic_string<T>)temp); +	std::basic_istringstream<T> i_stream((string_type)temp);  	if(i_stream >> v)  	{  		//TODO: figure out overflow and underflow reporting here @@ -1277,7 +1326,7 @@ BOOL LLStringUtilBase<T>::convertToS32(const std::basic_string<T>& string, S32&  }  template<class T>  -BOOL LLStringUtilBase<T>::convertToF32(const std::basic_string<T>& string, F32& value)  +BOOL LLStringUtilBase<T>::convertToF32(const string_type& string, F32& value)   {  	F64 value64 = 0.0;  	BOOL success = convertToF64(string, value64); @@ -1290,17 +1339,17 @@ BOOL LLStringUtilBase<T>::convertToF32(const std::basic_string<T>& string, F32&  }  template<class T>  -BOOL LLStringUtilBase<T>::convertToF64(const std::basic_string<T>& string, F64& value) +BOOL LLStringUtilBase<T>::convertToF64(const string_type& string, F64& value)  {  	if( string.empty() )  	{  		return FALSE;  	} -	std::basic_string<T> temp( string ); +	string_type temp( string );  	trim(temp);  	F64 v; -	std::basic_istringstream<T> i_stream((std::basic_string<T>)temp); +	std::basic_istringstream<T> i_stream((string_type)temp);  	if(i_stream >> v)  	{  		//TODO: figure out overflow and underflow reporting here @@ -1317,7 +1366,7 @@ BOOL LLStringUtilBase<T>::convertToF64(const std::basic_string<T>& string, F64&  }  template<class T>  -void LLStringUtilBase<T>::truncate(std::basic_string<T>& string, size_type count) +void LLStringUtilBase<T>::truncate(string_type& string, size_type count)  {  	size_type cur_size = string.size();  	string.resize(count < cur_size ? count : cur_size); diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp new file mode 100644 index 0000000000..6d2292fb88 --- /dev/null +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -0,0 +1,706 @@ +/** + * @file   llprocess_test.cpp + * @author Nat Goodspeed + * @date   2011-12-19 + * @brief  Test for llprocess. + *  + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Copyright (c) 2011, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llprocess.h" +// STL headers +#include <vector> +#include <list> +// std headers +#include <fstream> +// external library headers +#include "llapr.h" +#include "apr_thread_proc.h" +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/algorithm/string/find_iterator.hpp> +#include <boost/algorithm/string/finder.hpp> +//#include <boost/lambda/lambda.hpp> +//#include <boost/lambda/bind.hpp> +// other Linden headers +#include "../test/lltut.h" +#include "../test/manageapr.h" +#include "../test/namedtempfile.h" +#include "stringize.h" +#include "llsdutil.h" + +#if defined(LL_WINDOWS) +#define sleep(secs) _sleep((secs) * 1000) +#define EOL "\r\n" +#else +#define EOL "\n" +#include <sys/wait.h> +#endif + +//namespace lambda = boost::lambda; + +// static instance of this manages APR init/cleanup +static ManageAPR manager; + +/***************************************************************************** +*   Helpers +*****************************************************************************/ + +#define ensure_equals_(left, right) \ +        ensure_equals(STRINGIZE(#left << " != " << #right), (left), (right)) + +#define aprchk(expr) aprchk_(#expr, (expr)) +static void aprchk_(const char* call, apr_status_t rv, apr_status_t expected=APR_SUCCESS) +{ +    tut::ensure_equals(STRINGIZE(call << " => " << rv << ": " << manager.strerror(rv)), +                       rv, expected); +} + +/** + * Read specified file using std::getline(). It is assumed to be an error if + * the file is empty: don't use this function if that's an acceptable case. + * Last line will not end with '\n'; this is to facilitate the usual case of + * string compares with a single line of output. + * @param pathname The file to read. + * @param desc Optional description of the file for error message; + * defaults to "in <pathname>" + */ +static std::string readfile(const std::string& pathname, const std::string& desc="") +{ +    std::string use_desc(desc); +    if (use_desc.empty()) +    { +        use_desc = STRINGIZE("in " << pathname); +    } +    std::ifstream inf(pathname.c_str()); +    std::string output; +    tut::ensure(STRINGIZE("No output " << use_desc), std::getline(inf, output)); +    std::string more; +    while (std::getline(inf, more)) +    { +        output += '\n' + more; +    } +    return output; +} + +/** + * Construct an LLProcess to run a Python script. + */ +struct PythonProcessLauncher +{ +    /** +     * @param desc Arbitrary description for error messages +     * @param script Python script, any form acceptable to NamedTempFile, +     * typically either a std::string or an expression of the form +     * (lambda::_1 << "script content with " << variable_data) +     */ +    template <typename CONTENT> +    PythonProcessLauncher(const std::string& desc, const CONTENT& script): +        mDesc(desc), +        mScript("py", script) +    { +        const char* PYTHON(getenv("PYTHON")); +        tut::ensure("Set $PYTHON to the Python interpreter", PYTHON); + +        mParams.executable = PYTHON; +        mParams.args.add(mScript.getName()); +    } + +    /// Run Python script and wait for it to complete. +    void run() +    { +        mPy = LLProcess::create(mParams); +        tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), mPy); +        // One of the irritating things about LLProcess is that +        // there's no API to wait for the child to terminate -- but given +        // its use in our graphics-intensive interactive viewer, it's +        // understandable. +        while (mPy->isRunning()) +        { +            sleep(1); +        } +    } + +    /** +     * Run a Python script using LLProcess, expecting that it will +     * write to the file passed as its sys.argv[1]. Retrieve that output. +     * +     * Until January 2012, LLProcess provided distressingly few +     * mechanisms for a child process to communicate back to its caller -- +     * not even its return code. We've introduced a convention by which we +     * create an empty temp file, pass the name of that file to our child +     * as sys.argv[1] and expect the script to write its output to that +     * file. This function implements the C++ (parent process) side of +     * that convention. +     */ +    std::string run_read() +    { +        NamedTempFile out("out", ""); // placeholder +        // pass name of this temporary file to the script +        mParams.args.add(out.getName()); +        run(); +        // assuming the script wrote to that file, read it +        return readfile(out.getName(), STRINGIZE("from " << mDesc << " script")); +    } + +    LLProcess::Params mParams; +    LLProcessPtr mPy; +    std::string mDesc; +    NamedTempFile mScript; +}; + +/// convenience function for PythonProcessLauncher::run() +template <typename CONTENT> +static void python(const std::string& desc, const CONTENT& script) +{ +    PythonProcessLauncher py(desc, script); +    py.run(); +} + +/// convenience function for PythonProcessLauncher::run_read() +template <typename CONTENT> +static std::string python_out(const std::string& desc, const CONTENT& script) +{ +    PythonProcessLauncher py(desc, script); +    return py.run_read(); +} + +/// Create a temporary directory and clean it up later. +class NamedTempDir: public boost::noncopyable +{ +public: +    // Use python() function to create a temp directory: I've found +    // nothing in either Boost.Filesystem or APR quite like Python's +    // tempfile.mkdtemp(). +    // Special extra bonus: on Mac, mkdtemp() reports a pathname +    // starting with /var/folders/something, whereas that's really a +    // symlink to /private/var/folders/something. Have to use +    // realpath() to compare properly. +    NamedTempDir(): +        mPath(python_out("mkdtemp()", +                         "from __future__ import with_statement\n" +                         "import os.path, sys, tempfile\n" +                         "with open(sys.argv[1], 'w') as f:\n" +                         "    f.write(os.path.normcase(os.path.normpath(os.path.realpath(tempfile.mkdtemp()))))\n")) +    {} + +    ~NamedTempDir() +    { +        aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp)); +    } + +    std::string getName() const { return mPath; } + +private: +    std::string mPath; +}; + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct llprocess_data +    { +        LLAPRPool pool; +    }; +    typedef test_group<llprocess_data> llprocess_group; +    typedef llprocess_group::object object; +    llprocess_group llprocessgrp("llprocess"); + +    struct Item +    { +        Item(): tries(0) {} +        unsigned    tries; +        std::string which; +        std::string what; +    }; + +/*==========================================================================*| +#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 +|*==========================================================================*/ + +    struct WaitInfo +    { +        WaitInfo(apr_proc_t* child_): +            child(child_), +            rv(-1),                 // we haven't yet called apr_proc_wait() +            rc(0), +            why(apr_exit_why_e(0)) +        {} +        apr_proc_t* child;          // which subprocess +        apr_status_t rv;            // return from apr_proc_wait() +        int rc;                     // child's exit code +        apr_exit_why_e why;         // APR_PROC_EXIT, APR_PROC_SIGNAL, APR_PROC_SIGNAL_CORE +    }; + +    void child_status_callback(int reason, void* data, int status) +    { +/*==========================================================================*| +        std::string reason_str; +        BOOST_FOREACH(const ReasonCode& rcp, reasons) +        { +            if (reason == rcp.code) +            { +                reason_str = rcp.name; +                break; +            } +        } +        if (reason_str.empty()) +        { +            reason_str = STRINGIZE("unknown reason " << reason); +        } +        std::cout << "child_status_callback(" << reason_str << ")\n"; +|*==========================================================================*/ + +        if (reason == APR_OC_REASON_DEATH || reason == APR_OC_REASON_LOST) +        { +            // Somewhat oddly, APR requires that you explicitly unregister +            // even when it already knows the child has terminated. +            apr_proc_other_child_unregister(data); + +            WaitInfo* wi(static_cast<WaitInfo*>(data)); +            // 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. +//          wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT); +            wi->rv = APR_CHILD_DONE; // fake apr_proc_wait() results +#if defined(LL_WINDOWS) +            wi->why = APR_PROC_EXIT; +            wi->rc  = status;         // no encoding on Windows (no signals) +#else  // Posix +            if (WIFEXITED(status)) +            { +                wi->why = APR_PROC_EXIT; +                wi->rc  = WEXITSTATUS(status); +            } +            else if (WIFSIGNALED(status)) +            { +                wi->why = APR_PROC_SIGNAL; +                wi->rc  = WTERMSIG(status); +            } +            else                    // uh, shouldn't happen? +            { +                wi->why = APR_PROC_EXIT; +                wi->rc  = status;   // someone else will have to decode +            } +#endif // Posix +        } +    } + +    template<> template<> +    void object::test<1>() +    { +        set_test_name("raw APR nonblocking I/O"); + +        // Create a script file in a temporary place. +        NamedTempFile script("py", +            "import sys" EOL +            "import time" EOL +            EOL +            "time.sleep(2)" EOL +            "print >>sys.stdout, 'stdout after wait'" EOL +            "sys.stdout.flush()" EOL +            "time.sleep(2)" EOL +            "print >>sys.stderr, 'stderr after wait'" EOL +            "sys.stderr.flush()" EOL +            ); + +        // Arrange to track the history of our interaction with child: what we +        // fetched, which pipe it came from, how many tries it took before we +        // got it. +        std::vector<Item> history; +        history.push_back(Item()); + +        // Run the child process. +        apr_procattr_t *procattr = NULL; +        aprchk(apr_procattr_create(&procattr, pool.getAPRPool())); +        aprchk(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK)); +        aprchk(apr_procattr_cmdtype_set(procattr, APR_PROGRAM_PATH)); + +        std::vector<const char*> argv; +        apr_proc_t child; +        argv.push_back("python"); +        // Have to have a named copy of this std::string so its c_str() value +        // will persist. +        std::string scriptname(script.getName()); +        argv.push_back(scriptname.c_str()); +        argv.push_back(NULL); + +        aprchk(apr_proc_create(&child, argv[0], +                               &argv[0], +                               NULL, // if we wanted to pass explicit environment +                               procattr, +                               pool.getAPRPool())); + +        // We do not want this child process to outlive our APR pool. On +        // destruction of the pool, forcibly kill the process. Tell APR to try +        // SIGTERM and wait 3 seconds. If that didn't work, use SIGKILL. +        apr_pool_note_subprocess(pool.getAPRPool(), &child, APR_KILL_AFTER_TIMEOUT); + +        // arrange to call child_status_callback() +        WaitInfo wi(&child); +        apr_proc_other_child_register(&child, child_status_callback, &wi, child.in, pool.getAPRPool()); + +        // TODO: +        // Stuff child.in until it (would) block to verify EWOULDBLOCK/EAGAIN. +        // Have child script clear it later, then write one more line to prove +        // that it gets through. + +        // Monitor two different output pipes. Because one will be closed +        // before the other, keep them in a list so we can drop whichever of +        // them is closed first. +        typedef std::pair<std::string, apr_file_t*> DescFile; +        typedef std::list<DescFile> DescFileList; +        DescFileList outfiles; +        outfiles.push_back(DescFile("out", child.out)); +        outfiles.push_back(DescFile("err", child.err)); + +        while (! outfiles.empty()) +        { +            // This peculiar for loop is designed to let us erase(dfli). With +            // a list, that invalidates only dfli itself -- but even so, we +            // lose the ability to increment it for the next item. So at the +            // top of every loop, while dfli is still valid, increment +            // dflnext. Then before the next iteration, set dfli to dflnext. +            for (DescFileList::iterator +                     dfli(outfiles.begin()), dflnext(outfiles.begin()), dflend(outfiles.end()); +                 dfli != dflend; dfli = dflnext) +            { +                // Only valid to increment dflnext once we're sure it's not +                // already at dflend. +                ++dflnext; + +                char buf[4096]; + +                apr_status_t rv = apr_file_gets(buf, sizeof(buf), dfli->second); +                if (APR_STATUS_IS_EOF(rv)) +                { +//                  std::cout << "(EOF on " << dfli->first << ")\n"; +//                  history.back().which = dfli->first; +//                  history.back().what  = "*eof*"; +//                  history.push_back(Item()); +                    outfiles.erase(dfli); +                    continue; +                } +                if (rv == EWOULDBLOCK || rv == EAGAIN) +                { +//                  std::cout << "(waiting; apr_file_gets(" << dfli->first << ") => " << rv << ": " << manager.strerror(rv) << ")\n"; +                    ++history.back().tries; +                    continue; +                } +                aprchk_("apr_file_gets(buf, sizeof(buf), dfli->second)", rv); +                // Is it even possible to get APR_SUCCESS but read 0 bytes? +                // Hope not, but defend against that anyway. +                if (buf[0]) +                { +//                  std::cout << dfli->first << ": " << buf; +                    history.back().which = dfli->first; +                    history.back().what.append(buf); +                    if (buf[strlen(buf) - 1] == '\n') +                        history.push_back(Item()); +                    else +                    { +                        // Just for pretty output... if we only read a partial +                        // line, terminate it. +//                      std::cout << "...\n"; +                    } +                } +            } +            // Do this once per tick, as we expect the viewer will +            apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); +            sleep(1); +        } +        apr_file_close(child.in); +        apr_file_close(child.out); +        apr_file_close(child.err); + +        // Okay, we've broken the loop because our pipes are all closed. If we +        // haven't yet called wait, give the callback one more chance. This +        // models the fact that unlike this small test program, the viewer +        // will still be running. +        if (wi.rv == -1) +        { +            std::cout << "last gasp apr_proc_other_child_refresh_all()\n"; +            apr_proc_other_child_refresh_all(APR_OC_REASON_RUNNING); +        } + +        if (wi.rv == -1) +        { +            std::cout << "child_status_callback(APR_OC_REASON_DEATH) wasn't called" << std::endl; +            wi.rv = apr_proc_wait(wi.child, &wi.rc, &wi.why, APR_NOWAIT); +        } +//      std::cout << "child done: rv = " << rv << " (" << manager.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n'; +        aprchk_("apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT)", wi.rv, APR_CHILD_DONE); +        ensure_equals_(wi.why, APR_PROC_EXIT); +        ensure_equals_(wi.rc, 0); + +        // Beyond merely executing all the above successfully, verify that we +        // obtained expected output -- and that we duly got control while +        // waiting, proving the non-blocking nature of these pipes. +        try +        { +            unsigned i = 0; +            ensure("blocking I/O on child pipe (0)", history[i].tries); +            ensure_equals_(history[i].which, "out"); +            ensure_equals_(history[i].what,  "stdout after wait" EOL); +//          ++i; +//          ensure_equals_(history[i].which, "out"); +//          ensure_equals_(history[i].what,  "*eof*"); +            ++i; +            ensure("blocking I/O on child pipe (1)", history[i].tries); +            ensure_equals_(history[i].which, "err"); +            ensure_equals_(history[i].what,  "stderr after wait" EOL); +//          ++i; +//          ensure_equals_(history[i].which, "err"); +//          ensure_equals_(history[i].what,  "*eof*"); +        } +        catch (const failure&) +        { +            std::cout << "History:\n"; +            BOOST_FOREACH(const Item& item, history) +            { +                std::string what(item.what); +                if ((! what.empty()) && what[what.length() - 1] == '\n') +                { +                    what.erase(what.length() - 1); +                    if ((! what.empty()) && what[what.length() - 1] == '\r') +                    { +                        what.erase(what.length() - 1); +                        what.append("\\r"); +                    } +                    what.append("\\n"); +                } +                std::cout << "  " << item.which << ": '" << what << "' (" +                          << item.tries << " tries)\n"; +            } +            std::cout << std::flush; +            // re-raise same error; just want to enrich the output +            throw; +        } +    } + +    template<> template<> +    void object::test<2>() +    { +        set_test_name("setWorkingDirectory()"); +        // We want to test setWorkingDirectory(). But what directory is +        // guaranteed to exist on every machine, under every OS? Have to +        // create one. Naturally, ensure we clean it up when done. +        NamedTempDir tempdir; +        PythonProcessLauncher py("getcwd()", +                                 "from __future__ import with_statement\n" +                                 "import os, sys\n" +                                 "with open(sys.argv[1], 'w') as f:\n" +                                 "    f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n"); +        // Before running, call setWorkingDirectory() +        py.mParams.cwd = tempdir.getName(); +        ensure_equals("os.getcwd()", py.run_read(), tempdir.getName()); +    } + +    template<> template<> +    void object::test<3>() +    { +        set_test_name("arguments"); +        PythonProcessLauncher py("args", +                                 "from __future__ import with_statement\n" +                                 "import sys\n" +                                 // note nonstandard output-file arg! +                                 "with open(sys.argv[3], 'w') as f:\n" +                                 "    for arg in sys.argv[1:]:\n" +                                 "        print >>f, arg\n"); +        // We expect that PythonProcessLauncher has already appended +        // its own NamedTempFile to mParams.args (sys.argv[0]). +        py.mParams.args.add("first arg");          // sys.argv[1] +        py.mParams.args.add("second arg");         // sys.argv[2] +        // run_read() appends() one more argument, hence [3] +        std::string output(py.run_read()); +        boost::split_iterator<std::string::const_iterator> +            li(output, boost::first_finder("\n")), lend; +        ensure("didn't get first arg", li != lend); +        std::string arg(li->begin(), li->end()); +        ensure_equals(arg, "first arg"); +        ++li; +        ensure("didn't get second arg", li != lend); +        arg.assign(li->begin(), li->end()); +        ensure_equals(arg, "second arg"); +        ++li; +        ensure("didn't get output filename?!", li != lend); +        arg.assign(li->begin(), li->end()); +        ensure("output filename empty?!", ! arg.empty()); +        ++li; +        ensure("too many args", li == lend); +    } + +    template<> template<> +    void object::test<4>() +    { +        set_test_name("explicit kill()"); +        PythonProcessLauncher py("kill()", +                                 "from __future__ import with_statement\n" +                                 "import sys, time\n" +                                 "with open(sys.argv[1], 'w') as f:\n" +                                 "    f.write('ok')\n" +                                 "# now sleep; expect caller to kill\n" +                                 "time.sleep(120)\n" +                                 "# if caller hasn't managed to kill by now, bad\n" +                                 "with open(sys.argv[1], 'w') as f:\n" +                                 "    f.write('bad')\n"); +        NamedTempFile out("out", "not started"); +        py.mParams.args.add(out.getName()); +        py.mPy = LLProcess::create(py.mParams); +        ensure("couldn't launch kill() script", py.mPy); +        // Wait for the script to wake up and do its first write +        int i = 0, timeout = 60; +        for ( ; i < timeout; ++i) +        { +            sleep(1); +            if (readfile(out.getName(), "from kill() script") == "ok") +                break; +        } +        // If we broke this loop because of the counter, something's wrong +        ensure("script never started", i < timeout); +        // script has performed its first write and should now be sleeping. +        py.mPy->kill(); +        // wait for the script to terminate... one way or another. +        while (py.mPy->isRunning()) +        { +            sleep(1); +        } +        // If kill() failed, the script would have woken up on its own and +        // overwritten the file with 'bad'. But if kill() succeeded, it should +        // not have had that chance. +        ensure_equals("kill() script output", readfile(out.getName()), "ok"); +    } + +    template<> template<> +    void object::test<5>() +    { +        set_test_name("implicit kill()"); +        NamedTempFile out("out", "not started"); +        LLProcess::handle phandle(0); +        { +            PythonProcessLauncher py("kill()", +                                     "from __future__ import with_statement\n" +                                     "import sys, time\n" +                                     "with open(sys.argv[1], 'w') as f:\n" +                                     "    f.write('ok')\n" +                                     "# now sleep; expect caller to kill\n" +                                     "time.sleep(120)\n" +                                     "# if caller hasn't managed to kill by now, bad\n" +                                     "with open(sys.argv[1], 'w') as f:\n" +                                     "    f.write('bad')\n"); +            py.mParams.args.add(out.getName()); +            py.mPy = LLProcess::create(py.mParams); +            ensure("couldn't launch kill() script", py.mPy); +            // Capture handle for later +            phandle = py.mPy->getProcessHandle(); +            // Wait for the script to wake up and do its first write +            int i = 0, timeout = 60; +            for ( ; i < timeout; ++i) +            { +                sleep(1); +                if (readfile(out.getName(), "from kill() script") == "ok") +                    break; +            } +            // If we broke this loop because of the counter, something's wrong +            ensure("script never started", i < timeout); +            // Script has performed its first write and should now be sleeping. +            // Destroy the LLProcess, which should kill the child. +        } +        // wait for the script to terminate... one way or another. +        while (LLProcess::isRunning(phandle, "kill() script")) +        { +            sleep(1); +        } +        // If kill() failed, the script would have woken up on its own and +        // overwritten the file with 'bad'. But if kill() succeeded, it should +        // not have had that chance. +        ensure_equals("kill() script output", readfile(out.getName()), "ok"); +    } + +    template<> template<> +    void object::test<6>() +    { +        set_test_name("autokill=false"); +        NamedTempFile from("from", "not started"); +        NamedTempFile to("to", ""); +        LLProcess::handle phandle(0); +        { +            PythonProcessLauncher py("autokill", +                                     "from __future__ import with_statement\n" +                                     "import sys, time\n" +                                     "with open(sys.argv[1], 'w') as f:\n" +                                     "    f.write('ok')\n" +                                     "# wait for 'go' from test program\n" +                                     "for i in xrange(60):\n" +                                     "    time.sleep(1)\n" +                                     "    with open(sys.argv[2]) as f:\n" +                                     "        go = f.read()\n" +                                     "    if go == 'go':\n" +                                     "        break\n" +                                     "else:\n" +                                     "    with open(sys.argv[1], 'w') as f:\n" +                                     "        f.write('never saw go')\n" +                                     "    sys.exit(1)\n" +                                     "# okay, saw 'go', write 'ack'\n" +                                     "with open(sys.argv[1], 'w') as f:\n" +                                     "    f.write('ack')\n"); +            py.mParams.args.add(from.getName()); +            py.mParams.args.add(to.getName()); +            py.mParams.autokill = false; +            py.mPy = LLProcess::create(py.mParams); +            ensure("couldn't launch kill() script", py.mPy); +            // Capture handle for later +            phandle = py.mPy->getProcessHandle(); +            // Wait for the script to wake up and do its first write +            int i = 0, timeout = 60; +            for ( ; i < timeout; ++i) +            { +                sleep(1); +                if (readfile(from.getName(), "from autokill script") == "ok") +                    break; +            } +            // If we broke this loop because of the counter, something's wrong +            ensure("script never started", i < timeout); +            // Now destroy the LLProcess, which should NOT kill the child! +        } +        // If the destructor killed the child anyway, give it time to die +        sleep(2); +        // How do we know it's not terminated? By making it respond to +        // a specific stimulus in a specific way. +        { +            std::ofstream outf(to.getName().c_str()); +            outf << "go"; +        } // flush and close. +        // now wait for the script to terminate... one way or another. +        while (LLProcess::isRunning(phandle, "autokill script")) +        { +            sleep(1); +        } +        // If the LLProcess destructor implicitly called kill(), the +        // script could not have written 'ack' as we expect. +        ensure_equals("autokill script output", readfile(from.getName()), "ack"); +    } +} // namespace tut diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 72322c3b72..e625545763 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -40,41 +40,15 @@ typedef U32 uint32_t;  #include <fcntl.h>  #include <sys/stat.h>  #include <sys/wait.h> -#include "llprocesslauncher.h" +#include "llprocess.h"  #endif -#include <sstream> - -/*==========================================================================*| -// Whoops, seems Linden's Boost package and the viewer are built with -// different settings of VC's /Zc:wchar_t switch! Using Boost.Filesystem -// pathname operations produces Windows link errors: -// unresolved external symbol "private: static class std::codecvt<unsigned short, -// char,int> const * & __cdecl boost::filesystem3::path::wchar_t_codecvt_facet()" -// unresolved external symbol "void __cdecl boost::filesystem3::path_traits::convert()" -// See: -// http://boost.2283326.n4.nabble.com/filesystem-v3-unicode-and-std-codecvt-linker-error-td3455549.html -// which points to: -// http://msdn.microsoft.com/en-us/library/dh8che7s%28v=VS.100%29.aspx - -// As we're not trying to preserve compatibility with old Boost.Filesystem -// code, but rather writing brand-new code, use the newest available -// Filesystem API. -#define BOOST_FILESYSTEM_VERSION 3 -#include "boost/filesystem.hpp" -#include "boost/filesystem/v3/fstream.hpp" -|*==========================================================================*/  #include "boost/range.hpp"  #include "boost/foreach.hpp"  #include "boost/function.hpp"  #include "boost/lambda/lambda.hpp"  #include "boost/lambda/bind.hpp"  namespace lambda = boost::lambda; -/*==========================================================================*| -// Aaaarrgh, Linden's Boost package doesn't even include Boost.Iostreams! -#include "boost/iostreams/stream.hpp" -#include "boost/iostreams/device/file_descriptor.hpp" -|*==========================================================================*/  #include "../llsd.h"  #include "../llsdserialize.h" @@ -82,236 +56,17 @@ namespace lambda = boost::lambda;  #include "../llformat.h"  #include "../test/lltut.h" +#include "../test/manageapr.h" +#include "../test/namedtempfile.h"  #include "stringize.h" +static ManageAPR manager; +  std::vector<U8> string_to_vector(const std::string& str)  {  	return std::vector<U8>(str.begin(), str.end());  } -#if ! LL_WINDOWS -// We want to call strerror_r(), but alarmingly, there are two different -// variants. The one that returns int always populates the passed buffer -// (except in case of error), whereas the other one always returns a valid -// char* but might or might not populate the passed buffer. How do we know -// which one we're getting? Define adapters for each and let the compiler -// select the applicable adapter. - -// strerror_r() returns char* -std::string message_from(int /*orig_errno*/, const char* /*buffer*/, const char* strerror_ret) -{ -    return strerror_ret; -} - -// strerror_r() returns int -std::string message_from(int orig_errno, const char* buffer, int strerror_ret) -{ -    if (strerror_ret == 0) -    { -        return buffer; -    } -    // Here strerror_r() has set errno. Since strerror_r() has already failed, -    // seems like a poor bet to call it again to diagnose its own error... -    int stre_errno = errno; -    if (stre_errno == ERANGE) -    { -        return STRINGIZE("strerror_r() can't explain errno " << orig_errno -                         << " (buffer too small)"); -    } -    if (stre_errno == EINVAL) -    { -        return STRINGIZE("unknown errno " << orig_errno); -    } -    // Here we don't even understand the errno from strerror_r()! -    return STRINGIZE("strerror_r() can't explain errno " << orig_errno -                     << " (error " << stre_errno << ')'); -} -#endif  // ! LL_WINDOWS - -// boost::filesystem::temp_directory_path() isn't yet in Boost 1.45! :-( -std::string temp_directory_path() -{ -#if LL_WINDOWS -    char buffer[4096]; -    GetTempPathA(sizeof(buffer), buffer); -    return buffer; - -#else  // LL_DARWIN, LL_LINUX -    static const char* vars[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR" }; -    BOOST_FOREACH(const char* var, vars) -    { -        const char* found = getenv(var); -        if (found) -            return found; -    } -    return "/tmp"; -#endif // LL_DARWIN, LL_LINUX -} - -// Windows presents a kinda sorta compatibility layer. Code to the yucky -// Windows names because they're less likely than the Posix names to collide -// with any other names in this source. -#if LL_WINDOWS -#define _remove   DeleteFileA -#else  // ! LL_WINDOWS -#define _open     open -#define _write    write -#define _close    close -#define _remove   remove -#endif  // ! LL_WINDOWS - -// Create a text file with specified content "somewhere in the -// filesystem," cleaning up when it goes out of scope. -class NamedTempFile -{ -public: -    // Function that accepts an ostream ref and (presumably) writes stuff to -    // it, e.g.: -    // (lambda::_1 << "the value is " << 17 << '\n') -    typedef boost::function<void(std::ostream&)> Streamer; - -    NamedTempFile(const std::string& ext, const std::string& content): -        mPath(temp_directory_path()) -    { -        createFile(ext, lambda::_1 << content); -    } - -    // Disambiguate when passing string literal -    NamedTempFile(const std::string& ext, const char* content): -        mPath(temp_directory_path()) -    { -        createFile(ext, lambda::_1 << content); -    } - -    NamedTempFile(const std::string& ext, const Streamer& func): -        mPath(temp_directory_path()) -    { -        createFile(ext, func); -    } - -    ~NamedTempFile() -    { -        _remove(mPath.c_str()); -    } - -    std::string getName() const { return mPath; } - -private: -    void createFile(const std::string& ext, const Streamer& func) -    { -        // Silly maybe, but use 'ext' as the name prefix. Strip off a leading -        // '.' if present. -        int pfx_offset = ((! ext.empty()) && ext[0] == '.')? 1 : 0; - -#if ! LL_WINDOWS -        // Make sure mPath ends with a directory separator, if it doesn't already. -        if (mPath.empty() || -            ! (mPath[mPath.length() - 1] == '\\' || mPath[mPath.length() - 1] == '/')) -        { -            mPath.append("/"); -        } - -        // mkstemp() accepts and modifies a char* template string. Generate -        // the template string, then copy to modifiable storage. -        // mkstemp() requires its template string to end in six X's. -        mPath += ext.substr(pfx_offset) + "XXXXXX"; -        // Copy to vector<char> -        std::vector<char> pathtemplate(mPath.begin(), mPath.end()); -        // append a nul byte for classic-C semantics -        pathtemplate.push_back('\0'); -        // std::vector promises that a pointer to the 0th element is the same -        // as a pointer to a contiguous classic-C array -        int fd(mkstemp(&pathtemplate[0])); -        if (fd == -1) -        { -            // The documented errno values (http://linux.die.net/man/3/mkstemp) -            // are used in a somewhat unusual way, so provide context-specific -            // errors. -            if (errno == EEXIST) -            { -                LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath -                                         << "\") could not create unique file " << LL_ENDL; -            } -            if (errno == EINVAL) -            { -                LL_ERRS("NamedTempFile") << "bad mkstemp() file path template '" -                                         << mPath << "'" << LL_ENDL; -            } -            // Shrug, something else -            int mkst_errno = errno; -            char buffer[256]; -            LL_ERRS("NamedTempFile") << "mkstemp(\"" << mPath << "\") failed: " -                                     << message_from(mkst_errno, buffer, -                                                     strerror_r(mkst_errno, buffer, sizeof(buffer))) -                                     << LL_ENDL; -        } -        // mkstemp() seems to have worked! Capture the modified filename. -        // Avoid the nul byte we appended. -        mPath.assign(pathtemplate.begin(), (pathtemplate.end()-1)); - -/*==========================================================================*| -        // Define an ostream on the open fd. Tell it to close fd on destruction. -        boost::iostreams::stream<boost::iostreams::file_descriptor_sink> -            out(fd, boost::iostreams::close_handle); -|*==========================================================================*/ - -        // Write desired content. -        std::ostringstream out; -        // Stream stuff to it. -        func(out); - -        std::string data(out.str()); -        int written(_write(fd, data.c_str(), data.length())); -        int closed(_close(fd)); -        llassert_always(written == data.length() && closed == 0); - -#else // LL_WINDOWS -        // GetTempFileName() is documented to require a MAX_PATH buffer. -        char tempname[MAX_PATH]; -        // Use 'ext' as filename prefix, but skip leading '.' if any. -        // The 0 param is very important: requests iterating until we get a -        // unique name. -        if (0 == GetTempFileNameA(mPath.c_str(), ext.c_str() + pfx_offset, 0, tempname)) -        { -            // I always have to look up this call...  :-P -            LPSTR msgptr; -            FormatMessageA( -                FORMAT_MESSAGE_ALLOCATE_BUFFER |  -                FORMAT_MESSAGE_FROM_SYSTEM | -                FORMAT_MESSAGE_IGNORE_INSERTS, -                NULL, -                GetLastError(), -                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), -                LPSTR(&msgptr),     // have to cast (char**) to (char*) -                0, NULL ); -            LL_ERRS("NamedTempFile") << "GetTempFileName(\"" << mPath << "\", \"" -                                     << (ext.c_str() + pfx_offset) << "\") failed: " -                                     << msgptr << LL_ENDL; -            LocalFree(msgptr); -        } -        // GetTempFileName() appears to have worked! Capture the actual -        // filename. -        mPath = tempname; -        // Open the file and stream content to it. Destructor will close. -        std::ofstream out(tempname); -        func(out); - -#endif  // LL_WINDOWS -    } - -    void peep() -    { -        std::cout << "File '" << mPath << "' contains:\n"; -        std::ifstream reader(mPath.c_str()); -        std::string line; -        while (std::getline(reader, line)) -            std::cout << line << '\n'; -        std::cout << "---\n"; -    } - -    std::string mPath; -}; -  namespace tut  {  	struct sd_xml_data @@ -1783,7 +1538,7 @@ namespace tut              const char* PYTHON(getenv("PYTHON"));              ensure("Set $PYTHON to the Python interpreter", PYTHON); -            NamedTempFile scriptfile(".py", script); +            NamedTempFile scriptfile("py", script);  #if LL_WINDOWS              std::string q("\""); @@ -1802,14 +1557,15 @@ namespace tut              }  #else  // LL_DARWIN, LL_LINUX -            LLProcessLauncher py; -            py.setExecutable(PYTHON); -            py.addArgument(scriptfile.getName()); -            ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.launch(), 0); +            LLProcess::Params params; +            params.executable = PYTHON; +            params.args.add(scriptfile.getName()); +            LLProcessPtr py(LLProcess::create(params)); +            ensure(STRINGIZE("Couldn't launch " << desc << " script"), py);              // Implementing timeout would mean messing with alarm() and              // catching SIGALRM... later maybe...              int status(0); -            if (waitpid(py.getProcessID(), &status, 0) == -1) +            if (waitpid(py->getProcessID(), &status, 0) == -1)              {                  int waitpid_errno(errno);                  ensure_equals(STRINGIZE("Couldn't retrieve rc from " << desc << " script: " @@ -1888,12 +1644,12 @@ namespace tut              "    else:\n"              "        assert False, 'Too many data items'\n"; -        // Create a something.llsd file containing 'data' serialized to +        // Create an llsdXXXXXX file containing 'data' serialized to          // notation. It's important to separate with newlines because Python's          // llsd module doesn't support parsing from a file stream, only from a          // string, so we have to know how much of the file to read into a          // string. -        NamedTempFile file(".llsd", +        NamedTempFile file("llsd",                             // NamedTempFile's boost::function constructor                             // takes a callable. To this callable it passes the                             // std::ostream with which it's writing the @@ -1926,7 +1682,7 @@ namespace tut          // Create an empty data file. This is just a placeholder for our          // script to write into. Create it to establish a unique name that          // we know. -        NamedTempFile file(".llsd", ""); +        NamedTempFile file("llsd", "");          python("write Python notation",                 lambda::_1 << diff --git a/indra/llcommon/tests/llstreamqueue_test.cpp b/indra/llcommon/tests/llstreamqueue_test.cpp new file mode 100644 index 0000000000..050ad5c5bf --- /dev/null +++ b/indra/llcommon/tests/llstreamqueue_test.cpp @@ -0,0 +1,197 @@ +/** + * @file   llstreamqueue_test.cpp + * @author Nat Goodspeed + * @date   2012-01-05 + * @brief  Test for llstreamqueue. + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llstreamqueue.h" +// STL headers +#include <vector> +// std headers +// external library headers +#include <boost/foreach.hpp> +// other Linden headers +#include "../test/lltut.h" +#include "stringize.h" + +/***************************************************************************** +*   TUT +*****************************************************************************/ +namespace tut +{ +    struct llstreamqueue_data +    { +        llstreamqueue_data(): +            // we want a buffer with actual bytes in it, not an empty vector +            buffer(10) +        {} +        // As LLStreamQueue is merely a typedef for +        // LLGenericStreamQueue<char>, and no logic in LLGenericStreamQueue is +        // specific to the <char> instantiation, we're comfortable for now +        // testing only the narrow-char version. +        LLStreamQueue strq; +        // buffer for use in multiple tests +        std::vector<char> buffer; +    }; +    typedef test_group<llstreamqueue_data> llstreamqueue_group; +    typedef llstreamqueue_group::object object; +    llstreamqueue_group llstreamqueuegrp("llstreamqueue"); + +    template<> template<> +    void object::test<1>() +    { +        set_test_name("empty LLStreamQueue"); +        ensure_equals("brand-new LLStreamQueue isn't empty", +                      strq.size(), 0); +        ensure_equals("brand-new LLStreamQueue returns data", +                      strq.asSource().read(&buffer[0], buffer.size()), 0); +        strq.asSink().close(); +        ensure_equals("closed empty LLStreamQueue not at EOF", +                      strq.asSource().read(&buffer[0], buffer.size()), -1); +    } + +    template<> template<> +    void object::test<2>() +    { +        set_test_name("one internal block, one buffer"); +        LLStreamQueue::Sink sink(strq.asSink()); +        ensure_equals("write(\"\")", sink.write("", 0), 0); +        ensure_equals("0 write should leave LLStreamQueue empty (size())", +                      strq.size(), 0); +        ensure_equals("0 write should leave LLStreamQueue empty (peek())", +                      strq.peek(&buffer[0], buffer.size()), 0); +        // The meaning of "atomic" is that it must be smaller than our buffer. +        std::string atomic("atomic"); +        ensure("test data exceeds buffer", atomic.length() < buffer.size()); +        ensure_equals(STRINGIZE("write(\"" << atomic << "\")"), +                      sink.write(&atomic[0], atomic.length()), atomic.length()); +        ensure_equals("size() after write()", strq.size(), atomic.length()); +        size_t peeklen(strq.peek(&buffer[0], buffer.size())); +        ensure_equals(STRINGIZE("peek(\"" << atomic << "\")"), +                      peeklen, atomic.length()); +        ensure_equals(STRINGIZE("peek(\"" << atomic << "\") result"), +                      std::string(buffer.begin(), buffer.begin() + peeklen), atomic); +        ensure_equals("size() after peek()", strq.size(), atomic.length()); +        // peek() should not consume. Use a different buffer to prove it isn't +        // just leftover data from the first peek(). +        std::vector<char> again(buffer.size()); +        peeklen = size_t(strq.peek(&again[0], again.size())); +        ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again"), +                      peeklen, atomic.length()); +        ensure_equals(STRINGIZE("peek(\"" << atomic << "\") again result"), +                      std::string(again.begin(), again.begin() + peeklen), atomic); +        // now consume. +        std::vector<char> third(buffer.size()); +        size_t readlen(strq.read(&third[0], third.size())); +        ensure_equals(STRINGIZE("read(\"" << atomic << "\")"), +                      readlen, atomic.length()); +        ensure_equals(STRINGIZE("read(\"" << atomic << "\") result"), +                      std::string(third.begin(), third.begin() + readlen), atomic); +        ensure_equals("peek() after read()", strq.peek(&buffer[0], buffer.size()), 0); +        ensure_equals("size() after read()", strq.size(), 0); +    } + +    template<> template<> +    void object::test<3>() +    { +        set_test_name("basic skip()"); +        std::string lovecraft("lovecraft"); +        ensure("test data exceeds buffer", lovecraft.length() < buffer.size()); +        ensure_equals(STRINGIZE("write(\"" << lovecraft << "\")"), +                      strq.write(&lovecraft[0], lovecraft.length()), lovecraft.length()); +        size_t peeklen(strq.peek(&buffer[0], buffer.size())); +        ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\")"), +                      peeklen, lovecraft.length()); +        ensure_equals(STRINGIZE("peek(\"" << lovecraft << "\") result"), +                      std::string(buffer.begin(), buffer.begin() + peeklen), lovecraft); +        std::streamsize skip1(4); +        ensure_equals(STRINGIZE("skip(" << skip1 << ")"), strq.skip(skip1), skip1); +        ensure_equals("size() after skip()", strq.size(), lovecraft.length() - skip1); +        size_t readlen(strq.read(&buffer[0], buffer.size())); +        ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\")"), +                      readlen, lovecraft.length() - skip1); +        ensure_equals(STRINGIZE("read(\"" << lovecraft.substr(skip1) << "\") result"), +                      std::string(buffer.begin(), buffer.begin() + readlen), +                      lovecraft.substr(skip1)); +        ensure_equals("unconsumed", strq.read(&buffer[0], buffer.size()), 0); +    } + +    template<> template<> +    void object::test<4>() +    { +        set_test_name("skip() multiple blocks"); +        std::string blocks[] = { "books of ", "H.P. ", "Lovecraft" }; +        std::streamsize total(blocks[0].length() + blocks[1].length() + blocks[2].length()); +        std::streamsize leave(5);   // len("craft") above +        std::streamsize skip(total - leave); +        std::streamsize written(0); +        BOOST_FOREACH(const std::string& block, blocks) +        { +            written += strq.write(&block[0], block.length()); +            ensure_equals("size() after write()", strq.size(), written); +        } +        std::streamsize skiplen(strq.skip(skip)); +        ensure_equals(STRINGIZE("skip(" << skip << ")"), skiplen, skip); +        ensure_equals("size() after skip()", strq.size(), leave); +        size_t readlen(strq.read(&buffer[0], buffer.size())); +        ensure_equals("read(\"craft\")", readlen, leave); +        ensure_equals("read(\"craft\") result", +                      std::string(buffer.begin(), buffer.begin() + readlen), "craft"); +    } + +    template<> template<> +    void object::test<5>() +    { +        set_test_name("concatenate blocks"); +        std::string blocks[] = { "abcd", "efghij", "klmnopqrs" }; +        BOOST_FOREACH(const std::string& block, blocks) +        { +            strq.write(&block[0], block.length()); +        } +        std::vector<char> longbuffer(30); +        std::streamsize readlen(strq.read(&longbuffer[0], longbuffer.size())); +        ensure_equals("read() multiple blocks", +                      readlen, blocks[0].length() + blocks[1].length() + blocks[2].length()); +        ensure_equals("read() multiple blocks result", +                      std::string(longbuffer.begin(), longbuffer.begin() + readlen), +                      blocks[0] + blocks[1] + blocks[2]); +    } + +    template<> template<> +    void object::test<6>() +    { +        set_test_name("split blocks"); +        std::string blocks[] = { "abcdefghijklm", "nopqrstuvwxyz" }; +        BOOST_FOREACH(const std::string& block, blocks) +        { +            strq.write(&block[0], block.length()); +        } +        strq.close(); +        // We've already verified what strq.size() should be at this point; +        // see above test named "skip() multiple blocks" +        std::streamsize chksize(strq.size()); +        std::streamsize readlen(strq.read(&buffer[0], buffer.size())); +        ensure_equals("read() 0", readlen, buffer.size()); +        ensure_equals("read() 0 result", std::string(buffer.begin(), buffer.end()), "abcdefghij"); +        chksize -= readlen; +        ensure_equals("size() after read() 0", strq.size(), chksize); +        readlen = strq.read(&buffer[0], buffer.size()); +        ensure_equals("read() 1", readlen, buffer.size()); +        ensure_equals("read() 1 result", std::string(buffer.begin(), buffer.end()), "klmnopqrst"); +        chksize -= readlen; +        ensure_equals("size() after read() 1", strq.size(), chksize); +        readlen = strq.read(&buffer[0], buffer.size()); +        ensure_equals("read() 2", readlen, chksize); +        ensure_equals("read() 2 result", +                      std::string(buffer.begin(), buffer.begin() + readlen), "uvwxyz"); +        ensure_equals("read() 3", strq.read(&buffer[0], buffer.size()), -1); +    } +} // namespace tut | 
