/** @file * Settings File Manipulation API. */ /* * Copyright (C) 2007 Sun Microsystems, Inc. * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the * VirtualBox OSE distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 USA or visit http://www.sun.com if you need * additional information or have any questions. */ #ifndef ___VBox_settings_h #define ___VBox_settings_h #include #include #include #include #include #include /* these conflict with numeric_digits<>::min and max */ #undef min #undef max #include #include #include #include #include /** @defgroup grp_settings Settings File Manipulation API * @{ * * The Settings File Manipulation API allows to maintain a configuration file * that contains "name-value" pairs grouped under named keys which are in turn * organized in a hierarchical tree-like structure: * * @code * * * * SubKey1_Value * SubKey2_Value * Key2_Value * * * @endcode * * All strings this API manipulates with are zero-terminated arrays of @c char * in UTF-8 encoding. Strings returned by the API are owned by the API unless * explicitly stated otherwise. Strings passed to the API are accessed by the * API only during the given API call unless explicitly stated otherwise. If * necessary, the API will make a copy of the supplied string. * * Error reprting is perfomed using C++ exceptions. All exceptions thrown by * this API are derived from settings::Error. This doesn't cover exceptions * that may be thrown by third-party library calls made by this API. * * All public classes represented by this API that support copy operations * (i.e. may be created or assigned from other instsances of the same class), * such as Key and Value classes, implement shallow copies and use this mode by * default. It means two things: * * 1. Instances of these classes can be freely copied around and used as return * values. All copies will share the same internal data block (using the * reference counting technique) so that the copy operation is cheap, both * in terms of memory and speed. * * 2. Since copied instances share the same data, an attempt to change data in * the original will be reflected in all existing copies. * * Making deep copies or detaching the existing shallow copy from its original * is not yet supported. * * Note that the Settings File API is not thread-safe. It means that if you * want to use the same instance of a class from the settings namespace on more * than one thread at a time, you will have to provide necessary access * serialization yourself. * * Due to some (not propely studied) libxml2 limitations, the Settings File * API is not thread-safe. Therefore, the API caller must provide * serialization for threads using this API simultaneously. Note though that * if the libxml2 library is (even imlicitly) used on some other thread which * doesn't use this API (e.g. third-party code), it may lead to resource * conflicts (followed by crashes, memory corruption etc.). A proper solution * for these conflicts is to be found. * * In order to load a settings file the program creates a TreeBackend instance * using one of the specific backends (e.g. XmlTreeBackend) and then passes an * Input stream object (e.g. File or MemoryBuf) to the TreeBackend::read() * method to parse the stream and build the settings tree. On success, the * program uses the TreeBackend::rootKey() method to access the root key of * the settings tree. The root key provides access to the whole tree of * settings through the methods of the Key class which allow to read, change * and create new key values. Below is an example that uses the XML backend to * load the settings tree, then modifies it and then saves the modifications. * * @code using namespace settings; try { File file (File::ReadWrite, "myfile.xml"); XmlTreeBackend tree; // load the tree, parse it and validate using the XML schema tree.read (aFile, "myfile.xsd", XmlTreeBackend::Read_AddDefaults); // get the root key Key root = tree.key(); printf ("root=%s\n", root.name()); // enumerate all child keys of the root key named Foo Key::list children = root.keys ("Foo"); for (Key::list::const_iterator it = children.begin(); it != children.end(); ++ it) { // get the "level" attribute int level = (*it).value ("level"); if (level > 5) { // if so, create a "Bar" key if it doesn't exist yet Key bar = (*it).createKey ("Bar"); // set the "date" attribute RTTIMESPEC now; RTTimeNow (&now); bar.setValue ("date", now); } else if (level < 2) { // if its below 2, delete the whole "Foo" key (*it).zap(); } } // save the tree on success (the second try is to distinguish between // stream load and save errors) try { aTree.write (aFile); } catch (const EIPRTFailure &err) { // this is an expected exception that may happen in case of stream // read or write errors printf ("Could not save the settings file '%s' (%Rrc)"); file.uri(), err.rc()); return FAILURE; } return SUCCESS; } catch (const EIPRTFailure &err) { // this is an expected exception that may happen in case of stream // read or write errors printf ("Could not load the settings file '%s' (%Rrc)"); file.uri(), err.rc()); } catch (const XmlTreeBackend::Error &err) { // this is an XmlTreeBackend specific exception exception that may // happen in case of XML parse or validation errors printf ("Could not load the settings file '%s'.\n%s"), file.uri(), err.what() ? err.what() : "Unknown error"); } catch (const std::exception &err) { // the rest is unexpected (e.g. should not happen unless you // specifically wish so for some reason and therefore allow for a // situation that may throw one of these from within the try block // above) AssertMsgFailed ("Unexpected exception '%s' (%s)\n", typeid (err).name(), err.what()); catch (...) { // this is even more unexpected, and no any useful info here AssertMsgFailed ("Unexpected exception\n"); } return FAILURE; * @endcode * * Note that you can get a raw (string) value of the attribute using the * Key::stringValue() method but often it's simpler and better to use the * templated Key::value<>() method that can convert the string to a value of * the given type for you (and throw exceptions when the converison is not * possible). Similarly, the Key::setStringValue() methid is used to set a raw * string value and there is a templated Key::setValue<>() method to set a * typed value which will implicitly convert it to a string. * * Currently, types supported by Key::value<>() and Key::setValue<>() include * all C and IPRT integer types, bool and RTTIMESPEC (represented as isoDate * in XML). You can always add support for your own types by creating * additional specializations of the FromString<>() and ToString<>() templates * in the settings namespace (see the real examples in this header). * * See individual funciton, class and method descriptions to get more details * on the Settings File Manipulation API. */ #ifndef IN_RING3 # error "There are no settings APIs available in Ring-0 Context!" #else /* IN_RING3 */ /** @def IN_VBOXSETTINGS_R3 * Used to indicate whether we're inside the same link module as the * XML Settings File Manipulation API. * * @todo should go to a separate common include together with VBOXXML2_CLASS * once there becomes more than one header in the VBoxXML2 library. */ #ifdef DOXYGEN_RUNNING # define IN_VBOXSETTINGS_R3 #endif /** @def VBOXSETTINGS_CLASS * Class export/import wrapper. */ #ifdef IN_VBOXSETTINGS_R3 # define VBOXSETTINGS_CLASS DECLEXPORT_CLASS #else # define VBOXSETTINGS_CLASS DECLIMPORT_CLASS #endif /* * Shut up MSVC complaining that auto_ptr[_ref] template instantiations (as a * result of private data member declarations of some classes below) need to * be exported too to in order to be accessible by clients. * * The alternative is to instantiate a template before the data member * declaration with the VBOXSETTINGS_CLASS prefix, but the standard disables * explicit instantiations in a foreign namespace. In other words, a declaration * like: * * template class VBOXSETTINGS_CLASS std::auto_ptr ; * * right before the member declaration makes MSVC happy too, but this is not a * valid C++ construct (and G++ spits it out). So, for now we just disable the * warning and will come back to this problem one day later. * * We also disable another warning (4275) saying that a DLL-exported class * inherits form a non-DLL-exported one (e.g. settings::ENoMemory -> * std::bad_alloc). I can't get how it can harm yet. */ #if defined(_MSC_VER) #pragma warning (disable:4251) #pragma warning (disable:4275) #endif /* Forwards */ typedef struct _xmlParserInput xmlParserInput; typedef xmlParserInput *xmlParserInputPtr; typedef struct _xmlParserCtxt xmlParserCtxt; typedef xmlParserCtxt *xmlParserCtxtPtr; typedef struct _xmlError xmlError; typedef xmlError *xmlErrorPtr; /** * Settings File Manipulation API namespace. */ namespace settings { // Helpers ////////////////////////////////////////////////////////////////////////////// /** * Temporary holder for the formatted string. * * Instances of this class are used for passing the formatted string as an * argument to an Error constructor or to another function that takes * const char * and makes a copy of the string it points to. */ class VBOXSETTINGS_CLASS FmtStr { public: /** * Creates a formatted string using the format string and a set of * printf-like arguments. */ FmtStr (const char *aFmt, ...) { va_list args; va_start (args, aFmt); RTStrAPrintfV (&mStr, aFmt, args); va_end (args); } ~FmtStr() { RTStrFree (mStr); } operator const char *() { return mStr; } private: DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (FmtStr) char *mStr; }; // Exceptions ////////////////////////////////////////////////////////////////////////////// /** * Base exception class. */ class VBOXSETTINGS_CLASS Error : public std::exception { public: Error (const char *aMsg = NULL) : m (aMsg ? Str::New (aMsg) : NULL) {} virtual ~Error() throw() {} void setWhat (const char *aMsg) { m = aMsg ? Str::New (aMsg) : NULL; } const char *what() const throw() { return m.is_null() ? NULL : m->str; } private: /** smart string with support for reference counting */ struct Str { size_t ref() { return ++ refs; } size_t unref() { return -- refs; } size_t refs; char str [1]; static Str *New (const char *aStr) { Str *that = (Str *) RTMemAllocZ (sizeof (Str) + strlen (aStr)); AssertReturn (that, NULL); strcpy (that->str, aStr); return that; } void operator delete (void *that, size_t) { RTMemFree (that); } }; stdx::auto_ref_ptr m; }; class VBOXSETTINGS_CLASS LogicError : public Error { public: LogicError (const char *aMsg = NULL) : Error (aMsg) {} LogicError (RT_SRC_POS_DECL) { char *msg = NULL; RTStrAPrintf (&msg, "In '%s', '%s' at #%d", pszFunction, pszFile, iLine); setWhat (msg); RTStrFree (msg); } }; class VBOXSETTINGS_CLASS RuntimeError : public Error { public: RuntimeError (const char *aMsg = NULL) : Error (aMsg) {} }; // Logical errors ////////////////////////////////////////////////////////////////////////////// class VBOXSETTINGS_CLASS ENotImplemented : public LogicError { public: ENotImplemented (const char *aMsg = NULL) : LogicError (aMsg) {} ENotImplemented (RT_SRC_POS_DECL) : LogicError (RT_SRC_POS_ARGS) {} }; class VBOXSETTINGS_CLASS EInvalidArg : public LogicError { public: EInvalidArg (const char *aMsg = NULL) : LogicError (aMsg) {} EInvalidArg (RT_SRC_POS_DECL) : LogicError (RT_SRC_POS_ARGS) {} }; class VBOXSETTINGS_CLASS ENoKey : public LogicError { public: ENoKey (const char *aMsg = NULL) : LogicError (aMsg) {} }; class VBOXSETTINGS_CLASS ENoValue : public LogicError { public: ENoValue (const char *aMsg = NULL) : LogicError (aMsg) {} }; // Runtime errors ////////////////////////////////////////////////////////////////////////////// class VBOXSETTINGS_CLASS ENoMemory : public RuntimeError, public std::bad_alloc { public: ENoMemory (const char *aMsg = NULL) : RuntimeError (aMsg) {} virtual ~ENoMemory() throw() {} }; class VBOXSETTINGS_CLASS EIPRTFailure : public RuntimeError { public: EIPRTFailure (const char *aMsg = NULL) : RuntimeError (aMsg) {} EIPRTFailure (int aRC) : mRC (aRC) {} int rc() const { return mRC; } private: int mRC; }; class VBOXSETTINGS_CLASS ENoConversion : public RuntimeError { public: ENoConversion (const char *aMsg = NULL) : RuntimeError (aMsg) {} }; // string -> type conversions ////////////////////////////////////////////////////////////////////////////// /** @internal * Helper for the FromString() template, doesn't need to be called directly. */ DECLEXPORT (uint64_t) FromStringInteger (const char *aValue, bool aSigned, int aBits, uint64_t aMin, uint64_t aMax); /** * Generic template function to perform a conversion of an UTF-8 string to an * arbitrary value of type @a T. * * This generic template is implenented only for 8-, 16-, 32- and 64- bit * signed and unsigned integers where it uses RTStrTo[U]Int64() to perform the * conversion. For all other types it throws an ENotImplemented * exception. Individual template specializations for known types should do * the conversion job. * * If the conversion is not possible (for example the string format is wrong * or meaningless for the given type), this template will throw an * ENoConversion exception. All specializations must do the same. * * If the @a aValue argument is NULL, this method will throw an ENoValue * exception. All specializations must do the same. * * @param aValue Value to convert. * * @return Result of conversion. */ template T FromString (const char *aValue) { if (std::numeric_limits ::is_integer) { bool sign = std::numeric_limits ::is_signed; int bits = std::numeric_limits ::digits + (sign ? 1 : 0); return (T) FromStringInteger (aValue, sign, bits, (uint64_t) std::numeric_limits ::min(), (uint64_t) std::numeric_limits ::max()); } throw ENotImplemented (RT_SRC_POS); } /** * Specialization of FromString for bool. * * Converts "true", "yes", "on" to true and "false", "no", "off" to false. */ template<> DECLEXPORT (bool) FromString (const char *aValue); /** * Specialization of FromString for RTTIMESPEC. * * Converts the date in ISO format (--
T::[timezone]) * to a RTTIMESPEC value. Currently, the timezone must always be Z (UTC). */ template<> DECLEXPORT (RTTIMESPEC) FromString (const char *aValue); /** * Converts a string of hex digits to memory bytes. * * @param aValue String to convert. * @param aLen Where to store the length of the returned memory * block (may be NULL). * * @return Result of conversion (a block of @a aLen bytes). */ DECLEXPORT (stdx::char_auto_ptr) FromString (const char *aValue, size_t *aLen); // type -> string conversions ////////////////////////////////////////////////////////////////////////////// /** @internal * Helper for the ToString() template, doesn't need to be called directly. */ DECLEXPORT (stdx::char_auto_ptr) ToStringInteger (uint64_t aValue, unsigned int aBase, bool aSigned, int aBits); /** * Generic template function to perform a conversion of an arbitrary value to * an UTF-8 string. * * This generic template is implemented only for 8-, 16-, 32- and 64- bit * signed and unsigned integers where it uses RTStrFormatNumber() to perform * the conversion. For all other types it throws an ENotImplemented * exception. Individual template specializations for known types should do * the conversion job. If the conversion is not possible (for example the * given value doesn't have a string representation), the relevant * specialization should throw an ENoConversion exception. * * If the @a aValue argument's value would convert to a NULL string, this * method will throw an ENoValue exception. All specializations must do the * same. * * @param aValue Value to convert. * @param aExtra Extra flags to define additional formatting. In case of * integer types, it's the base used for string representation. * * @return Result of conversion. */ template stdx::char_auto_ptr ToString (const T &aValue, unsigned int aExtra = 0) { if (std::numeric_limits ::is_integer) { bool sign = std::numeric_limits ::is_signed; int bits = std::numeric_limits ::digits + (sign ? 1 : 0); return ToStringInteger (aValue, aExtra, sign, bits); } throw ENotImplemented (RT_SRC_POS); } /** * Specialization of ToString for bool. * * Converts true to "true" and false to "false". @a aExtra is not used. */ template<> DECLEXPORT (stdx::char_auto_ptr) ToString (const bool &aValue, unsigned int aExtra); /** * Specialization of ToString for RTTIMESPEC. * * Converts the RTTIMESPEC value to the date string in ISO format * (--
T::[timezone]). Currently, the timezone will * always be Z (UTC). * * @a aExtra is not used. */ template<> DECLEXPORT (stdx::char_auto_ptr) ToString (const RTTIMESPEC &aValue, unsigned int aExtra); /** * Converts memory bytes to a null-terminated string of hex values. * * @param aData Pointer to the memory block. * @param aLen Length of the memory block. * * @return Result of conversion. */ DECLEXPORT (stdx::char_auto_ptr) ToString (const void *aData, size_t aLen); // the rest ////////////////////////////////////////////////////////////////////////////// /** * The Key class represents a settings key. * * Every settings key has a name and zero or more uniquely named values * (attributes). There is a special attribute with a NULL name that is called * a key value. * * Besides values, settings keys may contain other settings keys. This way, * settings keys form a tree-like (or a directory-like) hierarchy of keys. Key * names do not need to be unique even if they belong to the same parent key * which allows to have an array of keys of the same name. * * @note Key and Value objects returned by methods of the Key and TreeBackend * classes are owned by the given TreeBackend instance and may refer to data * that becomes invalid when this TreeBackend instance is destroyed. */ class VBOXSETTINGS_CLASS Key { public: typedef std::list List; /** * Key backend interface used to perform actual key operations. * * This interface is implemented by backends that provide specific ways of * storing settings keys. */ class VBOXSETTINGS_CLASS Backend : public stdx::auto_ref { public: /** Performs the Key::name() function. */ virtual const char *name() const = 0; /** Performs the Key::setName() function. */ virtual void setName (const char *aName) = 0; /** Performs the Key::stringValue() function. */ virtual const char *value (const char *aName) const = 0; /** Performs the Key::setStringValue() function. */ virtual void setValue (const char *aName, const char *aValue) = 0; /** Performs the Key::keys() function. */ virtual List keys (const char *aName = NULL) const = 0; /** Performs the Key::findKey() function. */ virtual Key findKey (const char *aName) const = 0; /** Performs the Key::appendKey() function. */ virtual Key appendKey (const char *aName) = 0; /** Performs the Key::zap() function. */ virtual void zap() = 0; /** * Returns an opaque value that uniquely represents the position of * this key on the tree which is used to compare two keys. Two or more * keys may return the same value only if they actually represent the * same key (i.e. they have the same list of parents and children). */ virtual void *position() const = 0; }; /** * Creates a new key object. If @a aBackend is @c NULL then a null key is * created. * * Regular API users should never need to call this method with something * other than NULL argument (which is the default). * * @param aBackend Key backend to use. */ Key (Backend *aBackend = NULL) : m (aBackend) {} /** * Returns @c true if this key is null. */ bool isNull() const { return m.is_null(); } /** * Makes this object a null key. * * Note that as opposed to #zap(), this methid does not delete the key from * the list of children of its parent key. */ void setNull() { m = NULL; } /** * Returns the name of this key. * Returns NULL if this object a null (uninitialized) key. */ const char *name() const { return m.is_null() ? NULL : m->name(); } /** * Sets the name of this key. * * @param aName New key name. */ void setName (const char *aName) { if (!m.is_null()) m->setName (aName); } /** * Returns the value of the attribute with the given name as an UTF-8 * string. Returns @c NULL if there is no attribute with the given name. * * @param aName Name of the attribute. NULL may be used to * get the key value. */ const char *stringValue (const char *aName) const { return m.is_null() ? NULL : m->value (aName); } /** * Sets the value of the attribute with the given name from an UTF-8 * string. This method will do a copy of the supplied @a aValue string. * * @param aName Name of the attribute. NULL may be used to * set the key value. * @param aValue New value of the attribute. NULL may be used to * delete the value instead of setting it. */ void setStringValue (const char *aName, const char *aValue) { if (!m.is_null()) m->setValue (aName, aValue); } /** * Returns the value of the attribute with the given name as an object of * type @a T. Throws ENoValue if there is no attribute with the given * name. * * This function calls #stringValue() to get the string representation of * the attribute and then calls the FromString() template to convert this * string to a value of the given type. * * @param aName Name of the attribute. NULL may be used to * get the key value. */ template T value (const char *aName) const { try { return FromString (stringValue (aName)); } catch (const ENoValue &) { throw ENoValue (FmtStr ("No such attribute '%s'", aName)); } } /** * Returns the value of the attribute with the given name as an object of * type @a T. Returns the given default value if there is no attribute * with the given name. * * This function calls #stringValue() to get the string representation of * the attribute and then calls the FromString() template to convert this * string to a value of the given type. * * @param aName Name of the attribute. NULL may be used to * get the key value. * @param aDefault Default value to return for the missing attribute. */ template T valueOr (const char *aName, const T &aDefault) const { try { return FromString (stringValue (aName)); } catch (const ENoValue &) { return aDefault; } } /** * Sets the value of the attribute with the given name from an object of * type @a T. This method will do a copy of data represented by @a aValue * when necessary. * * This function converts the given value to a string using the ToString() * template and then calls #setStringValue(). * * @param aName Name of the attribute. NULL may be used to * set the key value. * @param aValue New value of the attribute. * @param aExtra Extra field used by some types to specify additional * details for storing the value as a string (such as the * base for decimal numbers). */ template void setValue (const char *aName, const T &aValue, unsigned int aExtra = 0) { try { stdx::char_auto_ptr value = ToString (aValue, aExtra); setStringValue (aName, value.get()); } catch (const ENoValue &) { throw ENoValue (FmtStr ("No value for attribute '%s'", aName)); } } /** * Sets the value of the attribute with the given name from an object of * type @a T. If the value of the @a aValue object equals to the value of * the given @a aDefault object, then the attribute with the given name * will be deleted instead of setting its value to @a aValue. * * This function converts the given value to a string using the ToString() * template and then calls #setStringValue(). * * @param aName Name of the attribute. NULL may be used to * set the key value. * @param aValue New value of the attribute. * @param aDefault Default value to compare @a aValue to. * @param aExtra Extra field used by some types to specify additional * details for storing the value as a string (such as the * base for decimal numbers). */ template void setValueOr (const char *aName, const T &aValue, const T &aDefault, unsigned int aExtra = 0) { if (aValue == aDefault) zapValue (aName); else setValue (aName, aValue, aExtra); } /** * Deletes the value of the attribute with the given name. * Shortcut to setStringValue(aName, NULL). */ void zapValue (const char *aName) { setStringValue (aName, NULL); } /** * Returns the key value. * Shortcut to stringValue (NULL). */ const char *keyStringValue() const { return stringValue (NULL); } /** * Sets the key value. * Shortcut to setStringValue (NULL, aValue). */ void setKeyStringValue (const char *aValue) { setStringValue (NULL, aValue); } /** * Returns the key value. * Shortcut to value (NULL). */ template T keyValue() const { return value (NULL); } /** * Returns the key value or the given default if the key value is NULL. * Shortcut to value (NULL). */ template T keyValueOr (const T &aDefault) const { return valueOr (NULL, aDefault); } /** * Sets the key value. * Shortcut to setValue (NULL, aValue, aExtra). */ template void setKeyValue (const T &aValue, unsigned int aExtra = 0) { setValue (NULL, aValue, aExtra); } /** * Sets the key value. * Shortcut to setValueOr (NULL, aValue, aDefault). */ template void setKeyValueOr (const T &aValue, const T &aDefault, unsigned int aExtra = 0) { setValueOr (NULL, aValue, aDefault, aExtra); } /** * Deletes the key value. * Shortcut to zapValue (NULL). */ void zapKeyValue () { zapValue (NULL); } /** * Returns a list of all child keys named @a aName. * * If @a aname is @c NULL, returns a list of all child keys. * * @param aName Child key name to list. */ List keys (const char *aName = NULL) const { return m.is_null() ? List() : m->keys (aName); }; /** * Returns the first child key with the given name. * * Throws ENoKey if no child key with the given name exists. * * @param aName Child key name. */ Key key (const char *aName) const { Key key = findKey (aName); if (key.isNull()) throw ENoKey (FmtStr ("No such key '%s'", aName)); return key; } /** * Returns the first child key with the given name. * * As opposed to #key(), this method will not throw an exception if no * child key with the given name exists, but return a null key instead. * * @param aName Child key name. */ Key findKey (const char *aName) const { return m.is_null() ? Key() : m->findKey (aName); } /** * Creates a key with the given name as a child of this key and returns it * to the caller. * * If one or more child keys with the given name already exist, no new key * is created but the first matching child key is returned. * * @param aName Name of the child key to create. */ Key createKey (const char *aName) { Key key = findKey (aName); if (key.isNull()) key = appendKey (aName); return key; } /** * Appends a key with the given name to the list of child keys of this key * and returns the appended key to the caller. * * @param aName Name of the child key to create. */ Key appendKey (const char *aName) { return m.is_null() ? Key() : m->appendKey (aName); } /** * Deletes this key. * * The deleted key is removed from the list of child keys of its parent * key and becomes a null object. */ void zap() { if (!m.is_null()) { m->zap(); setNull(); } } /** * Compares this object with the given object and returns @c true if both * represent the same key on the settings tree or if both are null * objects. * * @param that Object to compare this object with. */ bool operator== (const Key &that) const { return m == that.m || (!m.is_null() && !that.m.is_null() && m->position() == that.m->position()); } /** * Counterpart to operator==(). */ bool operator!= (const Key &that) const { return !operator== (that); } private: stdx::auto_ref_ptr m; friend class TreeBackend; }; /** * The Stream class is a base class for I/O streams. */ class VBOXSETTINGS_CLASS Stream { public: virtual ~Stream() {} virtual const char *uri() const = 0; /** * Returns the current read/write position in the stream. The returned * position is a zero-based byte offset from the beginning of the file. * * Throws ENotImplemented if this operation is not implemented for the * given stream. */ virtual uint64_t pos() const = 0; /** * Sets the current read/write position in the stream. * * @param aPos Zero-based byte offset from the beginning of the stream. * * Throws ENotImplemented if this operation is not implemented for the * given stream. */ virtual void setPos (uint64_t aPos) = 0; }; /** * The Input class represents an input stream. * * This input stream is used to read the settings tree from. * This is an abstract class that must be subclassed in order to fill it with * useful functionality. */ class VBOXSETTINGS_CLASS Input : virtual public Stream { public: /** * Reads from the stream to the supplied buffer. * * @param aBuf Buffer to store read data to. * @param aLen Buffer length. * * @return Number of bytes read. */ virtual int read (char *aBuf, int aLen) = 0; }; /** * */ class VBOXSETTINGS_CLASS Output : virtual public Stream { public: /** * Writes to the stream from the supplied buffer. * * @param aBuf Buffer to write data from. * @param aLen Buffer length. * * @return Number of bytes written. */ virtual int write (const char *aBuf, int aLen) = 0; /** * Truncates the stream from the current position and upto the end. * The new file size will become exactly #pos() bytes. * * Throws ENotImplemented if this operation is not implemented for the * given stream. */ virtual void truncate() = 0; }; /** * The TreeBackend class represents a storage backend used to read a settings * tree from and write it to a stream. * * @note All Key objects returned by any of the TreeBackend methods (and by * methods of returned Key objects) are owned by the given TreeBackend * instance. When this instance is destroyed, all Key objects become invalid * and an attempt to access Key data will cause the program crash. */ class VBOXSETTINGS_CLASS TreeBackend { public: /** * Reads and parses the given input stream. * * On success, the previous settings tree owned by this backend (if any) * is deleted. * * The optional schema URI argument determines the name of the schema to * use for input validation. If the schema URI is NULL then the validation * is not performed. Note that you may set a custom input resolver if you * want to provide the input stream for the schema file (and for other * external entities) instead of letting the backend to read the specified * URI directly. * * This method will set the read/write position to the beginning of the * given stream before reading. After the stream has been successfully * parsed, the position will be set back to the beginning. * * @param aInput Input stream. * @param aSchema Schema URI to use for input stream validation. * @param aFlags Optional bit flags. */ void read (Input &aInput, const char *aSchema = NULL, int aFlags = 0) { aInput.setPos (0); rawRead (aInput, aSchema, aFlags); aInput.setPos (0); } /** * Reads and parses the given input stream in a raw fashion. * * This method doesn't set the stream position to the beginnign before and * after reading but instead leaves it as is in both cases. It's the * caller's responsibility to maintain the correct position. * * @see read() */ virtual void rawRead (Input &aInput, const char *aSchema = NULL, int aFlags = 0) = 0; /** * Writes the current settings tree to the given output stream. * * This method will set the read/write position to the beginning of the * given stream before writing. After the settings have been successfully * written to the stream, the stream will be truncated at the position * following the last byte written by this method anc ghd position will be * set back to the beginning. * * @param aOutput Output stream. */ void write (Output &aOutput) { aOutput.setPos (0); rawWrite (aOutput); aOutput.truncate(); aOutput.setPos (0); } /** * Writes the current settings tree to the given output stream in a raw * fashion. * * This method doesn't set the stream position to the beginnign before and * after reading and doesn't truncate the stream, but instead leaves it as * is in both cases. It's the caller's responsibility to maintain the * correct position and perform truncation. * * @see write() */ virtual void rawWrite (Output &aOutput) = 0; /** * Deletes the current settings tree. */ virtual void reset() = 0; /** * Returns the root settings key. */ virtual Key &rootKey() const = 0; protected: static Key::Backend *GetKeyBackend (const Key &aKey) { return aKey.m.raw(); } }; ////////////////////////////////////////////////////////////////////////////// /** * The File class is a stream implementation that reads from and writes to * regular files. * * The File class uses IPRT File API for file operations. Note that IPRT File * API is not thread-safe. This means that if you pass the same RTFILE handle to * different File instances that may be simultaneously used on different * threads, you should care about serialization; otherwise you will get garbage * when reading from or writing to such File instances. */ class VBOXSETTINGS_CLASS File : public Input, public Output { public: /** * Possible file access modes. */ enum Mode { Mode_Read, Mode_Write, Mode_ReadWrite }; /** * Opens a file with the given name in the given mode. If @a aMode is Read * or ReadWrite, the file must exist. If @a aMode is Write, the file must * not exist. Otherwise, an EIPRTFailure excetion will be thrown. * * @param aMode File mode. * @param aFileName File name. */ File (Mode aMode, const char *aFileName); /** * Uses the given file handle to perform file operations. This file * handle must be already open in necessary mode (read, or write, or mixed). * * The read/write position of the given handle will be reset to the * beginning of the file on success. * * Note that the given file handle will not be automatically closed upon * this object destruction. * * @note It you pass the same RTFILE handle to more than one File instance, * please make sure you have provided serialization in case if these * instasnces are to be simultaneously used by different threads. * Otherwise you may get garbage when reading or writing. * * @param aHandle Open file handle. * @param aFileName File name (for reference). */ File (RTFILE aHandle, const char *aFileName = NULL); /** * Destrroys the File object. If the object was created from a file name * the corresponding file will be automatically closed. If the object was * created from a file handle, it will remain open. */ virtual ~File(); const char *uri() const; uint64_t pos() const; void setPos (uint64_t aPos); /** * See Input::read(). If this method is called in wrong file mode, * LogicError will be thrown. */ int read (char *aBuf, int aLen); /** * See Output::write(). If this method is called in wrong file mode, * LogicError will be thrown. */ int write (const char *aBuf, int aLen); /** * See Output::truncate(). If this method is called in wrong file mode, * LogicError will be thrown. */ void truncate(); private: /* Obscure class data */ struct Data; std::auto_ptr m; /* auto_ptr data doesn't have proper copy semantics */ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (File) }; /** * The MemoryBuf class represents a stream implementation that reads from the * memory buffer. */ class VBOXSETTINGS_CLASS MemoryBuf : public Input { public: MemoryBuf (const char *aBuf, size_t aLen, const char *aURI = NULL); virtual ~MemoryBuf(); const char *uri() const; int read (char *aBuf, int aLen); uint64_t pos() const; void setPos (uint64_t aPos); private: /* Obscure class data */ struct Data; std::auto_ptr m; /* auto_ptr data doesn't have proper copy semantics */ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (MemoryBuf) }; class XmlKeyBackend; /** * The XmlTreeBackend class uses XML markup to store settings trees. * * @note libxml2 and libxslt libraries used by the XmlTreeBackend are not * fully reentrant. To "fix" this, the XmlTreeBackend backend serializes access * to such non-reentrant parts using a global mutex so that only one thread can * use non-reentrant code at a time. Currently, this relates to the #rawRead() * method (and to #read() as a consequence). This menas that only one thread can * parse an XML stream at a time; other threads trying to parse same or * different streams using different XmlTreeBackend and Input instances * will have to wait. * * Keep in mind that the above reentrancy fix does not imply thread-safety: it * is still the caller's responsibility to provide serialization if the same * XmlTreeBackend instnace (as well as instances of other classes from the * settings namespace) needs to be used by more than one thread. */ class VBOXSETTINGS_CLASS XmlTreeBackend : public TreeBackend { public: /** Flags for TreeBackend::read(). */ enum { /** * Sbstitute default values for missing attributes that have defaults * in the XML schema. Otherwise, stringValue() will return NULL for * such attributes. */ Read_AddDefaults = RT_BIT (0), }; /** * The Error class represents errors that may happen when parsing or * validating the XML document representing the settings tree. */ class VBOXSETTINGS_CLASS Error : public RuntimeError { public: Error (const char *aMsg = NULL) : RuntimeError (aMsg) {} }; /** * The EConversionCycle class represents a conversion cycle detected by the * AutoConverter::needsConversion() implementation. */ class VBOXSETTINGS_CLASS EConversionCycle : public Error { public: EConversionCycle (const char *aMsg = NULL) : Error (aMsg) {} }; /** * The InputResolver class represents an interface to provide input streams * for external entities given an URL and entity ID. */ class VBOXSETTINGS_CLASS InputResolver { public: /** * Returns a newly allocated input stream for the given arguments. The * caller will delete the returned object when no more necessary. * * @param aURI URI of the external entity. * @param aID ID of the external entity (may be NULL). * * @return Input stream created using @c new or NULL to indicate * a wrong URI/ID pair. * * @todo Return by value after implementing the copy semantics for * Input subclasses. */ virtual Input *resolveEntity (const char *aURI, const char *aID) = 0; }; /** * The AutoConverter class represents an interface to automatically convert * old settings trees to a new version when the tree is read from the * stream. */ class VBOXSETTINGS_CLASS AutoConverter { public: /** * Returns @true if the given tree needs to be converted using the XSLT * template identified by #templateUri(), or @false if no conversion is * required. * * The implementation normally checks for the "version" value of the * root key to determine if the conversion is necessary. When the * @a aOldVersion argument is not NULL, the implementation must return a * non-NULL non-empty string representing the old version (before * conversion) in it this string is used by XmlTreeBackend::oldVersion() * and must be non-NULL to indicate that the conversion has been * performed on the tree. The returned string must be allocated using * RTStrDup() or such. * * This method is called again after the successful transformation to * let the implementation retry the version check and request another * transformation if necessary. This may be used to perform multi-step * conversion like this: 1.1 => 1.2, 1.2 => 1.3 (instead of 1.1 => 1.3) * which saves from the need to update all previous conversion * templates to make each of them convert directly to the recent * version. * * @note Multi-step transformations are performed in a loop that exits * only when this method returns @false. It's up to the * implementation to detect cycling (repeated requests to convert * from the same version) wrong version order, etc. and throw an * EConversionCycle exception to break the loop without returning * @false (which means the transformation succeeded). * * @param aRoot Root settings key. * @param aOldVersionString Where to store old version string * pointer. May be NULL. */ virtual bool needsConversion (const Key &aRoot, char **aOldVersion) const = 0; /** * Returns the URI of the XSLT template to perform the conversion. * This template will be applied to the tree if #needsConversion() * returns @c true for this tree. */ virtual const char *templateUri() const = 0; }; XmlTreeBackend(); ~XmlTreeBackend(); /** * Sets an external entity resolver used to provide input streams for * entities referred to by the XML document being parsed. * * The given resolver object must exist as long as this instance exists or * until a different resolver is set using setInputResolver() or reset * using resetInputResolver(). * * @param aResolver Resolver to use. */ void setInputResolver (InputResolver &aResolver); /** * Resets the entity resolver to the default resolver. The default * resolver provides support for 'file:' and 'http:' protocols. */ void resetInputResolver(); /** * Sets a settings tree converter and enables the automatic conversion. * * The Automatic settings tree conversion is useful for upgrading old * settings files to the new version transparently during execution of the * #read() method. * * The automatic conversion takes place after reading the document from the * stream but before validating it. The given converter is asked if the * conversion is necessary using the AutoConverter::needsConversion() call, * and if so, the XSLT template specified by AutoConverter::templateUri() is * applied to the settings tree. * * Note that in order to make the result of the conversion permanent, the * settings tree needs to be exlicitly written back to the stream. * * The given converter object must exist as long as this instance exists or * until a different converter is set using setAutoConverter() or reset * using resetAutoConverter(). * * @param aConverter Settings converter to use. */ void setAutoConverter (AutoConverter &aConverter); /** * Disables the automatic settings conversion previously enabled by * setAutoConverter(). By default automatic conversion it is disabled. */ void resetAutoConverter(); /** * Returns a non-NULL string if the automatic settings conversion has been * performed during the last successful #read() call. Returns @c NULL if * there was no settings conversion. * * If #read() fails, this method will return the version string set by the * previous successful #read() call or @c NULL if there were no #read() * calls. */ const char *oldVersion() const; void rawRead (Input &aInput, const char *aSchema = NULL, int aFlags = 0); void rawWrite (Output &aOutput); void reset(); Key &rootKey() const; private: class XmlError; /* Obscure class data */ struct Data; std::auto_ptr m; /* auto_ptr data doesn't have proper copy semantics */ DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP (XmlTreeBackend) static int ReadCallback (void *aCtxt, char *aBuf, int aLen); static int WriteCallback (void *aCtxt, const char *aBuf, int aLen); static int CloseCallback (void *aCtxt); static void ValidityErrorCallback (void *aCtxt, const char *aMsg, ...); static void ValidityWarningCallback (void *aCtxt, const char *aMsg, ...); static void StructuredErrorCallback (void *aCtxt, xmlErrorPtr aErr); static xmlParserInput *ExternalEntityLoader (const char *aURI, const char *aID, xmlParserCtxt *aCtxt); static XmlTreeBackend *sThat; static XmlKeyBackend *GetKeyBackend (const Key &aKey) { return (XmlKeyBackend *) TreeBackend::GetKeyBackend (aKey); } }; } /* namespace settings */ #if defined(_MSC_VER) #pragma warning (default:4251) #endif #endif /* IN_RING3 */ /** @} */ #endif /* ___VBox_settings_h */