/* $Id: CFGM.cpp $ */ /** @file * CFGM - Configuration Manager. */ /* * Copyright (C) 2006-2022 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /** @page pg_cfgm CFGM - The Configuration Manager * * The configuration manager is a directory containing the VM configuration at * run time. It works in a manner similar to the windows registry - it's like a * file system hierarchy, but the files (values) live in a separate name space * and can include the path separators. * * The configuration is normally created via a callback passed to VMR3Create() * via the pfnCFGMConstructor parameter. To make testcase writing a bit simpler, * we allow the callback to be NULL, in which case a simple default * configuration will be created by CFGMR3ConstructDefaultTree(). The * Console::configConstructor() method in Main/ConsoleImpl2.cpp creates the * configuration from the XML. * * Devices, drivers, services and other PDM stuff are given their own subtree * where they are protected from accessing information of any parents. This is * is implemented via the CFGMR3SetRestrictedRoot() API. * * Data validation beyond the basic primitives is left to the caller. The caller * is in a better position to know the proper validation rules of the individual * properties. * * @see grp_cfgm * * * @section sec_cfgm_primitives Data Primitives * * CFGM supports the following data primitives: * - Integers. Representation is unsigned 64-bit. Boolean, unsigned and * small integers, and pointers are all represented using this primitive. * - Zero terminated character strings. These are of course UTF-8. * - Variable length byte strings. This can be used to get/put binary * objects like for instance RTMAC. * */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_CFGM #include #include #include #include #include "CFGMInternal.h" #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static void cfgmR3DumpPath(PCFGMNODE pNode, PCDBGFINFOHLP pHlp); static void cfgmR3Dump(PCFGMNODE pRoot, unsigned iLevel, PCDBGFINFOHLP pHlp); static DECLCALLBACK(void) cfgmR3Info(PVM pVM, PCDBGFINFOHLP pHlp, const char *pszArgs); static int cfgmR3ResolveNode(PCFGMNODE pNode, const char *pszPath, PCFGMNODE *ppChild); static int cfgmR3ResolveLeaf(PCFGMNODE pNode, const char *pszName, PCFGMLEAF *ppLeaf); static int cfgmR3InsertLeaf(PCFGMNODE pNode, const char *pszName, PCFGMLEAF *ppLeaf); static void cfgmR3RemoveLeaf(PCFGMNODE pNode, PCFGMLEAF pLeaf); static void cfgmR3FreeValue(PVM pVM, PCFGMLEAF pLeaf); /** @todo replace pVM for pUVM !*/ /** * Allocator wrapper. * * @returns Pointer to the allocated memory, NULL on failure. * @param pVM The cross context VM structure, if the tree * is associated with one. * @param enmTag The allocation tag. * @param cb The size of the allocation. */ static void *cfgmR3MemAlloc(PVM pVM, MMTAG enmTag, size_t cb) { if (pVM) return MMR3HeapAlloc(pVM, enmTag, cb); return RTMemAlloc(cb); } /** * Free wrapper. * * @returns Pointer to the allocated memory, NULL on failure. * @param pVM The cross context VM structure, if the tree * is associated with one. * @param pv The memory block to free. */ static void cfgmR3MemFree(PVM pVM, void *pv) { if (pVM) MMR3HeapFree(pv); else RTMemFree(pv); } /** * String allocator wrapper. * * @returns Pointer to the allocated memory, NULL on failure. * @param pVM The cross context VM structure, if the tree * is associated with one. * @param enmTag The allocation tag. * @param cbString The size of the allocation, terminator included. */ static char *cfgmR3StrAlloc(PVM pVM, MMTAG enmTag, size_t cbString) { if (pVM) return (char *)MMR3HeapAlloc(pVM, enmTag, cbString); return (char *)RTStrAlloc(cbString); } /** * String free wrapper. * * @returns Pointer to the allocated memory, NULL on failure. * @param pVM The cross context VM structure, if the tree * is associated with one. * @param pszString The memory block to free. */ static void cfgmR3StrFree(PVM pVM, char *pszString) { if (pVM) MMR3HeapFree(pszString); else RTStrFree(pszString); } /** * Frees one node, leaving any children or leaves to the caller. * * @param pNode The node structure to free. */ static void cfgmR3FreeNodeOnly(PCFGMNODE pNode) { pNode->pFirstLeaf = NULL; pNode->pFirstChild = NULL; pNode->pNext = NULL; pNode->pPrev = NULL; if (!pNode->pVM) RTMemFree(pNode); else { pNode->pVM = NULL; MMR3HeapFree(pNode); } } /** * Constructs the configuration for the VM. * * This should only be called used once. * * @returns VBox status code. * @param pVM The cross context VM structure. * @param pfnCFGMConstructor Pointer to callback function for constructing * the VM configuration tree. This is called on * the EMT. * @param pvUser The user argument passed to pfnCFGMConstructor. * @thread EMT. * @internal */ VMMR3DECL(int) CFGMR3Init(PVM pVM, PFNCFGMCONSTRUCTOR pfnCFGMConstructor, void *pvUser) { LogFlow(("CFGMR3Init: pfnCFGMConstructor=%p pvUser=%p\n", pfnCFGMConstructor, pvUser)); /* * Init data members. */ pVM->cfgm.s.pRoot = NULL; /* * Register DBGF into item. */ int rc = DBGFR3InfoRegisterInternal(pVM, "cfgm", "Dumps a part of the CFGM tree. The argument indicates where to start.", cfgmR3Info); AssertRCReturn(rc,rc); /* * Root Node. */ PCFGMNODE pRoot = (PCFGMNODE)MMR3HeapAllocZ(pVM, MM_TAG_CFGM, sizeof(*pRoot)); if (!pRoot) return VERR_NO_MEMORY; pRoot->pVM = pVM; pRoot->cchName = 0; pVM->cfgm.s.pRoot = pRoot; /* * Call the constructor if specified, if not use the default one. */ if (pfnCFGMConstructor) rc = pfnCFGMConstructor(pVM->pUVM, pVM, VMMR3GetVTable(), pvUser); else rc = CFGMR3ConstructDefaultTree(pVM); if (RT_SUCCESS(rc)) { Log(("CFGMR3Init: Successfully constructed the configuration\n")); CFGMR3Dump(CFGMR3GetRoot(pVM)); } else LogRel(("Constructor failed with rc=%Rrc pfnCFGMConstructor=%p\n", rc, pfnCFGMConstructor)); return rc; } /** * Terminates the configuration manager. * * @returns VBox status code. * @param pVM The cross context VM structure. * @internal */ VMMR3DECL(int) CFGMR3Term(PVM pVM) { CFGMR3RemoveNode(pVM->cfgm.s.pRoot); pVM->cfgm.s.pRoot = NULL; return 0; } /** * Gets the root node for the VM. * * @returns Pointer to root node. * @param pVM The cross context VM structure. */ VMMR3DECL(PCFGMNODE) CFGMR3GetRoot(PVM pVM) { return pVM->cfgm.s.pRoot; } /** * Gets the root node for the VM. * * @returns Pointer to root node. * @param pUVM The user mode VM structure. */ VMMR3DECL(PCFGMNODE) CFGMR3GetRootU(PUVM pUVM) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, NULL); PVM pVM = pUVM->pVM; AssertReturn(pVM, NULL); return pVM->cfgm.s.pRoot; } /** * Gets the parent of a CFGM node. * * @returns Pointer to the parent node. * @returns NULL if pNode is Root or pNode is the start of a * restricted subtree (use CFGMR3GetParentEx() for that). * * @param pNode The node which parent we query. */ VMMR3DECL(PCFGMNODE) CFGMR3GetParent(PCFGMNODE pNode) { if (pNode && !pNode->fRestrictedRoot) return pNode->pParent; return NULL; } /** * Gets the parent of a CFGM node. * * @returns Pointer to the parent node. * @returns NULL if pNode is Root or pVM is not correct. * * @param pVM The cross context VM structure. Used as token that * the caller is trusted. * @param pNode The node which parent we query. */ VMMR3DECL(PCFGMNODE) CFGMR3GetParentEx(PVM pVM, PCFGMNODE pNode) { if (pNode && pNode->pVM == pVM) return pNode->pParent; return NULL; } /** * Query a child node. * * @returns Pointer to the specified node. * @returns NULL if node was not found or pNode is NULL. * @param pNode Node pszPath is relative to. * @param pszPath Path to the child node or pNode. * It's good style to end this with '/'. */ VMMR3DECL(PCFGMNODE) CFGMR3GetChild(PCFGMNODE pNode, const char *pszPath) { PCFGMNODE pChild; int rc = cfgmR3ResolveNode(pNode, pszPath, &pChild); if (RT_SUCCESS(rc)) return pChild; return NULL; } /** * Query a child node by a format string. * * @returns Pointer to the specified node. * @returns NULL if node was not found or pNode is NULL. * @param pNode Node pszPath is relative to. * @param pszPathFormat Path to the child node or pNode. * It's good style to end this with '/'. * @param ... Arguments to pszPathFormat. */ VMMR3DECL(PCFGMNODE) CFGMR3GetChildF(PCFGMNODE pNode, const char *pszPathFormat, ...) { va_list Args; va_start(Args, pszPathFormat); PCFGMNODE pRet = CFGMR3GetChildFV(pNode, pszPathFormat, Args); va_end(Args); return pRet; } /** * Query a child node by a format string. * * @returns Pointer to the specified node. * @returns NULL if node was not found or pNode is NULL. * @param pNode Node pszPath is relative to. * @param pszPathFormat Path to the child node or pNode. * It's good style to end this with '/'. * @param Args Arguments to pszPathFormat. */ VMMR3DECL(PCFGMNODE) CFGMR3GetChildFV(PCFGMNODE pNode, const char *pszPathFormat, va_list Args) { char *pszPath; RTStrAPrintfV(&pszPath, pszPathFormat, Args); if (pszPath) { PCFGMNODE pChild; int rc = cfgmR3ResolveNode(pNode, pszPath, &pChild); RTStrFree(pszPath); if (RT_SUCCESS(rc)) return pChild; } return NULL; } /** * Gets the first child node. * Use this to start an enumeration of child nodes. * * @returns Pointer to the first child. * @returns NULL if no children. * @param pNode Node to enumerate children for. */ VMMR3DECL(PCFGMNODE) CFGMR3GetFirstChild(PCFGMNODE pNode) { return pNode ? pNode->pFirstChild : NULL; } /** * Gets the next sibling node. * Use this to continue an enumeration. * * @returns Pointer to the first child. * @returns NULL if no children. * @param pCur Node to returned by a call to CFGMR3GetFirstChild() * or successive calls to this function. */ VMMR3DECL(PCFGMNODE) CFGMR3GetNextChild(PCFGMNODE pCur) { return pCur ? pCur->pNext : NULL; } /** * Gets the name of the current node. * (Needed for enumeration.) * * @returns VBox status code. * @param pCur Node to returned by a call to CFGMR3GetFirstChild() * or successive calls to CFGMR3GetNextChild(). * @param pszName Where to store the node name. * @param cchName Size of the buffer pointed to by pszName (with terminator). */ VMMR3DECL(int) CFGMR3GetName(PCFGMNODE pCur, char *pszName, size_t cchName) { int rc; if (pCur) { if (cchName > pCur->cchName) { rc = VINF_SUCCESS; memcpy(pszName, pCur->szName, pCur->cchName + 1); } else rc = VERR_CFGM_NOT_ENOUGH_SPACE; } else rc = VERR_CFGM_NO_NODE; return rc; } /** * Gets the length of the current node's name. * (Needed for enumeration.) * * @returns Node name length in bytes including the terminating null char. * @returns 0 if pCur is NULL. * @param pCur Node to returned by a call to CFGMR3GetFirstChild() * or successive calls to CFGMR3GetNextChild(). */ VMMR3DECL(size_t) CFGMR3GetNameLen(PCFGMNODE pCur) { return pCur ? pCur->cchName + 1 : 0; } /** * Validates that the child nodes are within a set of valid names. * * @returns true if all names are found in pszzAllowed. * @returns false if not. * @param pNode The node which children should be examined. * @param pszzValid List of valid names separated by '\\0' and ending with * a double '\\0'. * * @deprecated Use CFGMR3ValidateConfig. */ VMMR3DECL(bool) CFGMR3AreChildrenValid(PCFGMNODE pNode, const char *pszzValid) { if (pNode) { for (PCFGMNODE pChild = pNode->pFirstChild; pChild; pChild = pChild->pNext) { /* search pszzValid for the name */ const char *psz = pszzValid; while (*psz) { size_t cch = strlen(psz); if ( cch == pChild->cchName && !memcmp(psz, pChild->szName, cch)) break; /* next */ psz += cch + 1; } /* if at end of pszzValid we didn't find it => failure */ if (!*psz) { AssertMsgFailed(("Couldn't find '%s' in the valid values\n", pChild->szName)); return false; } } } /* all ok. */ return true; } /** * Gets the first value of a node. * Use this to start an enumeration of values. * * @returns Pointer to the first value. * @param pCur The node (Key) which values to enumerate. */ VMMR3DECL(PCFGMLEAF) CFGMR3GetFirstValue(PCFGMNODE pCur) { return pCur ? pCur->pFirstLeaf : NULL; } /** * Gets the next value in enumeration. * * @returns Pointer to the next value. * @param pCur The current value as returned by this function or CFGMR3GetFirstValue(). */ VMMR3DECL(PCFGMLEAF) CFGMR3GetNextValue(PCFGMLEAF pCur) { return pCur ? pCur->pNext : NULL; } /** * Get the value name. * (Needed for enumeration.) * * @returns VBox status code. * @param pCur Value returned by a call to CFGMR3GetFirstValue() * or successive calls to CFGMR3GetNextValue(). * @param pszName Where to store the value name. * @param cchName Size of the buffer pointed to by pszName (with terminator). */ VMMR3DECL(int) CFGMR3GetValueName(PCFGMLEAF pCur, char *pszName, size_t cchName) { int rc; if (pCur) { if (cchName > pCur->cchName) { rc = VINF_SUCCESS; memcpy(pszName, pCur->szName, pCur->cchName + 1); } else rc = VERR_CFGM_NOT_ENOUGH_SPACE; } else rc = VERR_CFGM_NO_NODE; return rc; } /** * Gets the length of the current node's name. * (Needed for enumeration.) * * @returns Value name length in bytes including the terminating null char. * @returns 0 if pCur is NULL. * @param pCur Value returned by a call to CFGMR3GetFirstValue() * or successive calls to CFGMR3GetNextValue(). */ VMMR3DECL(size_t) CFGMR3GetValueNameLen(PCFGMLEAF pCur) { return pCur ? pCur->cchName + 1 : 0; } /** * Gets the value type. * (For enumeration.) * * @returns VBox status code. * @param pCur Value returned by a call to CFGMR3GetFirstValue() * or successive calls to CFGMR3GetNextValue(). */ VMMR3DECL(CFGMVALUETYPE) CFGMR3GetValueType(PCFGMLEAF pCur) { Assert(pCur); return pCur->enmType; } /** * Validates that the values are within a set of valid names. * * @returns true if all names are found in pszzValid. * @returns false if not. * @param pNode The node which values should be examined. * @param pszzValid List of valid names separated by '\\0' and ending with * a double '\\0'. * @deprecated Use CFGMR3ValidateConfig. */ VMMR3DECL(bool) CFGMR3AreValuesValid(PCFGMNODE pNode, const char *pszzValid) { if (pNode) { for (PCFGMLEAF pLeaf = pNode->pFirstLeaf; pLeaf; pLeaf = pLeaf->pNext) { /* search pszzValid for the name */ const char *psz = pszzValid; while (*psz) { size_t cch = strlen(psz); if ( cch == pLeaf->cchName && !memcmp(psz, pLeaf->szName, cch)) break; /* next */ psz += cch + 1; } /* if at end of pszzValid we didn't find it => failure */ if (!*psz) { AssertMsgFailed(("Couldn't find '%s' in the valid values\n", pLeaf->szName)); return false; } } } /* all ok. */ return true; } /** * Checks if the given value exists. * * @returns true if it exists, false if not. * @param pNode Which node to search for pszName in. * @param pszName The name of the value we seek. */ VMMR3DECL(bool) CFGMR3Exists(PCFGMNODE pNode, const char *pszName) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); return RT_SUCCESS_NP(rc); } /** * Query value type. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param penmType Where to store the type. */ VMMR3DECL(int) CFGMR3QueryType(PCFGMNODE pNode, const char *pszName, PCFGMVALUETYPE penmType) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { if (penmType) *penmType = pLeaf->enmType; } return rc; } /** * Query value size. * This works on all types of values. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pcb Where to store the value size. */ VMMR3DECL(int) CFGMR3QuerySize(PCFGMNODE pNode, const char *pszName, size_t *pcb) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { switch (pLeaf->enmType) { case CFGMVALUETYPE_INTEGER: *pcb = sizeof(pLeaf->Value.Integer.u64); break; case CFGMVALUETYPE_STRING: case CFGMVALUETYPE_PASSWORD: *pcb = pLeaf->Value.String.cb; break; case CFGMVALUETYPE_BYTES: *pcb = pLeaf->Value.Bytes.cb; break; default: rc = VERR_CFGM_IPE_1; AssertMsgFailed(("Invalid value type %d\n", pLeaf->enmType)); break; } } return rc; } /** * Query integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu64 Where to store the integer value. */ VMMR3DECL(int) CFGMR3QueryInteger(PCFGMNODE pNode, const char *pszName, uint64_t *pu64) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { if (pLeaf->enmType == CFGMVALUETYPE_INTEGER) *pu64 = pLeaf->Value.Integer.u64; else rc = VERR_CFGM_NOT_INTEGER; } return rc; } /** * Query integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu64 Where to store the integer value. This is set to the default on failure. * @param u64Def The default value. This is always set. */ VMMR3DECL(int) CFGMR3QueryIntegerDef(PCFGMNODE pNode, const char *pszName, uint64_t *pu64, uint64_t u64Def) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { if (pLeaf->enmType == CFGMVALUETYPE_INTEGER) *pu64 = pLeaf->Value.Integer.u64; else rc = VERR_CFGM_NOT_INTEGER; } if (RT_FAILURE(rc)) { *pu64 = u64Def; if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) rc = VINF_SUCCESS; } return rc; } /** * Query zero terminated character value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of a zero terminate character value. * @param pszString Where to store the string. * @param cchString Size of the string buffer. (Includes terminator.) */ VMMR3DECL(int) CFGMR3QueryString(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { if (pLeaf->enmType == CFGMVALUETYPE_STRING) { size_t cbSrc = pLeaf->Value.String.cb; if (cchString >= cbSrc) { memcpy(pszString, pLeaf->Value.String.psz, cbSrc); memset(pszString + cbSrc, 0, cchString - cbSrc); } else rc = VERR_CFGM_NOT_ENOUGH_SPACE; } else rc = VERR_CFGM_NOT_STRING; } return rc; } /** * Query zero terminated character value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of a zero terminate character value. * @param pszString Where to store the string. This will not be set on overflow error. * @param cchString Size of the string buffer. (Includes terminator.) * @param pszDef The default value. */ VMMR3DECL(int) CFGMR3QueryStringDef(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString, const char *pszDef) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { if (pLeaf->enmType == CFGMVALUETYPE_STRING) { size_t cbSrc = pLeaf->Value.String.cb; if (cchString >= cbSrc) { memcpy(pszString, pLeaf->Value.String.psz, cbSrc); memset(pszString + cbSrc, 0, cchString - cbSrc); } else rc = VERR_CFGM_NOT_ENOUGH_SPACE; } else rc = VERR_CFGM_NOT_STRING; } if (RT_FAILURE(rc) && rc != VERR_CFGM_NOT_ENOUGH_SPACE) { size_t cchDef = strlen(pszDef); if (cchString > cchDef) { memcpy(pszString, pszDef, cchDef); memset(pszString + cchDef, 0, cchString - cchDef); if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) rc = VINF_SUCCESS; } else if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) rc = VERR_CFGM_NOT_ENOUGH_SPACE; } return rc; } /** * Query byte string value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of a byte string value. * @param pvData Where to store the binary data. * @param cbData Size of buffer pvData points too. */ VMMR3DECL(int) CFGMR3QueryBytes(PCFGMNODE pNode, const char *pszName, void *pvData, size_t cbData) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { if (pLeaf->enmType == CFGMVALUETYPE_BYTES) { if (cbData >= pLeaf->Value.Bytes.cb) { memcpy(pvData, pLeaf->Value.Bytes.pau8, pLeaf->Value.Bytes.cb); memset((char *)pvData + pLeaf->Value.Bytes.cb, 0, cbData - pLeaf->Value.Bytes.cb); } else rc = VERR_CFGM_NOT_ENOUGH_SPACE; } else rc = VERR_CFGM_NOT_BYTES; } return rc; } /** * Query zero terminated character value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of a zero terminate character value. * @param pszString Where to store the string. * @param cchString Size of the string buffer. (Includes terminator.) * * @note Concurrent calls to this function and CFGMR3QueryPasswordDef are not * supported. */ VMMR3DECL(int) CFGMR3QueryPassword(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { if (pLeaf->enmType == CFGMVALUETYPE_PASSWORD) { size_t cbSrc = pLeaf->Value.String.cb; if (cchString >= cbSrc) { RTMemSaferUnscramble(pLeaf->Value.String.psz, cbSrc); memcpy(pszString, pLeaf->Value.String.psz, cbSrc); memset(pszString + cbSrc, 0, cchString - cbSrc); RTMemSaferScramble(pLeaf->Value.String.psz, cbSrc); Assert(pszString[cbSrc - 1] == '\0'); } else rc = VERR_CFGM_NOT_ENOUGH_SPACE; } else rc = VERR_CFGM_NOT_PASSWORD; } return rc; } /** * Query zero terminated character value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of a zero terminate character value. * @param pszString Where to store the string. This will not be set on overflow error. * @param cchString Size of the string buffer. (Includes terminator.) * @param pszDef The default value. * * @note Concurrent calls to this function and CFGMR3QueryPassword are not * supported. */ VMMR3DECL(int) CFGMR3QueryPasswordDef(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString, const char *pszDef) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { if (pLeaf->enmType == CFGMVALUETYPE_PASSWORD) { size_t cbSrc = pLeaf->Value.String.cb; if (cchString >= cbSrc) { RTMemSaferUnscramble(pLeaf->Value.String.psz, cbSrc); memcpy(pszString, pLeaf->Value.String.psz, cbSrc); memset(pszString + cbSrc, 0, cchString - cbSrc); RTMemSaferScramble(pLeaf->Value.String.psz, cbSrc); Assert(pszString[cbSrc - 1] == '\0'); } else rc = VERR_CFGM_NOT_ENOUGH_SPACE; } else rc = VERR_CFGM_NOT_PASSWORD; } if (RT_FAILURE(rc) && rc != VERR_CFGM_NOT_ENOUGH_SPACE) { size_t cchDef = strlen(pszDef); if (cchString > cchDef) { memcpy(pszString, pszDef, cchDef); memset(pszString + cchDef, 0, cchString - cchDef); if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) rc = VINF_SUCCESS; } else if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) rc = VERR_CFGM_NOT_ENOUGH_SPACE; } return rc; } /** * Validate one level of a configuration node. * * This replaces the CFGMR3AreChildrenValid and CFGMR3AreValuesValid APIs. * * @returns VBox status code. * * When an error is returned, both VMSetError and AssertLogRelMsgFailed * have been called. So, all the caller needs to do is to propagate * the error status up to PDM. * * @param pNode The node to validate. * @param pszNode The node path, always ends with a slash. Use * "/" for the root config node. * @param pszValidValues Patterns describing the valid value names. See * RTStrSimplePatternMultiMatch for details on the * pattern syntax. * @param pszValidNodes Patterns describing the valid node (key) names. * See RTStrSimplePatternMultiMatch for details on * the pattern syntax. * @param pszWho Who is calling. * @param uInstance The instance number of the caller. */ VMMR3DECL(int) CFGMR3ValidateConfig(PCFGMNODE pNode, const char *pszNode, const char *pszValidValues, const char *pszValidNodes, const char *pszWho, uint32_t uInstance) { /* Input validation. */ AssertPtrNullReturn(pNode, VERR_INVALID_POINTER); AssertPtrReturn(pszNode, VERR_INVALID_POINTER); Assert(*pszNode && pszNode[strlen(pszNode) - 1] == '/'); AssertPtrReturn(pszValidValues, VERR_INVALID_POINTER); AssertPtrReturn(pszValidNodes, VERR_INVALID_POINTER); AssertPtrReturn(pszWho, VERR_INVALID_POINTER); if (pNode) { /* * Enumerate the leaves and check them against pszValidValues. */ for (PCFGMLEAF pLeaf = pNode->pFirstLeaf; pLeaf; pLeaf = pLeaf->pNext) { if (!RTStrSimplePatternMultiMatch(pszValidValues, RTSTR_MAX, pLeaf->szName, pLeaf->cchName, NULL)) { AssertLogRelMsgFailed(("%s/%u: Value '%s%s' didn't match '%s'\n", pszWho, uInstance, pszNode, pLeaf->szName, pszValidValues)); return VMSetError(pNode->pVM, VERR_CFGM_CONFIG_UNKNOWN_VALUE, RT_SRC_POS, N_("Unknown configuration value '%s%s' found in the configuration of %s instance #%u"), pszNode, pLeaf->szName, pszWho, uInstance); } } /* * Enumerate the child nodes and check them against pszValidNodes. */ for (PCFGMNODE pChild = pNode->pFirstChild; pChild; pChild = pChild->pNext) { if (!RTStrSimplePatternMultiMatch(pszValidNodes, RTSTR_MAX, pChild->szName, pChild->cchName, NULL)) { AssertLogRelMsgFailed(("%s/%u: Node '%s%s' didn't match '%s'\n", pszWho, uInstance, pszNode, pChild->szName, pszValidNodes)); return VMSetError(pNode->pVM, VERR_CFGM_CONFIG_UNKNOWN_NODE, RT_SRC_POS, N_("Unknown configuration node '%s%s' found in the configuration of %s instance #%u"), pszNode, pChild->szName, pszWho, uInstance); } } } /* All is well. */ return VINF_SUCCESS; } /** * Populates the CFGM tree with the default configuration. * * This assumes an empty tree and is intended for testcases and such that only * need to do very small adjustments to the config. * * @returns VBox status code. * @param pVM The cross context VM structure. * @internal */ VMMR3DECL(int) CFGMR3ConstructDefaultTree(PVM pVM) { int rc; int rcAll = VINF_SUCCESS; #define UPDATERC() do { if (RT_FAILURE(rc) && RT_SUCCESS(rcAll)) rcAll = rc; } while (0) PCFGMNODE pRoot = CFGMR3GetRoot(pVM); AssertReturn(pRoot, VERR_WRONG_ORDER); /* * Create VM default values. */ rc = CFGMR3InsertString(pRoot, "Name", "Default VM"); UPDATERC(); rc = CFGMR3InsertInteger(pRoot, "RamSize", 128U * _1M); UPDATERC(); rc = CFGMR3InsertInteger(pRoot, "RamHoleSize", 512U * _1M); UPDATERC(); rc = CFGMR3InsertInteger(pRoot, "TimerMillies", 10); UPDATERC(); /* * PDM. */ PCFGMNODE pPdm; rc = CFGMR3InsertNode(pRoot, "PDM", &pPdm); UPDATERC(); PCFGMNODE pDevices = NULL; rc = CFGMR3InsertNode(pPdm, "Devices", &pDevices); UPDATERC(); rc = CFGMR3InsertInteger(pDevices, "LoadBuiltin", 1); /* boolean */ UPDATERC(); PCFGMNODE pDrivers = NULL; rc = CFGMR3InsertNode(pPdm, "Drivers", &pDrivers); UPDATERC(); rc = CFGMR3InsertInteger(pDrivers, "LoadBuiltin", 1); /* boolean */ UPDATERC(); /* * Devices */ pDevices = NULL; rc = CFGMR3InsertNode(pRoot, "Devices", &pDevices); UPDATERC(); /* device */ PCFGMNODE pDev = NULL; PCFGMNODE pInst = NULL; PCFGMNODE pCfg = NULL; #if 0 PCFGMNODE pLunL0 = NULL; PCFGMNODE pLunL1 = NULL; #endif /* * PC Arch. */ rc = CFGMR3InsertNode(pDevices, "pcarch", &pDev); UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); rc = CFGMR3InsertInteger(pInst, "Trusted", 1); /* boolean */ UPDATERC(); rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); /* * PC Bios. */ rc = CFGMR3InsertNode(pDevices, "pcbios", &pDev); UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); rc = CFGMR3InsertInteger(pInst, "Trusted", 1); /* boolean */ UPDATERC(); rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); rc = CFGMR3InsertString(pCfg, "BootDevice0", "IDE"); UPDATERC(); rc = CFGMR3InsertString(pCfg, "BootDevice1", "NONE"); UPDATERC(); rc = CFGMR3InsertString(pCfg, "BootDevice2", "NONE"); UPDATERC(); rc = CFGMR3InsertString(pCfg, "BootDevice3", "NONE"); UPDATERC(); rc = CFGMR3InsertString(pCfg, "HardDiskDevice", "piix3ide"); UPDATERC(); rc = CFGMR3InsertString(pCfg, "FloppyDevice", ""); UPDATERC(); RTUUID Uuid; RTUuidClear(&Uuid); rc = CFGMR3InsertBytes(pCfg, "UUID", &Uuid, sizeof(Uuid)); UPDATERC(); /* * PCI bus. */ rc = CFGMR3InsertNode(pDevices, "pci", &pDev); /* piix3 */ UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); rc = CFGMR3InsertInteger(pInst, "Trusted", 1); /* boolean */ UPDATERC(); rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); /* * PS/2 keyboard & mouse */ rc = CFGMR3InsertNode(pDevices, "pckbd", &pDev); UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); #if 0 rc = CFGMR3InsertNode(pInst, "LUN#0", &pLunL0); UPDATERC(); rc = CFGMR3InsertString(pLunL0, "Driver", "KeyboardQueue"); UPDATERC(); rc = CFGMR3InsertNode(pLunL0, "Config", &pCfg); UPDATERC(); rc = CFGMR3InsertInteger(pCfg, "QueueSize", 64); UPDATERC(); rc = CFGMR3InsertNode(pLunL0, "AttachedDriver", &pLunL1); UPDATERC(); rc = CFGMR3InsertString(pLunL1, "Driver", "MainKeyboard"); UPDATERC(); rc = CFGMR3InsertNode(pLunL1, "Config", &pCfg); UPDATERC(); #endif #if 0 rc = CFGMR3InsertNode(pInst, "LUN#1", &pLunL0); UPDATERC(); rc = CFGMR3InsertString(pLunL0, "Driver", "MouseQueue"); UPDATERC(); rc = CFGMR3InsertNode(pLunL0, "Config", &pCfg); UPDATERC(); rc = CFGMR3InsertInteger(pCfg, "QueueSize", 128); UPDATERC(); rc = CFGMR3InsertNode(pLunL0, "AttachedDriver", &pLunL1); UPDATERC(); rc = CFGMR3InsertString(pLunL1, "Driver", "MainMouse"); UPDATERC(); rc = CFGMR3InsertNode(pLunL1, "Config", &pCfg); UPDATERC(); #endif /* * i8254 Programmable Interval Timer And Dummy Speaker */ rc = CFGMR3InsertNode(pDevices, "i8254", &pDev); UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); #ifdef DEBUG rc = CFGMR3InsertInteger(pInst, "Trusted", 1); /* boolean */ UPDATERC(); #endif rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); /* * i8259 Programmable Interrupt Controller. */ rc = CFGMR3InsertNode(pDevices, "i8259", &pDev); UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); rc = CFGMR3InsertInteger(pInst, "Trusted", 1); /* boolean */ UPDATERC(); rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); /* * RTC MC146818. */ rc = CFGMR3InsertNode(pDevices, "mc146818", &pDev); UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); /* * VGA. */ rc = CFGMR3InsertNode(pDevices, "vga", &pDev); UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); rc = CFGMR3InsertInteger(pInst, "Trusted", 1); /* boolean */ UPDATERC(); rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); rc = CFGMR3InsertInteger(pCfg, "VRamSize", 4 * _1M); UPDATERC(); /* Bios logo. */ rc = CFGMR3InsertInteger(pCfg, "FadeIn", 1); UPDATERC(); rc = CFGMR3InsertInteger(pCfg, "FadeOut", 1); UPDATERC(); rc = CFGMR3InsertInteger(pCfg, "LogoTime", 0); UPDATERC(); rc = CFGMR3InsertString(pCfg, "LogoFile", ""); UPDATERC(); #if 0 rc = CFGMR3InsertNode(pInst, "LUN#0", &pLunL0); UPDATERC(); rc = CFGMR3InsertString(pLunL0, "Driver", "MainDisplay"); UPDATERC(); #endif /* * IDE controller. */ rc = CFGMR3InsertNode(pDevices, "piix3ide", &pDev); /* piix3 */ UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); rc = CFGMR3InsertInteger(pInst, "Trusted", 1); /* boolean */ UPDATERC(); rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); /* * VMMDev. */ rc = CFGMR3InsertNode(pDevices, "VMMDev", &pDev); UPDATERC(); rc = CFGMR3InsertNode(pDev, "0", &pInst); UPDATERC(); rc = CFGMR3InsertNode(pInst, "Config", &pCfg); UPDATERC(); rc = CFGMR3InsertInteger(pInst, "Trusted", 1); /* boolean */ UPDATERC(); /* * ... */ #undef UPDATERC return rcAll; } /** * Resolves a path reference to a child node. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszPath Path to the child node. * @param ppChild Where to store the pointer to the child node. */ static int cfgmR3ResolveNode(PCFGMNODE pNode, const char *pszPath, PCFGMNODE *ppChild) { *ppChild = NULL; if (!pNode) return VERR_CFGM_NO_PARENT; PCFGMNODE pChild = NULL; for (;;) { /* skip leading slashes. */ while (*pszPath == '/') pszPath++; /* End of path? */ if (!*pszPath) { if (!pChild) return VERR_CFGM_INVALID_CHILD_PATH; *ppChild = pChild; return VINF_SUCCESS; } /* find end of component. */ const char *pszNext = strchr(pszPath, '/'); if (!pszNext) pszNext = strchr(pszPath, '\0'); RTUINT cchName = pszNext - pszPath; /* search child list. */ pChild = pNode->pFirstChild; for ( ; pChild; pChild = pChild->pNext) if (pChild->cchName == cchName) { int iDiff = memcmp(pszPath, pChild->szName, cchName); if (iDiff <= 0) { if (iDiff != 0) pChild = NULL; break; } } if (!pChild) return VERR_CFGM_CHILD_NOT_FOUND; /* next iteration */ pNode = pChild; pszPath = pszNext; } /* won't get here */ } /** * Resolves a path reference to a child node. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of a byte string value. * @param ppLeaf Where to store the pointer to the leaf node. */ static int cfgmR3ResolveLeaf(PCFGMNODE pNode, const char *pszName, PCFGMLEAF *ppLeaf) { *ppLeaf = NULL; if (!pNode) return VERR_CFGM_NO_PARENT; size_t cchName = strlen(pszName); PCFGMLEAF pLeaf = pNode->pFirstLeaf; while (pLeaf) { if (cchName == pLeaf->cchName) { int iDiff = memcmp(pszName, pLeaf->szName, cchName); if (iDiff <= 0) { if (iDiff != 0) break; *ppLeaf = pLeaf; return VINF_SUCCESS; } } /* next */ pLeaf = pLeaf->pNext; } return VERR_CFGM_VALUE_NOT_FOUND; } /** * Creates a CFGM tree. * * This is intended for creating device/driver configs can be * passed around and later attached to the main tree in the * correct location. * * @returns Pointer to the root node, NULL on error (out of memory or invalid * VM handle). * @param pUVM The user mode VM handle. For testcase (and other * purposes, NULL can be used. However, the resulting * tree cannot be inserted into a tree that has a * non-NULL value. Using NULL can be usedful for * testcases and similar, non VMM uses. */ VMMR3DECL(PCFGMNODE) CFGMR3CreateTree(PUVM pUVM) { if (pUVM) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, NULL); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, NULL); } PCFGMNODE pNew; if (pUVM) pNew = (PCFGMNODE)MMR3HeapAllocU(pUVM, MM_TAG_CFGM, sizeof(*pNew)); else pNew = (PCFGMNODE)RTMemAlloc(sizeof(*pNew)); if (pNew) { pNew->pPrev = NULL; pNew->pNext = NULL; pNew->pParent = NULL; pNew->pFirstChild = NULL; pNew->pFirstLeaf = NULL; pNew->pVM = pUVM ? pUVM->pVM : NULL; pNew->fRestrictedRoot = false; pNew->cchName = 0; pNew->szName[0] = 0; } return pNew; } /** * Duplicates a CFGM sub-tree or a full tree. * * @returns Pointer to the root node. NULL if we run out of memory or the * input parameter is NULL. * @param pRoot The root of the tree to duplicate. * @param ppCopy Where to return the root of the duplicate. */ VMMR3DECL(int) CFGMR3DuplicateSubTree(PCFGMNODE pRoot, PCFGMNODE *ppCopy) { AssertPtrReturn(pRoot, VERR_INVALID_POINTER); /* * Create a new tree. */ PCFGMNODE pNewRoot = CFGMR3CreateTree(pRoot->pVM ? pRoot->pVM->pUVM : NULL); if (!pNewRoot) return VERR_NO_MEMORY; /* * Duplicate the content. */ int rc = VINF_SUCCESS; PCFGMNODE pSrcCur = pRoot; PCFGMNODE pDstCur = pNewRoot; for (;;) { if ( !pDstCur->pFirstChild && !pDstCur->pFirstLeaf) { /* * Values first. */ /** @todo this isn't the most efficient way to do it. */ for (PCFGMLEAF pLeaf = pSrcCur->pFirstLeaf; pLeaf && RT_SUCCESS(rc); pLeaf = pLeaf->pNext) rc = CFGMR3InsertValue(pDstCur, pLeaf); /* * Insert immediate child nodes. */ /** @todo this isn't the most efficient way to do it. */ for (PCFGMNODE pChild = pSrcCur->pFirstChild; pChild && RT_SUCCESS(rc); pChild = pChild->pNext) rc = CFGMR3InsertNode(pDstCur, pChild->szName, NULL); AssertLogRelRCBreak(rc); } /* * Deep copy of the children. */ if (pSrcCur->pFirstChild) { Assert(pDstCur->pFirstChild && !strcmp(pDstCur->pFirstChild->szName, pSrcCur->pFirstChild->szName)); pSrcCur = pSrcCur->pFirstChild; pDstCur = pDstCur->pFirstChild; } /* * If it's the root node, we're done. */ else if (pSrcCur == pRoot) break; else { /* * Upon reaching the end of a sibling list, we must ascend and * resume the sibiling walk on an previous level. */ if (!pSrcCur->pNext) { do { pSrcCur = pSrcCur->pParent; pDstCur = pDstCur->pParent; } while (!pSrcCur->pNext && pSrcCur != pRoot); if (pSrcCur == pRoot) break; } /* * Next sibling. */ Assert(pDstCur->pNext && !strcmp(pDstCur->pNext->szName, pSrcCur->pNext->szName)); pSrcCur = pSrcCur->pNext; pDstCur = pDstCur->pNext; } } if (RT_FAILURE(rc)) { CFGMR3RemoveNode(pNewRoot); return rc; } *ppCopy = pNewRoot; return VINF_SUCCESS; } /** * Insert subtree. * * This function inserts (no duplication) a tree created by CFGMR3CreateTree() * into the main tree. * * The root node of the inserted subtree will need to be reallocated, which * effectually means that the passed in pSubTree handle becomes invalid * upon successful return. Use the value returned in ppChild instead * of pSubTree. * * @returns VBox status code. * @returns VERR_CFGM_NODE_EXISTS if the final child node name component exists. * @param pNode Parent node. * @param pszName Name or path of the new child node. * @param pSubTree The subtree to insert. Must be returned by CFGMR3CreateTree(). * @param ppChild Where to store the address of the new child node. (optional) */ VMMR3DECL(int) CFGMR3InsertSubTree(PCFGMNODE pNode, const char *pszName, PCFGMNODE pSubTree, PCFGMNODE *ppChild) { /* * Validate input. */ AssertPtrReturn(pNode, VERR_INVALID_POINTER); AssertPtrReturn(pSubTree, VERR_INVALID_POINTER); AssertReturn(pNode != pSubTree, VERR_INVALID_PARAMETER); AssertReturn(!pSubTree->pParent, VERR_INVALID_PARAMETER); AssertReturn(pNode->pVM == pSubTree->pVM, VERR_INVALID_PARAMETER); Assert(!pSubTree->pNext); Assert(!pSubTree->pPrev); /* * Use CFGMR3InsertNode to create a new node and then * re-attach the children and leaves of the subtree to it. */ PCFGMNODE pNewChild; int rc = CFGMR3InsertNode(pNode, pszName, &pNewChild); if (RT_SUCCESS(rc)) { Assert(!pNewChild->pFirstChild); Assert(!pNewChild->pFirstLeaf); pNewChild->pFirstChild = pSubTree->pFirstChild; pNewChild->pFirstLeaf = pSubTree->pFirstLeaf; for (PCFGMNODE pChild = pNewChild->pFirstChild; pChild; pChild = pChild->pNext) pChild->pParent = pNewChild; if (ppChild) *ppChild = pNewChild; /* free the old subtree root */ cfgmR3FreeNodeOnly(pSubTree); } return rc; } /** * Replaces a (sub-)tree with new one. * * This function removes the exiting (sub-)tree, completely freeing it in the * process, and inserts (no duplication) the specified tree. The tree can * either be created by CFGMR3CreateTree or CFGMR3DuplicateSubTree. * * @returns VBox status code. * @param pRoot The sub-tree to replace. This node will remain valid * after the call. * @param pNewRoot The tree to replace @a pRoot with. This not will * become invalid after a successful call. */ VMMR3DECL(int) CFGMR3ReplaceSubTree(PCFGMNODE pRoot, PCFGMNODE pNewRoot) { /* * Validate input. */ AssertPtrReturn(pRoot, VERR_INVALID_POINTER); AssertPtrReturn(pNewRoot, VERR_INVALID_POINTER); AssertReturn(pRoot != pNewRoot, VERR_INVALID_PARAMETER); AssertReturn(!pNewRoot->pParent, VERR_INVALID_PARAMETER); AssertReturn(pNewRoot->pVM == pRoot->pVM, VERR_INVALID_PARAMETER); AssertReturn(!pNewRoot->pNext, VERR_INVALID_PARAMETER); AssertReturn(!pNewRoot->pPrev, VERR_INVALID_PARAMETER); /* * Free the current properties fo pRoot. */ while (pRoot->pFirstChild) CFGMR3RemoveNode(pRoot->pFirstChild); while (pRoot->pFirstLeaf) cfgmR3RemoveLeaf(pRoot, pRoot->pFirstLeaf); /* * Copy all the properties from the new root to the current one. */ pRoot->pFirstLeaf = pNewRoot->pFirstLeaf; pRoot->pFirstChild = pNewRoot->pFirstChild; for (PCFGMNODE pChild = pRoot->pFirstChild; pChild; pChild = pChild->pNext) pChild->pParent = pRoot; cfgmR3FreeNodeOnly(pNewRoot); return VINF_SUCCESS; } /** * Copies all values and keys from one tree onto another. * * The flags control what happens to keys and values with the same name * existing in both source and destination. * * @returns VBox status code. * @param pDstTree The destination tree. * @param pSrcTree The source tree. * @param fFlags Copy flags, see CFGM_COPY_FLAGS_XXX. */ VMMR3DECL(int) CFGMR3CopyTree(PCFGMNODE pDstTree, PCFGMNODE pSrcTree, uint32_t fFlags) { /* * Input validation. */ AssertPtrReturn(pSrcTree, VERR_INVALID_POINTER); AssertPtrReturn(pDstTree, VERR_INVALID_POINTER); AssertReturn(pDstTree != pSrcTree, VERR_INVALID_PARAMETER); AssertReturn(!(fFlags & ~(CFGM_COPY_FLAGS_VALUE_DISP_MASK | CFGM_COPY_FLAGS_KEY_DISP_MASK)), VERR_INVALID_PARAMETER); AssertReturn( (fFlags & CFGM_COPY_FLAGS_VALUE_DISP_MASK) != CFGM_COPY_FLAGS_RESERVED_VALUE_DISP_0 && (fFlags & CFGM_COPY_FLAGS_VALUE_DISP_MASK) != CFGM_COPY_FLAGS_RESERVED_VALUE_DISP_1, VERR_INVALID_PARAMETER); AssertReturn((fFlags & CFGM_COPY_FLAGS_KEY_DISP_MASK) != CFGM_COPY_FLAGS_RESERVED_KEY_DISP, VERR_INVALID_PARAMETER); /* * Copy the values. */ int rc; for (PCFGMLEAF pValue = CFGMR3GetFirstValue(pSrcTree); pValue; pValue = CFGMR3GetNextValue(pValue)) { rc = CFGMR3InsertValue(pDstTree, pValue); if (rc == VERR_CFGM_LEAF_EXISTS) { if ((fFlags & CFGM_COPY_FLAGS_VALUE_DISP_MASK) == CFGM_COPY_FLAGS_REPLACE_VALUES) { rc = CFGMR3RemoveValue(pDstTree, pValue->szName); if (RT_FAILURE(rc)) break; rc = CFGMR3InsertValue(pDstTree, pValue); } else rc = VINF_SUCCESS; } AssertRCReturn(rc, rc); } /* * Copy/merge the keys - merging results in recursion. */ for (PCFGMNODE pSrcChild = CFGMR3GetFirstChild(pSrcTree); pSrcChild; pSrcChild = CFGMR3GetNextChild(pSrcChild)) { PCFGMNODE pDstChild = CFGMR3GetChild(pDstTree, pSrcChild->szName); if ( pDstChild && (fFlags & CFGM_COPY_FLAGS_KEY_DISP_MASK) == CFGM_COPY_FLAGS_REPLACE_KEYS) { CFGMR3RemoveNode(pDstChild); pDstChild = NULL; } if (!pDstChild) { PCFGMNODE pChildCopy; rc = CFGMR3DuplicateSubTree(pSrcChild, &pChildCopy); AssertRCReturn(rc, rc); rc = CFGMR3InsertSubTree(pDstTree, pSrcChild->szName, pChildCopy, NULL); AssertRCReturnStmt(rc, CFGMR3RemoveNode(pChildCopy), rc); } else if ((fFlags & CFGM_COPY_FLAGS_KEY_DISP_MASK) == CFGM_COPY_FLAGS_MERGE_KEYS) { rc = CFGMR3CopyTree(pDstChild, pSrcChild, fFlags); AssertRCReturn(rc, rc); } } return VINF_SUCCESS; } /** * Compares two names. * * @returns Similar to memcpy. * @param pszName1 The first name. * @param cchName1 The length of the first name. * @param pszName2 The second name. * @param cchName2 The length of the second name. */ DECLINLINE(int) cfgmR3CompareNames(const char *pszName1, size_t cchName1, const char *pszName2, size_t cchName2) { int iDiff; if (cchName1 <= cchName2) { iDiff = memcmp(pszName1, pszName2, cchName1); if (!iDiff && cchName1 < cchName2) iDiff = -1; } else { iDiff = memcmp(pszName1, pszName2, cchName2); if (!iDiff) iDiff = 1; } return iDiff; } /** * Insert a node. * * @returns VBox status code. * @returns VERR_CFGM_NODE_EXISTS if the final child node name component exists. * @param pNode Parent node. * @param pszName Name or path of the new child node. * @param ppChild Where to store the address of the new child node. (optional) */ VMMR3DECL(int) CFGMR3InsertNode(PCFGMNODE pNode, const char *pszName, PCFGMNODE *ppChild) { int rc; if (pNode) { /* * If given a path we have to deal with it component by component. */ while (*pszName == '/') pszName++; if (strchr(pszName, '/')) { char *pszDup = RTStrDup(pszName); if (pszDup) { char *psz = pszDup; for (;;) { /* Terminate at '/' and find the next component. */ char *pszNext = strchr(psz, '/'); if (pszNext) { *pszNext++ = '\0'; while (*pszNext == '/') pszNext++; if (*pszNext == '\0') pszNext = NULL; } /* does it exist? */ PCFGMNODE pChild = CFGMR3GetChild(pNode, psz); if (!pChild) { /* no, insert it */ rc = CFGMR3InsertNode(pNode, psz, &pChild); if (RT_FAILURE(rc)) break; if (!pszNext) { if (ppChild) *ppChild = pChild; break; } } /* if last component fail */ else if (!pszNext) { rc = VERR_CFGM_NODE_EXISTS; break; } /* next */ pNode = pChild; psz = pszNext; } RTStrFree(pszDup); } else rc = VERR_NO_TMP_MEMORY; } /* * Not multicomponent, just make sure it's a non-zero name. */ else if (*pszName) { /* * Check if already exists and find last node in chain. */ size_t cchName = strlen(pszName); PCFGMNODE pPrev = NULL; PCFGMNODE pNext = pNode->pFirstChild; if (pNext) { for ( ; pNext; pPrev = pNext, pNext = pNext->pNext) { int iDiff = cfgmR3CompareNames(pszName, cchName, pNext->szName, pNext->cchName); if (iDiff <= 0) { if (!iDiff) return VERR_CFGM_NODE_EXISTS; break; } } } /* * Allocate and init node. */ PCFGMNODE pNew = (PCFGMNODE)cfgmR3MemAlloc(pNode->pVM, MM_TAG_CFGM, sizeof(*pNew) + cchName); if (pNew) { pNew->pParent = pNode; pNew->pFirstChild = NULL; pNew->pFirstLeaf = NULL; pNew->pVM = pNode->pVM; pNew->fRestrictedRoot = false; pNew->cchName = cchName; memcpy(pNew->szName, pszName, cchName + 1); /* * Insert into child list. */ pNew->pPrev = pPrev; if (pPrev) pPrev->pNext = pNew; else pNode->pFirstChild = pNew; pNew->pNext = pNext; if (pNext) pNext->pPrev = pNew; if (ppChild) *ppChild = pNew; rc = VINF_SUCCESS; } else rc = VERR_NO_MEMORY; } else { rc = VERR_CFGM_INVALID_NODE_PATH; AssertMsgFailed(("Invalid path %s\n", pszName)); } } else { rc = VERR_CFGM_NO_PARENT; AssertMsgFailed(("No parent! path %s\n", pszName)); } return rc; } /** * Insert a node, format string name. * * @returns VBox status code. * @param pNode Parent node. * @param ppChild Where to store the address of the new child node. (optional) * @param pszNameFormat Name of or path the new child node. * @param ... Name format arguments. */ VMMR3DECL(int) CFGMR3InsertNodeF(PCFGMNODE pNode, PCFGMNODE *ppChild, const char *pszNameFormat, ...) { va_list Args; va_start(Args, pszNameFormat); int rc = CFGMR3InsertNodeFV(pNode, ppChild, pszNameFormat, Args); va_end(Args); return rc; } /** * Insert a node, format string name. * * @returns VBox status code. * @param pNode Parent node. * @param ppChild Where to store the address of the new child node. (optional) * @param pszNameFormat Name or path of the new child node. * @param Args Name format arguments. */ VMMR3DECL(int) CFGMR3InsertNodeFV(PCFGMNODE pNode, PCFGMNODE *ppChild, const char *pszNameFormat, va_list Args) { int rc; char *pszName; RTStrAPrintfV(&pszName, pszNameFormat, Args); if (pszName) { rc = CFGMR3InsertNode(pNode, pszName, ppChild); RTStrFree(pszName); } else rc = VERR_NO_MEMORY; return rc; } /** * Marks the node as the root of a restricted subtree, i.e. the end of * a CFGMR3GetParent() journey. * * @param pNode The node to mark. */ VMMR3DECL(void) CFGMR3SetRestrictedRoot(PCFGMNODE pNode) { if (pNode) pNode->fRestrictedRoot = true; } /** * Insert a node. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Name of the new child node. * @param ppLeaf Where to store the new leaf. * The caller must fill in the enmType and Value fields! */ static int cfgmR3InsertLeaf(PCFGMNODE pNode, const char *pszName, PCFGMLEAF *ppLeaf) { int rc; if (*pszName) { if (pNode) { /* * Check if already exists and find last node in chain. */ size_t cchName = strlen(pszName); PCFGMLEAF pPrev = NULL; PCFGMLEAF pNext = pNode->pFirstLeaf; if (pNext) { for ( ; pNext; pPrev = pNext, pNext = pNext->pNext) { int iDiff = cfgmR3CompareNames(pszName, cchName, pNext->szName, pNext->cchName); if (iDiff <= 0) { if (!iDiff) return VERR_CFGM_LEAF_EXISTS; break; } } } /* * Allocate and init node. */ PCFGMLEAF pNew = (PCFGMLEAF)cfgmR3MemAlloc(pNode->pVM, MM_TAG_CFGM, sizeof(*pNew) + cchName); if (pNew) { pNew->cchName = cchName; memcpy(pNew->szName, pszName, cchName + 1); /* * Insert into child list. */ pNew->pPrev = pPrev; if (pPrev) pPrev->pNext = pNew; else pNode->pFirstLeaf = pNew; pNew->pNext = pNext; if (pNext) pNext->pPrev = pNew; *ppLeaf = pNew; rc = VINF_SUCCESS; } else rc = VERR_NO_MEMORY; } else rc = VERR_CFGM_NO_PARENT; } else rc = VERR_CFGM_INVALID_CHILD_PATH; return rc; } /** * Removes a node. * * @param pNode The node to remove. */ VMMR3DECL(void) CFGMR3RemoveNode(PCFGMNODE pNode) { if (pNode) { /* * Free children. */ while (pNode->pFirstChild) CFGMR3RemoveNode(pNode->pFirstChild); /* * Free leaves. */ while (pNode->pFirstLeaf) cfgmR3RemoveLeaf(pNode, pNode->pFirstLeaf); /* * Unlink ourselves. */ if (pNode->pPrev) pNode->pPrev->pNext = pNode->pNext; else { if (pNode->pParent) pNode->pParent->pFirstChild = pNode->pNext; else if ( pNode->pVM /* might be a different tree */ && pNode == pNode->pVM->cfgm.s.pRoot) pNode->pVM->cfgm.s.pRoot = NULL; } if (pNode->pNext) pNode->pNext->pPrev = pNode->pPrev; /* * Free ourselves. */ cfgmR3FreeNodeOnly(pNode); } } /** * Removes a leaf. * * @param pNode Parent node. * @param pLeaf Leaf to remove. */ static void cfgmR3RemoveLeaf(PCFGMNODE pNode, PCFGMLEAF pLeaf) { if (pNode && pLeaf) { /* * Unlink. */ if (pLeaf->pPrev) pLeaf->pPrev->pNext = pLeaf->pNext; else pNode->pFirstLeaf = pLeaf->pNext; if (pLeaf->pNext) pLeaf->pNext->pPrev = pLeaf->pPrev; /* * Free value and node. */ cfgmR3FreeValue(pNode->pVM, pLeaf); pLeaf->pNext = NULL; pLeaf->pPrev = NULL; cfgmR3MemFree(pNode->pVM, pLeaf); } } /** * Frees whatever resources the leaf value is owning. * * Use this before assigning a new value to a leaf. * The caller must either free the leaf or assign a new value to it. * * @param pVM The cross context VM structure, if the tree * is associated with one. * @param pLeaf Pointer to the leaf which value should be free. */ static void cfgmR3FreeValue(PVM pVM, PCFGMLEAF pLeaf) { if (pLeaf) { switch (pLeaf->enmType) { case CFGMVALUETYPE_BYTES: cfgmR3MemFree(pVM, pLeaf->Value.Bytes.pau8); pLeaf->Value.Bytes.pau8 = NULL; pLeaf->Value.Bytes.cb = 0; break; case CFGMVALUETYPE_STRING: cfgmR3StrFree(pVM, pLeaf->Value.String.psz); pLeaf->Value.String.psz = NULL; pLeaf->Value.String.cb = 0; break; case CFGMVALUETYPE_PASSWORD: RTMemSaferFree(pLeaf->Value.String.psz, pLeaf->Value.String.cb); pLeaf->Value.String.psz = NULL; pLeaf->Value.String.cb = 0; break; case CFGMVALUETYPE_INTEGER: break; } pLeaf->enmType = (CFGMVALUETYPE)0; } } /** * Destroys a tree created with CFGMR3CreateTree or CFGMR3DuplicateSubTree. * * @returns VBox status code. * @param pRoot The root node of the tree. */ VMMR3DECL(int) CFGMR3DestroyTree(PCFGMNODE pRoot) { if (!pRoot) return VINF_SUCCESS; AssertReturn(!pRoot->pParent, VERR_INVALID_PARAMETER); AssertReturn(!pRoot->pVM || pRoot != pRoot->pVM->cfgm.s.pRoot, VERR_ACCESS_DENIED); CFGMR3RemoveNode(pRoot); return VINF_SUCCESS; } /** * Inserts a new integer value. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Value name. * @param u64Integer The value. */ VMMR3DECL(int) CFGMR3InsertInteger(PCFGMNODE pNode, const char *pszName, uint64_t u64Integer) { PCFGMLEAF pLeaf; int rc = cfgmR3InsertLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { pLeaf->enmType = CFGMVALUETYPE_INTEGER; pLeaf->Value.Integer.u64 = u64Integer; } return rc; } /** * Inserts a new string value. * * This variant expects that the caller know the length of the string already so * we can avoid calling strlen() here. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Value name. * @param pszString The value. Must not be NULL. * @param cchString The length of the string excluding the * terminator. */ VMMR3DECL(int) CFGMR3InsertStringN(PCFGMNODE pNode, const char *pszName, const char *pszString, size_t cchString) { Assert(RTStrNLen(pszString, cchString) == cchString); int rc; if (pNode) { /* * Allocate string object first. */ char *pszStringCopy = (char *)cfgmR3StrAlloc(pNode->pVM, MM_TAG_CFGM_STRING, cchString + 1); if (pszStringCopy) { memcpy(pszStringCopy, pszString, cchString); pszStringCopy[cchString] = '\0'; /* * Create value leaf and set it to string type. */ PCFGMLEAF pLeaf; rc = cfgmR3InsertLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { pLeaf->enmType = CFGMVALUETYPE_STRING; pLeaf->Value.String.psz = pszStringCopy; pLeaf->Value.String.cb = cchString + 1; } else cfgmR3StrFree(pNode->pVM, pszStringCopy); } else rc = VERR_NO_MEMORY; } else rc = VERR_CFGM_NO_PARENT; return rc; } /** * Inserts a new string value. * * Calls strlen(pszString) internally; if you know the length of the string, * CFGMR3InsertStringLengthKnown() is faster. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Value name. * @param pszString The value. */ VMMR3DECL(int) CFGMR3InsertString(PCFGMNODE pNode, const char *pszName, const char *pszString) { return CFGMR3InsertStringN(pNode, pszName, pszString, strlen(pszString)); } /** * Same as CFGMR3InsertString except the string value given in RTStrPrintfV * fashion. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Value name. * @param pszFormat The value given as a format string. * @param va Argument to pszFormat. */ VMMR3DECL(int) CFGMR3InsertStringFV(PCFGMNODE pNode, const char *pszName, const char *pszFormat, va_list va) { int rc; if (pNode) { /* * Allocate string object first. */ char *pszString; if (!pNode->pVM) pszString = RTStrAPrintf2(pszFormat, va); else pszString = MMR3HeapAPrintfVU(pNode->pVM->pUVM, MM_TAG_CFGM_STRING, pszFormat, va); if (pszString) { /* * Create value leaf and set it to string type. */ PCFGMLEAF pLeaf; rc = cfgmR3InsertLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { pLeaf->enmType = CFGMVALUETYPE_STRING; pLeaf->Value.String.psz = pszString; pLeaf->Value.String.cb = strlen(pszString) + 1; } else cfgmR3StrFree(pNode->pVM, pszString); } else rc = VERR_NO_MEMORY; } else rc = VERR_CFGM_NO_PARENT; return rc; } /** * Same as CFGMR3InsertString except the string value given in RTStrPrintf * fashion. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Value name. * @param pszFormat The value given as a format string. * @param ... Argument to pszFormat. */ VMMR3DECL(int) CFGMR3InsertStringF(PCFGMNODE pNode, const char *pszName, const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); int rc = CFGMR3InsertStringFV(pNode, pszName, pszFormat, va); va_end(va); return rc; } /** * Same as CFGMR3InsertString except the string value given as a UTF-16 string. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Value name. * @param pwszValue The string value (UTF-16). */ VMMR3DECL(int) CFGMR3InsertStringW(PCFGMNODE pNode, const char *pszName, PCRTUTF16 pwszValue) { char *pszValue; int rc = RTUtf16ToUtf8(pwszValue, &pszValue); if (RT_SUCCESS(rc)) { rc = CFGMR3InsertString(pNode, pszName, pszValue); RTStrFree(pszValue); } return rc; } /** * Inserts a new bytes value. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Value name. * @param pvBytes The value. * @param cbBytes The value size. */ VMMR3DECL(int) CFGMR3InsertBytes(PCFGMNODE pNode, const char *pszName, const void *pvBytes, size_t cbBytes) { int rc; if (pNode) { if (cbBytes == (RTUINT)cbBytes) { /* * Allocate string object first. */ void *pvCopy = cfgmR3MemAlloc(pNode->pVM, MM_TAG_CFGM_STRING, cbBytes); if (pvCopy || !cbBytes) { memcpy(pvCopy, pvBytes, cbBytes); /* * Create value leaf and set it to string type. */ PCFGMLEAF pLeaf; rc = cfgmR3InsertLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { pLeaf->enmType = CFGMVALUETYPE_BYTES; pLeaf->Value.Bytes.cb = cbBytes; pLeaf->Value.Bytes.pau8 = (uint8_t *)pvCopy; } else cfgmR3MemFree(pNode->pVM, pvCopy); } else rc = VERR_NO_MEMORY; } else rc = VERR_OUT_OF_RANGE; } else rc = VERR_CFGM_NO_PARENT; return rc; } /** * Inserts a new password value. * * This variant expects that the caller know the length of the password string * already so we can avoid calling strlen() here. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Value name. * @param pszString The value. Must not be NULL. * @param cchString The length of the string excluding the terminator. */ VMMR3DECL(int) CFGMR3InsertPasswordN(PCFGMNODE pNode, const char *pszName, const char *pszString, size_t cchString) { Assert(RTStrNLen(pszString, cchString) == cchString); int rc; if (pNode) { /* * Allocate string object first using the safer memory API since this * is considered sensitive information. */ char *pszStringCopy = (char *)RTMemSaferAllocZ(cchString + 1); if (pszStringCopy) { memcpy(pszStringCopy, pszString, cchString); pszStringCopy[cchString] = '\0'; RTMemSaferScramble(pszStringCopy, cchString + 1); /* * Create value leaf and set it to string type. */ PCFGMLEAF pLeaf; rc = cfgmR3InsertLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { pLeaf->enmType = CFGMVALUETYPE_PASSWORD; pLeaf->Value.String.psz = pszStringCopy; pLeaf->Value.String.cb = cchString + 1; } else RTMemSaferFree(pszStringCopy, cchString + 1); } else rc = VERR_NO_MEMORY; } else rc = VERR_CFGM_NO_PARENT; return rc; } /** * Inserts a new password value. * * Calls strlen(pszString) internally; if you know the length of the string, * CFGMR3InsertStringLengthKnown() is faster. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Value name. * @param pszString The value. */ VMMR3DECL(int) CFGMR3InsertPassword(PCFGMNODE pNode, const char *pszName, const char *pszString) { return CFGMR3InsertPasswordN(pNode, pszName, pszString, strlen(pszString)); } /** * Make a copy of the specified value under the given node. * * @returns VBox status code. * @param pNode Parent node. * @param pValue The value to copy and insert. */ VMMR3DECL(int) CFGMR3InsertValue(PCFGMNODE pNode, PCFGMLEAF pValue) { int rc; switch (pValue->enmType) { case CFGMVALUETYPE_INTEGER: rc = CFGMR3InsertInteger(pNode, pValue->szName, pValue->Value.Integer.u64); break; case CFGMVALUETYPE_BYTES: rc = CFGMR3InsertBytes(pNode, pValue->szName, pValue->Value.Bytes.pau8, pValue->Value.Bytes.cb); break; case CFGMVALUETYPE_STRING: rc = CFGMR3InsertStringN(pNode, pValue->szName, pValue->Value.String.psz, pValue->Value.String.cb - 1); break; case CFGMVALUETYPE_PASSWORD: rc = CFGMR3InsertPasswordN(pNode, pValue->szName, pValue->Value.String.psz, pValue->Value.String.cb - 1); break; default: rc = VERR_CFGM_IPE_1; AssertMsgFailed(("Invalid value type %d\n", pValue->enmType)); break; } return rc; } /** * Remove a value. * * @returns VBox status code. * @param pNode Parent node. * @param pszName Name of the new child node. */ VMMR3DECL(int) CFGMR3RemoveValue(PCFGMNODE pNode, const char *pszName) { PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) cfgmR3RemoveLeaf(pNode, pLeaf); return rc; } /* * -+- helper apis -+- */ /** * Query unsigned 64-bit integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu64 Where to store the integer value. */ VMMR3DECL(int) CFGMR3QueryU64(PCFGMNODE pNode, const char *pszName, uint64_t *pu64) { return CFGMR3QueryInteger(pNode, pszName, pu64); } /** * Query unsigned 64-bit integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu64 Where to store the integer value. Set to default on failure. * @param u64Def The default value. */ VMMR3DECL(int) CFGMR3QueryU64Def(PCFGMNODE pNode, const char *pszName, uint64_t *pu64, uint64_t u64Def) { return CFGMR3QueryIntegerDef(pNode, pszName, pu64, u64Def); } /** * Query signed 64-bit integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi64 Where to store the value. */ VMMR3DECL(int) CFGMR3QueryS64(PCFGMNODE pNode, const char *pszName, int64_t *pi64) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) *pi64 = (int64_t)u64; return rc; } /** * Query signed 64-bit integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi64 Where to store the value. Set to default on failure. * @param i64Def The default value. */ VMMR3DECL(int) CFGMR3QueryS64Def(PCFGMNODE pNode, const char *pszName, int64_t *pi64, int64_t i64Def) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, i64Def); *pi64 = (int64_t)u64; return rc; } /** * Query unsigned 32-bit integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu32 Where to store the value. */ VMMR3DECL(int) CFGMR3QueryU32(PCFGMNODE pNode, const char *pszName, uint32_t *pu32) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) { if (!(u64 & UINT64_C(0xffffffff00000000))) *pu32 = (uint32_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } return rc; } /** * Query unsigned 32-bit integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu32 Where to store the value. Set to default on failure. * @param u32Def The default value. */ VMMR3DECL(int) CFGMR3QueryU32Def(PCFGMNODE pNode, const char *pszName, uint32_t *pu32, uint32_t u32Def) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, u32Def); if (RT_SUCCESS(rc)) { if (!(u64 & UINT64_C(0xffffffff00000000))) *pu32 = (uint32_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } if (RT_FAILURE(rc)) *pu32 = u32Def; return rc; } /** * Query signed 32-bit integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi32 Where to store the value. */ VMMR3DECL(int) CFGMR3QueryS32(PCFGMNODE pNode, const char *pszName, int32_t *pi32) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) { if ( !(u64 & UINT64_C(0xffffffff80000000)) || (u64 & UINT64_C(0xffffffff80000000)) == UINT64_C(0xffffffff80000000)) *pi32 = (int32_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } return rc; } /** * Query signed 32-bit integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi32 Where to store the value. Set to default on failure. * @param i32Def The default value. */ VMMR3DECL(int) CFGMR3QueryS32Def(PCFGMNODE pNode, const char *pszName, int32_t *pi32, int32_t i32Def) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, i32Def); if (RT_SUCCESS(rc)) { if ( !(u64 & UINT64_C(0xffffffff80000000)) || (u64 & UINT64_C(0xffffffff80000000)) == UINT64_C(0xffffffff80000000)) *pi32 = (int32_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } if (RT_FAILURE(rc)) *pi32 = i32Def; return rc; } /** * Query unsigned 16-bit integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu16 Where to store the value. */ VMMR3DECL(int) CFGMR3QueryU16(PCFGMNODE pNode, const char *pszName, uint16_t *pu16) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) { if (!(u64 & UINT64_C(0xffffffffffff0000))) *pu16 = (int16_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } return rc; } /** * Query unsigned 16-bit integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu16 Where to store the value. Set to default on failure. * @param u16Def The default value. */ VMMR3DECL(int) CFGMR3QueryU16Def(PCFGMNODE pNode, const char *pszName, uint16_t *pu16, uint16_t u16Def) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, u16Def); if (RT_SUCCESS(rc)) { if (!(u64 & UINT64_C(0xffffffffffff0000))) *pu16 = (int16_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } if (RT_FAILURE(rc)) *pu16 = u16Def; return rc; } /** * Query signed 16-bit integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi16 Where to store the value. */ VMMR3DECL(int) CFGMR3QueryS16(PCFGMNODE pNode, const char *pszName, int16_t *pi16) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) { if ( !(u64 & UINT64_C(0xffffffffffff8000)) || (u64 & UINT64_C(0xffffffffffff8000)) == UINT64_C(0xffffffffffff8000)) *pi16 = (int16_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } return rc; } /** * Query signed 16-bit integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi16 Where to store the value. Set to default on failure. * @param i16Def The default value. */ VMMR3DECL(int) CFGMR3QueryS16Def(PCFGMNODE pNode, const char *pszName, int16_t *pi16, int16_t i16Def) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, i16Def); if (RT_SUCCESS(rc)) { if ( !(u64 & UINT64_C(0xffffffffffff8000)) || (u64 & UINT64_C(0xffffffffffff8000)) == UINT64_C(0xffffffffffff8000)) *pi16 = (int16_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } if (RT_FAILURE(rc)) *pi16 = i16Def; return rc; } /** * Query unsigned 8-bit integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu8 Where to store the value. */ VMMR3DECL(int) CFGMR3QueryU8(PCFGMNODE pNode, const char *pszName, uint8_t *pu8) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) { if (!(u64 & UINT64_C(0xffffffffffffff00))) *pu8 = (uint8_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } return rc; } /** * Query unsigned 8-bit integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu8 Where to store the value. Set to default on failure. * @param u8Def The default value. */ VMMR3DECL(int) CFGMR3QueryU8Def(PCFGMNODE pNode, const char *pszName, uint8_t *pu8, uint8_t u8Def) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, u8Def); if (RT_SUCCESS(rc)) { if (!(u64 & UINT64_C(0xffffffffffffff00))) *pu8 = (uint8_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } if (RT_FAILURE(rc)) *pu8 = u8Def; return rc; } /** * Query signed 8-bit integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi8 Where to store the value. */ VMMR3DECL(int) CFGMR3QueryS8(PCFGMNODE pNode, const char *pszName, int8_t *pi8) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) { if ( !(u64 & UINT64_C(0xffffffffffffff80)) || (u64 & UINT64_C(0xffffffffffffff80)) == UINT64_C(0xffffffffffffff80)) *pi8 = (int8_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } return rc; } /** * Query signed 8-bit integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi8 Where to store the value. Set to default on failure. * @param i8Def The default value. */ VMMR3DECL(int) CFGMR3QueryS8Def(PCFGMNODE pNode, const char *pszName, int8_t *pi8, int8_t i8Def) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, i8Def); if (RT_SUCCESS(rc)) { if ( !(u64 & UINT64_C(0xffffffffffffff80)) || (u64 & UINT64_C(0xffffffffffffff80)) == UINT64_C(0xffffffffffffff80)) *pi8 = (int8_t)u64; else rc = VERR_CFGM_INTEGER_TOO_BIG; } if (RT_FAILURE(rc)) *pi8 = i8Def; return rc; } /** * Query boolean integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pf Where to store the value. * @remark This function will interpret any non-zero value as true. */ VMMR3DECL(int) CFGMR3QueryBool(PCFGMNODE pNode, const char *pszName, bool *pf) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) *pf = u64 ? true : false; return rc; } /** * Query boolean integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pf Where to store the value. Set to default on failure. * @param fDef The default value. * @remark This function will interpret any non-zero value as true. */ VMMR3DECL(int) CFGMR3QueryBoolDef(PCFGMNODE pNode, const char *pszName, bool *pf, bool fDef) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, fDef); *pf = u64 ? true : false; return rc; } /** * Query I/O port address value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pPort Where to store the value. */ VMMR3DECL(int) CFGMR3QueryPort(PCFGMNODE pNode, const char *pszName, PRTIOPORT pPort) { AssertCompileSize(RTIOPORT, 2); return CFGMR3QueryU16(pNode, pszName, pPort); } /** * Query I/O port address value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pPort Where to store the value. Set to default on failure. * @param PortDef The default value. */ VMMR3DECL(int) CFGMR3QueryPortDef(PCFGMNODE pNode, const char *pszName, PRTIOPORT pPort, RTIOPORT PortDef) { AssertCompileSize(RTIOPORT, 2); return CFGMR3QueryU16Def(pNode, pszName, pPort, PortDef); } /** * Query unsigned int address value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu Where to store the value. */ VMMR3DECL(int) CFGMR3QueryUInt(PCFGMNODE pNode, const char *pszName, unsigned int *pu) { AssertCompileSize(unsigned int, 4); return CFGMR3QueryU32(pNode, pszName, (uint32_t *)pu); } /** * Query unsigned int address value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pu Where to store the value. Set to default on failure. * @param uDef The default value. */ VMMR3DECL(int) CFGMR3QueryUIntDef(PCFGMNODE pNode, const char *pszName, unsigned int *pu, unsigned int uDef) { AssertCompileSize(unsigned int, 4); return CFGMR3QueryU32Def(pNode, pszName, (uint32_t *)pu, uDef); } /** * Query signed int address value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi Where to store the value. */ VMMR3DECL(int) CFGMR3QuerySInt(PCFGMNODE pNode, const char *pszName, signed int *pi) { AssertCompileSize(signed int, 4); return CFGMR3QueryS32(pNode, pszName, (int32_t *)pi); } /** * Query unsigned int address value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pi Where to store the value. Set to default on failure. * @param iDef The default value. */ VMMR3DECL(int) CFGMR3QuerySIntDef(PCFGMNODE pNode, const char *pszName, signed int *pi, signed int iDef) { AssertCompileSize(signed int, 4); return CFGMR3QueryS32Def(pNode, pszName, (int32_t *)pi, iDef); } /** * Query Guest Context pointer integer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pGCPtr Where to store the value. */ VMMR3DECL(int) CFGMR3QueryGCPtr(PCFGMNODE pNode, const char *pszName, PRTGCPTR pGCPtr) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) { RTGCPTR u = (RTGCPTR)u64; if (u64 == u) *pGCPtr = u; else rc = VERR_CFGM_INTEGER_TOO_BIG; } return rc; } /** * Query Guest Context pointer integer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pGCPtr Where to store the value. Set to default on failure. * @param GCPtrDef The default value. */ VMMR3DECL(int) CFGMR3QueryGCPtrDef(PCFGMNODE pNode, const char *pszName, PRTGCPTR pGCPtr, RTGCPTR GCPtrDef) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, GCPtrDef); if (RT_SUCCESS(rc)) { RTGCPTR u = (RTGCPTR)u64; if (u64 == u) *pGCPtr = u; else rc = VERR_CFGM_INTEGER_TOO_BIG; } if (RT_FAILURE(rc)) *pGCPtr = GCPtrDef; return rc; } /** * Query Guest Context unsigned pointer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pGCPtr Where to store the value. */ VMMR3DECL(int) CFGMR3QueryGCPtrU(PCFGMNODE pNode, const char *pszName, PRTGCUINTPTR pGCPtr) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) { RTGCUINTPTR u = (RTGCUINTPTR)u64; if (u64 == u) *pGCPtr = u; else rc = VERR_CFGM_INTEGER_TOO_BIG; } return rc; } /** * Query Guest Context unsigned pointer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pGCPtr Where to store the value. Set to default on failure. * @param GCPtrDef The default value. */ VMMR3DECL(int) CFGMR3QueryGCPtrUDef(PCFGMNODE pNode, const char *pszName, PRTGCUINTPTR pGCPtr, RTGCUINTPTR GCPtrDef) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, GCPtrDef); if (RT_SUCCESS(rc)) { RTGCUINTPTR u = (RTGCUINTPTR)u64; if (u64 == u) *pGCPtr = u; else rc = VERR_CFGM_INTEGER_TOO_BIG; } if (RT_FAILURE(rc)) *pGCPtr = GCPtrDef; return rc; } /** * Query Guest Context signed pointer value. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pGCPtr Where to store the value. */ VMMR3DECL(int) CFGMR3QueryGCPtrS(PCFGMNODE pNode, const char *pszName, PRTGCINTPTR pGCPtr) { uint64_t u64; int rc = CFGMR3QueryInteger(pNode, pszName, &u64); if (RT_SUCCESS(rc)) { RTGCINTPTR u = (RTGCINTPTR)u64; if (u64 == (uint64_t)u) *pGCPtr = u; else rc = VERR_CFGM_INTEGER_TOO_BIG; } return rc; } /** * Query Guest Context signed pointer value with default. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Name of an integer value. * @param pGCPtr Where to store the value. Set to default on failure. * @param GCPtrDef The default value. */ VMMR3DECL(int) CFGMR3QueryGCPtrSDef(PCFGMNODE pNode, const char *pszName, PRTGCINTPTR pGCPtr, RTGCINTPTR GCPtrDef) { uint64_t u64; int rc = CFGMR3QueryIntegerDef(pNode, pszName, &u64, GCPtrDef); if (RT_SUCCESS(rc)) { RTGCINTPTR u = (RTGCINTPTR)u64; if (u64 == (uint64_t)u) *pGCPtr = u; else rc = VERR_CFGM_INTEGER_TOO_BIG; } if (RT_FAILURE(rc)) *pGCPtr = GCPtrDef; return rc; } /** * Query zero terminated character value storing it in a * buffer allocated from the MM heap. * * @returns VBox status code. * @param pNode Which node to search for pszName in. * @param pszName Value name. This value must be of zero terminated character string type. * @param ppszString Where to store the string pointer. * Free this using MMR3HeapFree() (or RTStrFree if not * associated with a pUVM - see CFGMR3CreateTree). */ VMMR3DECL(int) CFGMR3QueryStringAlloc(PCFGMNODE pNode, const char *pszName, char **ppszString) { size_t cbString; int rc = CFGMR3QuerySize(pNode, pszName, &cbString); if (RT_SUCCESS(rc)) { char *pszString = cfgmR3StrAlloc(pNode->pVM, MM_TAG_CFGM_USER, cbString); if (pszString) { rc = CFGMR3QueryString(pNode, pszName, pszString, cbString); if (RT_SUCCESS(rc)) *ppszString = pszString; else cfgmR3StrFree(pNode->pVM, pszString); } else rc = VERR_NO_MEMORY; } return rc; } /** * Query zero terminated character value storing it in a * buffer allocated from the MM heap. * * @returns VBox status code. * @param pNode Which node to search for pszName in. This cannot be * NULL if @a pszDef is not NULL, because we need * somewhere way to get to the VM in order to call * MMR3HeapStrDup. * @param pszName Value name. This value must be of zero terminated character string type. * @param ppszString Where to store the string pointer. Not set on failure. * Free this using MMR3HeapFree() (or RTStrFree if not * associated with a pUVM - see CFGMR3CreateTree). * @param pszDef The default return value. This can be NULL. */ VMMR3DECL(int) CFGMR3QueryStringAllocDef(PCFGMNODE pNode, const char *pszName, char **ppszString, const char *pszDef) { Assert(pNode || !pszDef); /* We need pVM if we need to duplicate the string later. */ /* * (Don't call CFGMR3QuerySize and CFGMR3QueryStringDef here as the latter * cannot handle pszDef being NULL.) */ PCFGMLEAF pLeaf; int rc = cfgmR3ResolveLeaf(pNode, pszName, &pLeaf); if (RT_SUCCESS(rc)) { if (pLeaf->enmType == CFGMVALUETYPE_STRING) { size_t const cbSrc = pLeaf->Value.String.cb; char *pszString = cfgmR3StrAlloc(pNode->pVM, MM_TAG_CFGM_USER, cbSrc); if (pszString) { memcpy(pszString, pLeaf->Value.String.psz, cbSrc); *ppszString = pszString; } else rc = VERR_NO_MEMORY; } else rc = VERR_CFGM_NOT_STRING; } if (RT_FAILURE(rc)) { if (!pszDef) *ppszString = NULL; else { size_t const cbDef = strlen(pszDef) + 1; *ppszString = cfgmR3StrAlloc(pNode->pVM, MM_TAG_CFGM_USER, cbDef); memcpy(*ppszString, pszDef, cbDef); } if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT) rc = VINF_SUCCESS; } return rc; } /** * Dumps the configuration (sub)tree to the release log. * * @param pRoot The root node of the dump. */ VMMR3DECL(void) CFGMR3Dump(PCFGMNODE pRoot) { bool fOldBuffered = RTLogRelSetBuffering(true /*fBuffered*/); LogRel(("************************* CFGM dump *************************\n")); cfgmR3Dump(pRoot, 0, DBGFR3InfoLogRelHlp()); LogRel(("********************* End of CFGM dump **********************\n")); RTLogRelSetBuffering(fOldBuffered); } /** * Info handler, internal version. * * @param pVM The cross context VM structure. * @param pHlp Callback functions for doing output. * @param pszArgs Argument string. Optional and specific to the handler. */ static DECLCALLBACK(void) cfgmR3Info(PVM pVM, PCDBGFINFOHLP pHlp, const char *pszArgs) { /* * Figure where to start. */ PCFGMNODE pRoot = pVM->cfgm.s.pRoot; if (pszArgs && *pszArgs) { int rc = cfgmR3ResolveNode(pRoot, pszArgs, &pRoot); if (RT_FAILURE(rc)) { pHlp->pfnPrintf(pHlp, "Failed to resolve CFGM path '%s', %Rrc", pszArgs, rc); return; } } /* * Dump the specified tree. */ pHlp->pfnPrintf(pHlp, "pRoot=%p:{", pRoot); cfgmR3DumpPath(pRoot, pHlp); pHlp->pfnPrintf(pHlp, "}\n"); cfgmR3Dump(pRoot, 0, pHlp); } /** * Recursively prints a path name. */ static void cfgmR3DumpPath(PCFGMNODE pNode, PCDBGFINFOHLP pHlp) { if (pNode->pParent) cfgmR3DumpPath(pNode->pParent, pHlp); pHlp->pfnPrintf(pHlp, "%s/", pNode->szName); } /** * Dumps a branch of a tree. */ static void cfgmR3Dump(PCFGMNODE pRoot, unsigned iLevel, PCDBGFINFOHLP pHlp) { /* * Path. */ pHlp->pfnPrintf(pHlp, "["); cfgmR3DumpPath(pRoot, pHlp); pHlp->pfnPrintf(pHlp, "] (level %d)%s\n", iLevel, pRoot->fRestrictedRoot ? " (restricted root)" : ""); /* * Values. */ PCFGMLEAF pLeaf; size_t cchMax = 0; for (pLeaf = CFGMR3GetFirstValue(pRoot); pLeaf; pLeaf = CFGMR3GetNextValue(pLeaf)) cchMax = RT_MAX(cchMax, pLeaf->cchName); for (pLeaf = CFGMR3GetFirstValue(pRoot); pLeaf; pLeaf = CFGMR3GetNextValue(pLeaf)) { switch (CFGMR3GetValueType(pLeaf)) { case CFGMVALUETYPE_INTEGER: { pHlp->pfnPrintf(pHlp, " %-*s = %#018llx (%'lld", (int)cchMax, pLeaf->szName, pLeaf->Value.Integer.u64, pLeaf->Value.Integer.u64); if ( ( pLeaf->cchName >= 4 && !RTStrCmp(&pLeaf->szName[pLeaf->cchName - 4], "Size")) || ( pLeaf->cchName >= 2 && !RTStrNCmp(pLeaf->szName, "cb", 2)) ) pHlp->pfnPrintf(pHlp, ", %' Rhcb)\n", pLeaf->Value.Integer.u64); else pHlp->pfnPrintf(pHlp, ")\n"); break; } case CFGMVALUETYPE_STRING: pHlp->pfnPrintf(pHlp, " %-*s = \"%s\" (cb=%zu)\n", (int)cchMax, pLeaf->szName, pLeaf->Value.String.psz, pLeaf->Value.String.cb); break; case CFGMVALUETYPE_BYTES: pHlp->pfnPrintf(pHlp, " %-*s = \"%.*Rhxs\" (cb=%zu)\n", (int)cchMax, pLeaf->szName, pLeaf->Value.Bytes.cb, pLeaf->Value.Bytes.pau8, pLeaf->Value.Bytes.cb); break; case CFGMVALUETYPE_PASSWORD: pHlp->pfnPrintf(pHlp, " %-*s = \"***REDACTED***\" (cb=%zu)\n", (int)cchMax, pLeaf->szName, pLeaf->Value.String.cb); break; default: AssertMsgFailed(("bad leaf!\n")); break; } } pHlp->pfnPrintf(pHlp, "\n"); /* * Children. */ for (PCFGMNODE pChild = CFGMR3GetFirstChild(pRoot); pChild; pChild = CFGMR3GetNextChild(pChild)) { Assert(pChild->pNext != pChild); Assert(pChild->pPrev != pChild); Assert(pChild->pPrev != pChild->pNext || !pChild->pPrev); Assert(pChild->pFirstChild != pChild); Assert(pChild->pParent == pRoot); cfgmR3Dump(pChild, iLevel + 1, pHlp); } }