diff options
Diffstat (limited to 'src/VBox/Runtime/common/misc/inifile.cpp')
-rw-r--r-- | src/VBox/Runtime/common/misc/inifile.cpp | 791 |
1 files changed, 791 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/misc/inifile.cpp b/src/VBox/Runtime/common/misc/inifile.cpp new file mode 100644 index 00000000..90e82586 --- /dev/null +++ b/src/VBox/Runtime/common/misc/inifile.cpp @@ -0,0 +1,791 @@ +/* $Id: inifile.cpp $ */ +/** @file + * IPRT - INI-file parser. + */ + +/* + * Copyright (C) 2017-2020 Oracle Corporation + * + * 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/inifile.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/latin1.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <iprt/vfs.h> + +#include "internal/magics.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def RTINIFILE_MAX_SIZE + * The maximum INI-file size we accept loading. */ +#if ARCH_BITS > 32 +# define RTINIFILE_MAX_SIZE (_64M - 2U) +#elif ARCH_BITS > 16 +# define RTINIFILE_MAX_SIZE (_16M - 2U) +#else +# define RTINIFILE_MAX_SIZE (_64K - 2U) +#endif + +/** @def RTINIFILE_MAX_SECTIONS + * The maximum number of sections we accept in an INI-file. */ +#if ARCH_BITS > 32 +# define RTINIFILE_MAX_SECTIONS (_1M) +#elif ARCH_BITS > 16 +# define RTINIFILE_MAX_SECTIONS (_256K) +#else +# define RTINIFILE_MAX_SECTIONS (_1K) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * File encoding types. + */ +typedef enum RTINIFILEENCODING +{ + /** The customary invalid zero value. */ + RTINIFILEENCODING_INVALID = 0, + /** We treat this as latin-1. */ + RTINIFILEENCODING_ANSI, + /** UTF-8. */ + RTINIFILEENCODING_UTF8, + /** Little endian UTF-16. */ + RTINIFILEENCODING_UTF16LE, + /** Big endian UTF-16. */ + RTINIFILEENCODING_UTF16BE, + /** End of valid encoding types. */ + RTINIFILEENCODING_END +} RTINIFILEENCODING; + + +/** + * Preparsed section info. + */ +typedef struct RTINIFILESECTION +{ + /** The section name offset (byte). */ + uint32_t offName; + /** The section length in bytes starting with the name. */ + uint32_t cchSection; + /** The UTF-8 length of the section name. */ + uint32_t cchName; + /** Offset into the section where to start looking for values. */ + uint32_t cchSkipToValues : 24; + /** @todo use 4 bits for flags and stuff. like escaped name. */ +} RTINIFILESECTION; +/** Pointer to preparsed section info. */ +typedef RTINIFILESECTION *PRTINIFILESECTION; + + +/** + * INI-file instance data. + */ +typedef struct RTINIFILEINT +{ + /** Magic value (RTINIFILEINT_MAGIC). */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** The file we're working on. */ + RTVFSFILE hVfsFile; + /** Flags, RTINIFILE_F_XXX. */ + uint32_t fFlags; + + /** The original file encoding. */ + RTINIFILEENCODING enmEncoding; + /** Pointer to the file content (converted to UTF-8). */ + char *pszFile; + /** The file size. */ + uint32_t cbFile; + /** Number of sections. */ + uint32_t cSections; + /** Sections in the loaded file. */ + PRTINIFILESECTION paSections; + +} RTINIFILEINT; +/** Pointer to an INI-file instance. */ +typedef RTINIFILEINT *PRTINIFILEINT; + + +static int rtIniFileLoad(PRTINIFILEINT pThis) +{ + /* + * Load the entire file into memory, ensuring two terminating zeros. + */ + uint64_t cbFile; + int rc = RTVfsFileQuerySize(pThis->hVfsFile, &cbFile); + AssertRCReturn(rc, rc); + + if (cbFile > RTINIFILE_MAX_SIZE) + return VERR_TOO_MUCH_DATA; + if (cbFile == 0) + return VINF_SUCCESS; /* Nothing to do. */ + + pThis->cbFile = (uint32_t)cbFile; + pThis->pszFile = (char *)RTMemAllocZ(pThis->cbFile + 2); + if (!pThis->pszFile) + return VERR_NO_MEMORY; + + rc = RTVfsFileReadAt(pThis->hVfsFile, 0, pThis->pszFile, pThis->cbFile, NULL); + AssertRCReturn(rc, rc); + + /* + * Detect encoding and convert to BOM prefixed UTF-8. + */ + if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xef) + && (uint8_t)pThis->pszFile[1] == UINT8_C(0xbb) + && (uint8_t)pThis->pszFile[2] == UINT8_C(0xbf)) + { + pThis->enmEncoding = RTINIFILEENCODING_UTF8; + rc = RTStrValidateEncoding(&pThis->pszFile[3]); + if (RT_FAILURE(rc)) + return rc; + } + else + { + size_t cchUtf8; + if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xfe) + && (uint8_t)pThis->pszFile[1] == UINT8_C(0xff)) + { + pThis->enmEncoding = RTINIFILEENCODING_UTF16BE; + rc = RTUtf16BigCalcUtf8LenEx((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &cchUtf8); + } + else if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xff) + && (uint8_t)pThis->pszFile[1] == UINT8_C(0xfe)) + { + pThis->enmEncoding = RTINIFILEENCODING_UTF16LE; + rc = RTUtf16LittleCalcUtf8LenEx((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &cchUtf8); + } + else + { + pThis->enmEncoding = RTINIFILEENCODING_ANSI; + rc = RTLatin1CalcUtf8LenEx(pThis->pszFile, RTSTR_MAX, &cchUtf8); + } + if (RT_FAILURE(rc)) + return rc; + + char *pszUtf8Bom = (char *)RTMemAllocZ(3 + cchUtf8 + 1); + if (!pszUtf8Bom) + return VERR_NO_MEMORY; + pszUtf8Bom[0] = '\xEF'; + pszUtf8Bom[1] = '\xBB'; + pszUtf8Bom[2] = '\xBF'; + + char *pszUtf8 = pszUtf8Bom + 3; + if (pThis->enmEncoding == RTINIFILEENCODING_UTF16BE) + rc = RTUtf16BigToUtf8Ex((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL); + else if (pThis->enmEncoding == RTINIFILEENCODING_UTF16LE) + rc = RTUtf16LittleToUtf8Ex((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL); + else + rc = RTLatin1ToUtf8Ex(pThis->pszFile, RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL); + AssertRCReturnStmt(rc, RTMemFree(pszUtf8Bom), rc); + + RTMemFree(pThis->pszFile); + pThis->pszFile = pszUtf8Bom; + pThis->cbFile = 3 + (uint32_t)cchUtf8; + } + + /* + * Do a rough section count. + * Section zero is for unsectioned values at the start of the file. + */ + uint32_t cSections = 1; + const char *psz = pThis->pszFile + 3; + char ch; + while ((ch = *psz) != '\0') + { + while (RT_C_IS_SPACE(ch)) + ch = *++psz; + if (ch == '[') + cSections++; + + /* next line. */ + psz = strchr(psz, '\n'); + if (psz) + psz++; + else + break; + } + if (cSections > RTINIFILE_MAX_SECTIONS) + return VERR_TOO_MUCH_DATA; + + /* + * Allocation section array and do the preparsing. + */ + pThis->paSections = (PRTINIFILESECTION)RTMemAllocZ(sizeof(pThis->paSections[0]) * cSections); + if (!pThis->paSections) + return VERR_NO_MEMORY; + + uint32_t iSection = 0; + pThis->paSections[0].offName = 3; + pThis->paSections[0].cchName = 0; + pThis->paSections[0].cchSkipToValues = 0; + psz = pThis->pszFile + 3; + while ((ch = *psz) != '\0') + { + const char *const pszLine = psz; + + while (RT_C_IS_SPACE(ch)) + ch = *++psz; + if (ch == '[') + { + /* Complete previous section. */ + pThis->paSections[iSection].cchSection = (uint32_t)(pszLine - &pThis->pszFile[pThis->paSections[iSection].offName]); + + /* New section. */ + iSection++; + AssertReturn(iSection < cSections, VERR_INTERNAL_ERROR_3); + const char * const pszName = ++psz; + pThis->paSections[iSection].offName = (uint32_t)(psz - pThis->pszFile); + + /* Figure the name length. We're very very relaxed about terminating bracket. */ + while ((ch = *psz) != '\0' && ch != ']' && ch != '\r' && ch != '\n') + psz++; + pThis->paSections[iSection].cchName = (uint32_t)(psz - pszName); + + /* Set skip count to the start of the next line. */ + while (ch != '\0' && ch != '\n') + ch = *++psz; + pThis->paSections[iSection].cchSkipToValues = (uint32_t)(psz - pszName + 1); + + if (ch == '\n') + psz++; + else + break; + } + else + { + psz = strchr(psz, '\n'); + if (psz) + psz++; + else + break; + } + } + + /* Complete the final section. */ + pThis->paSections[iSection].cchSection = pThis->cbFile - pThis->paSections[iSection].offName; + pThis->cSections = iSection + 1; + + return VINF_SUCCESS; +} + + +/** + * Creates a INI-file instance from a VFS file handle. + * + * @returns IPRT status code + * @param phIniFile Where to return the INI-file handle. + * @param hVfsFile The VFS file handle (not consumed, additional + * reference is retained). + * @param fFlags Flags, RTINIFILE_F_XXX. + */ +RTDECL(int) RTIniFileCreateFromVfsFile(PRTINIFILE phIniFile, RTVFSFILE hVfsFile, uint32_t fFlags) +{ + /* + * Validate input, retaining a reference to the file. + */ + AssertPtrReturn(phIniFile, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTINIFILE_F_VALID_MASK), VERR_INVALID_FLAGS); + + uint32_t cRefs = RTVfsFileRetain(hVfsFile); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + + /* + * Create an instance. + */ + PRTINIFILEINT pThis = (PRTINIFILEINT)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTINIFILE_MAGIC; + pThis->cRefs = 1; + pThis->hVfsFile = hVfsFile; + pThis->fFlags = fFlags; + + int rc = rtIniFileLoad(pThis); + if (RT_SUCCESS(rc)) + { + + *phIniFile = pThis; + return VINF_SUCCESS; + } + RTIniFileRelease(pThis); + return rc; + } + RTVfsFileRelease(hVfsFile); + return VERR_NO_MEMORY; +} + + +/** + * Retains a reference to an INI-file instance. + * + * @returns New reference count, UINT32_MAX on failure. + * @param hIniFile The INI-file handle. + */ +RTDECL(uint32_t) RTIniFileRetain(RTINIFILE hIniFile) +{ + PRTINIFILEINT pThis = hIniFile; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs > 1); + Assert(cRefs < _64K); + return cRefs; +} + + +/** + * Releases a reference to an INI-file instance, destroying it if the count + * reaches zero. + * + * @returns New reference count, UINT32_MAX on failure. + * @param hIniFile The INI-file handle. NIL is ignored. + */ +RTDECL(uint32_t) RTIniFileRelease(RTINIFILE hIniFile) +{ + if (hIniFile == NIL_RTINIFILE) + return 0; + PRTINIFILEINT pThis = hIniFile; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < _64K); + if (cRefs == 0) + { + AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTINIFILE_MAGIC_DEAD, RTINIFILE_MAGIC), UINT32_MAX); + RTMemFree(pThis->paSections); + pThis->paSections = NULL; + RTMemFree(pThis->pszFile); + pThis->pszFile = NULL; + RTVfsFileRelease(pThis->hVfsFile); + pThis->hVfsFile = NIL_RTVFSFILE; + RTMemFree(pThis); + } + return cRefs; +} + + +/** + * Worker for RTIniFileQueryValue. + */ +static int rtIniFileQueryValueInSection(PRTINIFILEINT pThis, PRTINIFILESECTION pSection, const char *pszKey, size_t cchKey, + char *pszValue, size_t cbValue, size_t *pcbActual) +{ + /* + * Scan the section, looking for the matching key. + */ + Assert(pSection->cchSkipToValues <= pSection->cchSection); + const char * const pszEnd = &pThis->pszFile[pSection->offName + pSection->cchSection]; + const char * pszNext = pszEnd; + for (const char *psz = &pThis->pszFile[pSection->offName + pSection->cchSkipToValues]; + (uintptr_t)psz < (uintptr_t)pszEnd; + psz = pszNext) + { + /* Find start of next line so we can use 'continue' to skip a line. */ + pszNext = strchr(psz, '\n'); + if (pszNext) + pszNext++; + else + pszNext = pszEnd; + + /* Skip leading spaces. */ + char ch; + while ((ch = *psz) != '\0' && RT_C_IS_SPACE(ch)) + psz++; + if ( ch != ';' /* comment line */ + && ch != '\n' /* empty line */ + && ch != '\r' /* empty line */ + && (uintptr_t)psz < (uintptr_t)pszEnd) + { + /* Find end of key name, if any. */ + const char *pszCurKey = psz; + size_t cchCurKey; + const char *pszEqual; + if (ch != '=') + { + /** @todo deal with escaped equal signs? */ + pszEqual = strchr(psz, '='); + if (pszEqual) + { + if ((uintptr_t)pszEqual < (uintptr_t)pszNext) + cchCurKey = pszEqual - pszCurKey; + else + continue; + } + else + break; + + /* Strip trailing spaces from the current key name. */ + while (cchCurKey > 0 && RT_C_IS_SPACE(pszCurKey[cchCurKey - 1])) + cchCurKey--; + } + else + { + cchCurKey = 0; + pszEqual = psz; + } + + /* Match the keys. */ + /** @todo escape sequences? */ + if ( cchCurKey == cchKey + && RTStrNICmp(pszCurKey, pszKey, cchKey) == 0) + { + /* + * Copy out the return value, without quotes. + */ + + /* Skip leading blanks. */ + psz = pszEqual + 1; + while ((ch = *psz) && RT_C_IS_SPACE(ch) && ch != '\n') + psz++; + + /* Strip trailing spaces. */ + size_t cchCurValue = pszNext - psz; + while (cchCurValue > 1 && RT_C_IS_SPACE(psz[cchCurValue - 1])) + cchCurValue--; + + /* Strip quotes. */ + if ( cchCurValue > 2 + && ( (ch = *psz) == '"' + || ch == '\'' ) + && psz[cchCurValue - 1] == ch) + { + cchCurValue -= 2; + psz++; + } + + /* Do the copying. */ + if (cchCurValue < cbValue) + { + memcpy(pszValue, psz, cchCurValue); + pszValue[cchCurValue] = '\0'; + if (pcbActual) + *pcbActual = cchCurValue; + return VINF_SUCCESS; + } + + if (cbValue > 0) + { + memcpy(pszValue, psz, cbValue - 1); + pszValue[cbValue - 1] = '\0'; + } + if (pcbActual) + *pcbActual = cchCurValue + 1; + return VERR_BUFFER_OVERFLOW; + } + } + } + return VERR_NOT_FOUND; +} + + +/** + * Queries a value in a section. + * + * The first matching value is returned. The matching is by default case + * insensitive. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if section or key not found. + * + * @param hIniFile The INI-file handle. + * @param pszSection The section name. Pass NULL to refer to the + * unsectioned key space at the top of the file. + * @param pszKey The key name. + * @param pszValue Where to return the value. + * @param cbValue Size of the buffer @a pszValue points to. + * @param pcbActual Where to return the actual value size excluding + * terminator on success. On VERR_BUFFER_OVERFLOW this + * will be set to the buffer size needed to hold the + * value, terminator included. Optional. + */ +RTDECL(int) RTIniFileQueryValue(RTINIFILE hIniFile, const char *pszSection, const char *pszKey, + char *pszValue, size_t cbValue, size_t *pcbActual) +{ + /* + * Validate input. + */ + PRTINIFILEINT pThis = hIniFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, VERR_INVALID_HANDLE); + AssertPtrNullReturn(pszSection, VERR_INVALID_POINTER); + AssertPtrReturn(pszKey, VERR_INVALID_POINTER); + size_t const cchKey = strlen(pszKey); + if (cbValue) + AssertPtrReturn(pszValue, VERR_INVALID_POINTER); + AssertPtrNullReturn(pcbActual, VERR_INVALID_POINTER); + + /* + * Search relevant sections. + */ + int rc; + if (pszSection == NULL) + rc = rtIniFileQueryValueInSection(pThis, &pThis->paSections[0], pszKey, cchKey, pszValue, cbValue, pcbActual); + else + { + rc = VERR_NOT_FOUND; + uint32_t const cchSection = (uint32_t)strlen(pszSection); + for (uint32_t iSection = 1; iSection < pThis->cSections; iSection++) + if ( pThis->paSections[iSection].cchName == cchSection + && RTStrNICmp(&pThis->pszFile[pThis->paSections[iSection].offName], pszSection, cchSection) == 0) + { + rc = rtIniFileQueryValueInSection(pThis, &pThis->paSections[iSection], pszKey, cchKey, + pszValue, cbValue, pcbActual); + if (rc != VERR_NOT_FOUND) + break; + } + } + return rc; +} + + +/** + * Worker for RTIniFileQueryPair. + * + * This can also be used to count the number of pairs in a section. + */ +static int rtIniFileQueryPairInSection(PRTINIFILEINT pThis, PRTINIFILESECTION pSection, uint32_t *pidxPair, + char *pszKey, size_t cbKey, size_t *pcbKeyActual, + char *pszValue, size_t cbValue, size_t *pcbValueActual) +{ + uint32_t idxPair = *pidxPair; + + /* + * Scan the section, looking for the matching key. + */ + Assert(pSection->cchSkipToValues <= pSection->cchSection); + const char * const pszEnd = &pThis->pszFile[pSection->offName + pSection->cchSection]; + const char * pszNext = pszEnd; + for (const char *psz = &pThis->pszFile[pSection->offName + pSection->cchSkipToValues]; + (uintptr_t)psz < (uintptr_t)pszEnd; + psz = pszNext) + { + /* Find start of next line so we can use 'continue' to skip a line. */ + pszNext = strchr(psz, '\n'); + if (pszNext) + pszNext++; + else + pszNext = pszEnd; + + /* Skip leading spaces. */ + char ch; + while ((ch = *psz) != '\0' && RT_C_IS_SPACE(ch)) + psz++; + if ( ch != ';' /* comment line */ + && ch != '\n' /* empty line */ + && ch != '\r' /* empty line */ + && (uintptr_t)psz < (uintptr_t)pszEnd) + { + /* Find end of key name, if any. */ + const char *pszCurKey = psz; + size_t cchCurKey; + const char *pszEqual; + if (ch != '=') + { + /** @todo deal with escaped equal signs? */ + pszEqual = strchr(psz, '='); + if (pszEqual) + { + if ((uintptr_t)pszEqual < (uintptr_t)pszNext) + cchCurKey = pszEqual - pszCurKey; + else + continue; + } + else + break; + } + else + { + cchCurKey = 0; + pszEqual = psz; + } + + /* Is this the pair we're looking for? */ + if (idxPair > 0) + idxPair--; + else + { + /* + * Yes it's the stuff we're looking for. + * Prepare the the return stuff. + */ + + /* Strip trailing spaces from the key name. */ + while (cchCurKey > 0 && RT_C_IS_SPACE(pszCurKey[cchCurKey - 1])) + cchCurKey--; + + /* Skip leading blanks from the value. */ + psz = pszEqual + 1; + while ((ch = *psz) && RT_C_IS_SPACE(ch) && ch != '\n') + psz++; + + /* Strip trailing spaces from the value. */ + size_t cchCurValue = pszNext - psz; + while (cchCurValue > 1 && RT_C_IS_SPACE(psz[cchCurValue - 1])) + cchCurValue--; + + /* Strip value quotes. */ + if ( cchCurValue > 2 + && ( (ch = *psz) == '"' + || ch == '\'' ) + && psz[cchCurValue - 1] == ch) + { + cchCurValue -= 2; + psz++; + } + + /* + * Copy the stuff out. + */ + if ( cchCurValue < cbValue + && cchCurKey < cbKey) + { + memcpy(pszKey, pszCurKey, cchCurKey); + pszKey[cchCurKey] = '\0'; + if (pcbKeyActual) + *pcbKeyActual = cchCurKey; + + memcpy(pszValue, psz, cchCurValue); + pszValue[cchCurValue] = '\0'; + if (pcbValueActual) + *pcbValueActual = cchCurValue; + + *pidxPair = 0; + return VINF_SUCCESS; + } + + /* Buffer overflow. Copy out what we can. */ + if (cbKey > 0) + { + if (cchCurKey < cbKey) + cbKey = cchCurKey + 1; + memcpy(pszKey, pszCurKey, cbKey - 1); + pszKey[cbKey - 1] = '\0'; + } + if (pcbKeyActual) + *pcbKeyActual = cchCurKey + 1; + + if (cbValue > 0) + { + if (cchCurValue < cbValue) + cbValue = cchCurValue + 1; + memcpy(pszValue, psz, cbValue - 1); + pszValue[cbValue - 1] = '\0'; + } + if (pcbValueActual) + *pcbValueActual = cchCurValue + 1; + + *pidxPair = 0; + return VERR_BUFFER_OVERFLOW; + } + } + } + *pidxPair = idxPair; + return VERR_NOT_FOUND; +} + + +/** + * Queries a key-value pair in a section by ordinal. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if the section wasn't found or if it contains no pair + * with the given ordinal value. + * + * @param hIniFile The INI-file handle. + * @param pszSection The section name. Pass NULL to refer to the + * unsectioned key space at the top of the file. + * @param idxPair The pair to fetch (counting from 0). + * + * @param pszKey Where to return the key name. + * @param cbKey Size of the buffer @a pszKey points to. + * @param pcbKeyActual Where to return the actual key size excluding + * terminator on success. On VERR_BUFFER_OVERFLOW this + * will be set to the buffer size needed to hold the + * value, terminator included. Optional. + * + * @param pszValue Where to return the value. + * @param cbValue Size of the buffer @a pszValue points to. + * @param pcbValueActual Where to return the actual value size excluding + * terminator on success. On VERR_BUFFER_OVERFLOW this + * will be set to the buffer size needed to hold the + * value, terminator included. Optional. + */ +RTDECL(int) RTIniFileQueryPair(RTINIFILE hIniFile, const char *pszSection, uint32_t idxPair, + char *pszKey, size_t cbKey, size_t *pcbKeyActual, + char *pszValue, size_t cbValue, size_t *pcbValueActual) +{ + /* + * Validate input. + */ + PRTINIFILEINT pThis = hIniFile; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, VERR_INVALID_HANDLE); + AssertPtrNullReturn(pszSection, VERR_INVALID_POINTER); + if (cbKey) + AssertPtrReturn(pszKey, VERR_INVALID_POINTER); + AssertPtrNullReturn(pcbKeyActual, VERR_INVALID_POINTER); + if (cbValue) + AssertPtrReturn(pszValue, VERR_INVALID_POINTER); + AssertPtrNullReturn(pcbValueActual, VERR_INVALID_POINTER); + + /* + * Search relevant sections. + */ + int rc; + if (pszSection == NULL) + rc = rtIniFileQueryPairInSection(pThis, &pThis->paSections[0], &idxPair, + pszKey, cbKey, pcbKeyActual, pszValue, cbValue, pcbValueActual); + else + { + rc = VERR_NOT_FOUND; + uint32_t const cchSection = (uint32_t)strlen(pszSection); + for (uint32_t iSection = 1; iSection < pThis->cSections; iSection++) + if ( pThis->paSections[iSection].cchName == cchSection + && RTStrNICmp(&pThis->pszFile[pThis->paSections[iSection].offName], pszSection, cchSection) == 0) + { + rc = rtIniFileQueryPairInSection(pThis, &pThis->paSections[iSection], &idxPair, + pszKey, cbKey, pcbKeyActual, pszValue, cbValue, pcbValueActual); + if (rc != VERR_NOT_FOUND) + break; + } + } + return rc; +} + |