/* $Id: manifest2.cpp $ */
/** @file
 * IPRT - Manifest, the core.
 */

/*
 * Copyright (C) 2010-2012 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 "internal/iprt.h"
#include <iprt/manifest.h>

#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/ctype.h>
#include <iprt/err.h>
#include <iprt/mem.h>
#include <iprt/param.h>
#include <iprt/md5.h>
#include <iprt/sha.h>
#include <iprt/string.h>
#include <iprt/vfs.h>

#include "internal/magics.h"


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/**
 * Manifest attribute.
 *
 * Used both for entries and manifest attributes.
 */
typedef struct RTMANIFESTATTR
{
    /** The string space core (szName). */
    RTSTRSPACECORE      StrCore;
    /** The property value. */
    char               *pszValue;
    /** The attribute type if applicable, RTMANIFEST_ATTR_UNKNOWN if not. */
    uint32_t            fType;
    /** Whether it was visited by the equals operation or not. */
    bool                fVisited;
    /** The normalized property name that StrCore::pszString points at. */
    char                szName[1];
} RTMANIFESTATTR;
/** Pointer to a manifest attribute. */
typedef RTMANIFESTATTR *PRTMANIFESTATTR;


/**
 * Manifest entry.
 */
typedef struct RTMANIFESTENTRY
{
    /** The string space core (szName). */
    RTSTRSPACECORE      StrCore;
    /** The entry attributes (hashes, checksums, size, etc) -
     *  RTMANIFESTATTR. */
    RTSTRSPACE          Attributes;
    /** The number of attributes. */
    uint32_t            cAttributes;
    /** Whether it was visited by the equals operation or not. */
    bool                fVisited;
    /** The normalized entry name that StrCore::pszString points at. */
    char                szName[1];
} RTMANIFESTENTRY;
/** Pointer to a manifest entry. */
typedef RTMANIFESTENTRY *PRTMANIFESTENTRY;


/**
 * Manifest handle data.
 */
typedef struct RTMANIFESTINT
{
    /** Magic value (RTMANIFEST_MAGIC). */
    uint32_t            u32Magic;
    /** The number of references to this manifest. */
    uint32_t volatile   cRefs;
    /** String space of the entries covered by this manifest -
     *  RTMANIFESTENTRY. */
    RTSTRSPACE          Entries;
    /** The number of entries. */
    uint32_t            cEntries;
    /** The entry for the manifest itself. */
    RTMANIFESTENTRY     SelfEntry;
} RTMANIFESTINT;

/** The value of RTMANIFESTINT::u32Magic. */
#define RTMANIFEST_MAGIC    UINT32_C(0x99998866)

/**
 * Argument package passed to rtManifestWriteStdAttr by rtManifestWriteStdEntry
 * and RTManifestWriteStandard.
 */
typedef struct RTMANIFESTWRITESTDATTR
{
    /** The entry name. */
    const char     *pszEntry;
    /** The output I/O stream. */
    RTVFSIOSTREAM   hVfsIos;
} RTMANIFESTWRITESTDATTR;


/**
 * Argument package used by RTManifestEqualsEx to pass its arguments to the
 * enumeration callback functions.
 */
typedef struct RTMANIFESTEQUALS
{
    /** Name of entries to ignore. */
    const char * const *papszIgnoreEntries;
    /** Name of attributes to ignore. */
    const char * const *papszIgnoreAttr;
    /** Flags governing the comparision. */
    uint32_t            fFlags;
    /** Where to return an error message (++) on failure.  Can be NULL. */
    char               *pszError;
    /** The size of the buffer pszError points to.  Can be 0. */
    size_t              cbError;

    /** Pointer to the 2nd manifest. */
    RTMANIFESTINT      *pThis2;

    /** The number of ignored entries from the 1st manifest. */
    uint32_t            cIgnoredEntries2;
    /** The number of entries processed from the 2nd manifest. */
    uint32_t            cEntries2;

    /** The number of ignored attributes from the 1st manifest. */
    uint32_t            cIgnoredAttributes1;
    /** The number of ignored attributes from the 1st manifest. */
    uint32_t            cIgnoredAttributes2;
    /** The number of attributes processed from the 2nd manifest. */
    uint32_t            cAttributes2;
    /** Pointer to the string space to get matching attributes from. */
    PRTSTRSPACE         pAttributes2;
    /** The name of the current entry.
     * Points to an empty string it's the manifest attributes. */
    const char         *pszCurEntry;
} RTMANIFESTEQUALS;
/** Pointer to an RTManifestEqualEx argument packet. */
typedef RTMANIFESTEQUALS *PRTMANIFESTEQUALS;

/**
 * Argument package used by rtMainfestQueryAttrWorker to pass its search
 * criteria to rtMainfestQueryAttrEnumCallback and get a result back.
 */
typedef struct RTMANIFESTQUERYATTRARGS
{
    /** The attribute types we're hunting for. */
    uint32_t        fType;
    /** What we've found. */
    PRTMANIFESTATTR pAttr;
} RTMANIFESTQUERYATTRARGS;
/** Pointer to a rtMainfestQueryAttrEnumCallback argument packet. */
typedef RTMANIFESTQUERYATTRARGS *PRTMANIFESTQUERYATTRARGS;


/**
 * Creates an empty manifest.
 *
 * @returns IPRT status code.
 * @param   fFlags              Flags, MBZ.
 * @param   phManifest          Where to return the handle to the manifest.
 */
RTDECL(int) RTManifestCreate(uint32_t fFlags, PRTMANIFEST phManifest)
{
    AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
    AssertPtr(phManifest);

    RTMANIFESTINT *pThis = (RTMANIFESTINT *)RTMemAlloc(sizeof(*pThis));
    if (!pThis)
        return VERR_NO_MEMORY;

    pThis->u32Magic     = RTMANIFEST_MAGIC;
    pThis->cRefs        = 1;
    pThis->Entries      = NULL;
    pThis->cEntries     = 0;
    pThis->SelfEntry.StrCore.pszString = "main";
    pThis->SelfEntry.StrCore.cchString = 4;
    pThis->SelfEntry.Attributes        = NULL;
    pThis->SelfEntry.cAttributes       = 0;
    pThis->SelfEntry.fVisited          = false;
    pThis->SelfEntry.szName[0]         = '\0';

    *phManifest = pThis;
    return VINF_SUCCESS;
}

/**
 * Retains a reference to the manifest handle.
 *
 * @returns The new reference count, UINT32_MAX if the handle is invalid.
 * @param   hManifest           The handle to retain.
 */
RTDECL(uint32_t) RTManifestRetain(RTMANIFEST hManifest)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, UINT32_MAX);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, UINT32_MAX);

    uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
    Assert(cRefs > 1 && cRefs < _1M);

    return cRefs;
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Destroys RTMANIFESTATTR.}
 */
static DECLCALLBACK(int) rtManifestDestroyAttribute(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTATTR pAttr = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);
    RTStrFree(pAttr->pszValue);
    pAttr->pszValue = NULL;
    RTMemFree(pAttr);
    NOREF(pvUser);
    return 0;
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Destroys RTMANIFESTENTRY.}
 */
static DECLCALLBACK(int) rtManifestDestroyEntry(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTENTRY pEntry = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);
    RTStrSpaceDestroy(&pEntry->Attributes, rtManifestDestroyAttribute, pvUser);
    RTMemFree(pEntry);
    return 0;
}


/**
 * Releases a reference to the manifest handle.
 *
 * @returns The new reference count, 0 if free. UINT32_MAX is returned if the
 *          handle is invalid.
 * @param   hManifest           The handle to release.
 *                              NIL is quietly ignored (returns 0).
 */
RTDECL(uint32_t) RTManifestRelease(RTMANIFEST hManifest)
{
    RTMANIFESTINT *pThis = hManifest;
    if (pThis == NIL_RTMANIFEST)
        return 0;
    AssertPtrReturn(pThis, UINT32_MAX);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, UINT32_MAX);

    uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
    Assert(cRefs < _1M);
    if (!cRefs)
    {
        ASMAtomicWriteU32(&pThis->u32Magic, ~RTMANIFEST_MAGIC);
        RTStrSpaceDestroy(&pThis->Entries, rtManifestDestroyEntry,pThis);
        RTStrSpaceDestroy(&pThis->SelfEntry.Attributes, rtManifestDestroyAttribute, pThis);
        RTMemFree(pThis);
    }

    return cRefs;
}


/**
 * Creates a duplicate of the specified manifest.
 *
 * @returns IPRT status code
 * @param   hManifestSrc        The manifest to clone.
 * @param   phManifestDst       Where to store the handle to the duplicate.
 */
RTDECL(int) RTManifestDup(RTMANIFEST hManifestSrc, PRTMANIFEST phManifestDst)
{
    RTMANIFESTINT *pThis = hManifestSrc;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    AssertPtr(phManifestDst);


    /** @todo implement cloning. */

    return VERR_NOT_IMPLEMENTED;
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Prepare equals operation.}
 */
static DECLCALLBACK(int) rtManifestAttributeClearVisited(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTATTR pAttr = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);
    pAttr->fVisited = false;
    NOREF(pvUser);
    return 0;
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Prepare equals operation.}
 */
static DECLCALLBACK(int) rtManifestEntryClearVisited(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTENTRY pEntry = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);
    RTStrSpaceEnumerate(&pEntry->Attributes, rtManifestAttributeClearVisited, NULL);
    pEntry->fVisited = false;
    NOREF(pvUser);
    return 0;
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Finds the first missing.}
 */
static DECLCALLBACK(int) rtManifestAttributeFindMissing2(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTEQUALS pEquals = (PRTMANIFESTEQUALS)pvUser;
    PRTMANIFESTATTR   pAttr   = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);

    /*
     * Already visited?
     */
    if (pAttr->fVisited)
        return 0;

    /*
     * Ignore this entry?
     */
    char const * const *ppsz = pEquals->papszIgnoreAttr;
    if (ppsz)
    {
        while (*ppsz)
        {
            if (!strcmp(*ppsz, pAttr->szName))
                return 0;
            ppsz++;
        }
    }

    /*
     * Gotcha!
     */
    if (*pEquals->pszCurEntry)
        RTStrPrintf(pEquals->pszError, pEquals->cbError,
                    "Attribute '%s' on '%s' was not found in the 1st manifest",
                    pAttr->szName, pEquals->pszCurEntry);
    else
        RTStrPrintf(pEquals->pszError, pEquals->cbError, "Attribute '%s' was not found in the 1st manifest", pAttr->szName);
    return VERR_NOT_EQUAL;
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Finds the first missing.}
 */
static DECLCALLBACK(int) rtManifestEntryFindMissing2(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTEQUALS pEquals = (PRTMANIFESTEQUALS)pvUser;
    PRTMANIFESTENTRY  pEntry  = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);

    /*
     * Already visited?
     */
    if (pEntry->fVisited)
        return 0;

    /*
     * Ignore this entry?
     */
    char const * const *ppsz = pEquals->papszIgnoreEntries;
    if (ppsz)
    {
        while (*ppsz)
        {
            if (!strcmp(*ppsz, pEntry->StrCore.pszString))
                return 0;
            ppsz++;
        }
    }

    /*
     * Gotcha!
     */
    RTStrPrintf(pEquals->pszError, pEquals->cbError, "'%s' was not found in the 1st manifest", pEntry->StrCore.pszString);
    return VERR_NOT_EQUAL;
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Compares attributes.}
 */
static DECLCALLBACK(int) rtManifestAttributeCompare(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTEQUALS pEquals = (PRTMANIFESTEQUALS)pvUser;
    PRTMANIFESTATTR   pAttr1  = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);
    PRTMANIFESTATTR   pAttr2;

    Assert(!pAttr1->fVisited);
    pAttr1->fVisited = true;

    /*
     * Ignore this entry?
     */
    char const * const *ppsz = pEquals->papszIgnoreAttr;
    if (ppsz)
    {
        while (*ppsz)
        {
            if (!strcmp(*ppsz, pAttr1->szName))
            {
                pAttr2 = (PRTMANIFESTATTR)RTStrSpaceGet(pEquals->pAttributes2, pAttr1->szName);
                if (pAttr2)
                {
                    Assert(!pAttr2->fVisited);
                    pAttr2->fVisited = true;
                    pEquals->cIgnoredAttributes2++;
                }
                pEquals->cIgnoredAttributes1++;
                return 0;
            }
            ppsz++;
        }
    }

    /*
     * Find the matching attribute.
     */
    pAttr2 = (PRTMANIFESTATTR)RTStrSpaceGet(pEquals->pAttributes2, pAttr1->szName);
    if (!pAttr2)
    {
        if (pEquals->fFlags & RTMANIFEST_EQUALS_IGN_MISSING_ATTRS)
            return 0;

        if (*pEquals->pszCurEntry)
            RTStrPrintf(pEquals->pszError, pEquals->cbError,
                        "Attribute '%s' on '%s' was not found in the 2nd manifest",
                        pAttr1->szName, pEquals->pszCurEntry);
        else
            RTStrPrintf(pEquals->pszError, pEquals->cbError, "Attribute '%s' was not found in the 2nd manifest", pAttr1->szName);
        return VERR_NOT_EQUAL;
    }

    Assert(!pAttr2->fVisited);
    pAttr2->fVisited = true;
    pEquals->cAttributes2++;

    /*
     * Compare them.
     */
    if (strcmp(pAttr1->pszValue, pAttr2->pszValue))
    {
        if (*pEquals->pszCurEntry)
            RTStrPrintf(pEquals->pszError, pEquals->cbError,
                        "Attribute '%s' on '%s' does not match ('%s' vs. '%s')",
                        pAttr1->szName, pEquals->pszCurEntry, pAttr1->pszValue, pAttr2->pszValue);
        else
            RTStrPrintf(pEquals->pszError, pEquals->cbError,
                        "Attribute '%s' does not match ('%s' vs. '%s')",
                        pAttr1->szName, pAttr1->pszValue, pAttr2->pszValue);
        return VERR_NOT_EQUAL;
    }

    return 0;
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Prepare equals operation.}
 */
DECLINLINE (int) rtManifestEntryCompare2(PRTMANIFESTEQUALS pEquals, PRTMANIFESTENTRY pEntry1, PRTMANIFESTENTRY pEntry2)
{
    /*
     * Compare the attributes.  It's a bit ugly with all this counting, but
     * how else to efficiently implement RTMANIFEST_EQUALS_IGN_MISSING_ATTRS?
     */
    pEquals->cIgnoredAttributes1 = 0;
    pEquals->cIgnoredAttributes2 = 0;
    pEquals->cAttributes2        = 0;
    pEquals->pszCurEntry         = &pEntry2->szName[0];
    pEquals->pAttributes2        = &pEntry2->Attributes;
    int rc = RTStrSpaceEnumerate(&pEntry1->Attributes, rtManifestAttributeCompare, pEquals);
    if (RT_SUCCESS(rc))
    {
        /*
         * Check that we matched all that is required.
         */
        if (   pEquals->cAttributes2 + pEquals->cIgnoredAttributes2 != pEntry2->cAttributes
            && (  !(pEquals->fFlags & RTMANIFEST_EQUALS_IGN_MISSING_ATTRS)
                || pEquals->cIgnoredAttributes1 == pEntry1->cAttributes))
            rc = RTStrSpaceEnumerate(&pEntry2->Attributes, rtManifestAttributeFindMissing2, pEquals);
    }
    return rc;
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Prepare equals operation.}
 */
static DECLCALLBACK(int) rtManifestEntryCompare(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTEQUALS pEquals = (PRTMANIFESTEQUALS)pvUser;
    PRTMANIFESTENTRY  pEntry1 = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);
    PRTMANIFESTENTRY  pEntry2;

    /*
     * Ignore this entry.
     */
    char const * const *ppsz = pEquals->papszIgnoreEntries;
    if (ppsz)
    {
        while (*ppsz)
        {
            if (!strcmp(*ppsz, pStr->pszString))
            {
                pEntry2 = (PRTMANIFESTENTRY)RTStrSpaceGet(&pEquals->pThis2->Entries, pStr->pszString);
                if (pEntry2)
                {
                    pEntry2->fVisited = true;
                    pEquals->cIgnoredEntries2++;
                }
                pEntry1->fVisited = true;
                return 0;
            }
            ppsz++;
        }
    }

    /*
     * Try find the entry in the other manifest.
     */
    pEntry2 = (PRTMANIFESTENTRY)RTStrSpaceGet(&pEquals->pThis2->Entries, pEntry1->StrCore.pszString);
    if (!pEntry2)
    {
        RTStrPrintf(pEquals->pszError, pEquals->cbError, "'%s' not found in the 2nd manifest", pEntry1->StrCore.pszString);
        return VERR_NOT_EQUAL;
    }

    Assert(!pEntry1->fVisited);
    Assert(!pEntry2->fVisited);
    pEntry1->fVisited = true;
    pEntry2->fVisited = true;
    pEquals->cEntries2++;

    return rtManifestEntryCompare2(pEquals, pEntry1, pEntry2);
}



RTDECL(int) RTManifestEqualsEx(RTMANIFEST hManifest1, RTMANIFEST hManifest2, const char * const *papszIgnoreEntries,
                               const char * const *papszIgnoreAttr, uint32_t fFlags, char *pszError, size_t cbError)
{
    /*
     * Validate input.
     */
    AssertPtrNullReturn(pszError, VERR_INVALID_POINTER);
    if (pszError && cbError)
        *pszError = '\0';
    RTMANIFESTINT *pThis1 = hManifest1;
    RTMANIFESTINT *pThis2 = hManifest2;
    if (pThis1 != NIL_RTMANIFEST)
    {
        AssertPtrReturn(pThis1, VERR_INVALID_HANDLE);
        AssertReturn(pThis1->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    }
    if (pThis2 != NIL_RTMANIFEST)
    {
        AssertPtrReturn(pThis2, VERR_INVALID_HANDLE);
        AssertReturn(pThis2->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    }
    AssertReturn(!(fFlags & ~(RTMANIFEST_EQUALS_IGN_MISSING_ATTRS)), VERR_INVALID_PARAMETER);

    /*
     * The simple cases.
     */
    if (pThis1 == pThis2)
        return VINF_SUCCESS;
    if (pThis1 == NIL_RTMANIFEST || pThis2 == NIL_RTMANIFEST)
        return VERR_NOT_EQUAL;

    /*
     * Since we have to use callback style enumeration, we have to mark the
     * entries and attributes to make sure we've covered them all.
     */
    RTStrSpaceEnumerate(&pThis1->Entries, rtManifestEntryClearVisited, NULL);
    RTStrSpaceEnumerate(&pThis2->Entries, rtManifestEntryClearVisited, NULL);
    RTStrSpaceEnumerate(&pThis1->SelfEntry.Attributes, rtManifestAttributeClearVisited, NULL);
    RTStrSpaceEnumerate(&pThis2->SelfEntry.Attributes, rtManifestAttributeClearVisited, NULL);

    RTMANIFESTEQUALS Equals;
    Equals.pThis2               = pThis2;
    Equals.fFlags               = fFlags;
    Equals.papszIgnoreEntries   = papszIgnoreEntries;
    Equals.papszIgnoreAttr      = papszIgnoreAttr;
    Equals.pszError             = pszError;
    Equals.cbError              = cbError;

    Equals.cIgnoredEntries2     = 0;
    Equals.cEntries2            = 0;
    Equals.cIgnoredAttributes1  = 0;
    Equals.cIgnoredAttributes2  = 0;
    Equals.cAttributes2         = 0;
    Equals.pAttributes2         = NULL;
    Equals.pszCurEntry          = NULL;

    int rc = rtManifestEntryCompare2(&Equals, &pThis1->SelfEntry, &pThis2->SelfEntry);
    if (RT_SUCCESS(rc))
        rc = RTStrSpaceEnumerate(&pThis1->Entries, rtManifestEntryCompare, &Equals);
    if (RT_SUCCESS(rc))
    {
        /*
         * Did we cover all entries of the 2nd manifest?
         */
        if (Equals.cEntries2 + Equals.cIgnoredEntries2 != pThis2->cEntries)
            rc = RTStrSpaceEnumerate(&pThis1->Entries, rtManifestEntryFindMissing2, &Equals);
    }

    return rc;
}


RTDECL(int) RTManifestEquals(RTMANIFEST hManifest1, RTMANIFEST hManifest2)
{
    return RTManifestEqualsEx(hManifest1, hManifest2,
                              NULL /*papszIgnoreEntries*/, NULL /*papszIgnoreAttrs*/,
                              0 /*fFlags*/, NULL, 0);
}


/**
 * Worker common to RTManifestSetAttr and RTManifestEntrySetAttr.
 *
 * @returns IPRT status code.
 * @param   pEntry              Pointer to the entry.
 * @param   pszAttr             The name of the attribute to add.
 * @param   pszValue            The value string.
 * @param   fType               The attribute type type.
 */
static int rtManifestSetAttrWorker(PRTMANIFESTENTRY pEntry, const char *pszAttr, const char *pszValue, uint32_t fType)
{
    char *pszValueCopy;
    int rc = RTStrDupEx(&pszValueCopy, pszValue);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Does the attribute exist already?
     */
    AssertCompileMemberOffset(RTMANIFESTATTR, StrCore, 0);
    PRTMANIFESTATTR pAttr = (PRTMANIFESTATTR)RTStrSpaceGet(&pEntry->Attributes, pszAttr);
    if (pAttr)
    {
        RTStrFree(pAttr->pszValue);
        pAttr->pszValue = pszValueCopy;
        pAttr->fType    = fType;
    }
    else
    {
        size_t          cbName = strlen(pszAttr) + 1;
        pAttr = (PRTMANIFESTATTR)RTMemAllocVar(RT_OFFSETOF(RTMANIFESTATTR, szName[cbName]));
        if (!pAttr)
        {
            RTStrFree(pszValueCopy);
            return VERR_NO_MEMORY;
        }
        memcpy(pAttr->szName, pszAttr, cbName);
        pAttr->StrCore.pszString = pAttr->szName;
        pAttr->StrCore.cchString = cbName - 1;
        pAttr->pszValue = pszValueCopy;
        pAttr->fType    = fType;
        if (RT_UNLIKELY(!RTStrSpaceInsert(&pEntry->Attributes, &pAttr->StrCore)))
        {
            AssertFailed();
            RTStrFree(pszValueCopy);
            RTMemFree(pAttr);
            return VERR_INTERNAL_ERROR_4;
        }
        pEntry->cAttributes++;
    }

    return VINF_SUCCESS;
}


/**
 * Sets a manifest attribute.
 *
 * @returns IPRT status code.
 * @param   hManifest           The manifest handle.
 * @param   pszAttr             The attribute name.  If this already exists,
 *                              its value will be replaced.
 * @param   pszValue            The value string.
 * @param   fType               The attribute type, pass
 *                              RTMANIFEST_ATTR_UNKNOWN if not known.
 */
RTDECL(int) RTManifestSetAttr(RTMANIFEST hManifest, const char *pszAttr, const char *pszValue, uint32_t fType)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    AssertPtr(pszAttr);
    AssertPtr(pszValue);
    AssertReturn(RT_IS_POWER_OF_TWO(fType) && fType < RTMANIFEST_ATTR_END, VERR_INVALID_PARAMETER);

    return rtManifestSetAttrWorker(&pThis->SelfEntry, pszAttr, pszValue, fType);
}


/**
 * Worker common to RTManifestUnsetAttr and RTManifestEntryUnsetAttr.
 *
 * @returns IPRT status code.
 * @param   pEntry              Pointer to the entry.
 * @param   pszAttr             The name of the attribute to remove.
 */
static int rtManifestUnsetAttrWorker(PRTMANIFESTENTRY pEntry, const char *pszAttr)
{
    PRTSTRSPACECORE pStrCore = RTStrSpaceRemove(&pEntry->Attributes, pszAttr);
    if (!pStrCore)
        return VWRN_NOT_FOUND;
    pEntry->cAttributes--;
    rtManifestDestroyAttribute(pStrCore, NULL);
    return VINF_SUCCESS;
}


/**
 * Unsets (removes) a manifest attribute if it exists.
 *
 * @returns IPRT status code.
 * @retval  VWRN_NOT_FOUND if not found.
 *
 * @param   hManifest           The manifest handle.
 * @param   pszAttr             The attribute name.
 */
RTDECL(int) RTManifestUnsetAttr(RTMANIFEST hManifest, const char *pszAttr)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    AssertPtr(pszAttr);

    return rtManifestUnsetAttrWorker(&pThis->SelfEntry, pszAttr);
}


/**
 * Callback employed by rtManifestQueryAttrWorker to search by attribute type.
 *
 * @returns VINF_SUCCESS or VINF_CALLBACK_RETURN.
 * @param   pStr                The attribute string node.
 * @param   pvUser              The argument package.
 */
static DECLCALLBACK(int) rtMainfestQueryAttrEnumCallback(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTATTR             pAttr = (PRTMANIFESTATTR)pStr;
    PRTMANIFESTQUERYATTRARGS    pArgs = (PRTMANIFESTQUERYATTRARGS)pvUser;

    if (pAttr->fType & pArgs->fType)
    {
        pArgs->pAttr = pAttr;
        return VINF_CALLBACK_RETURN;
    }
    return VINF_SUCCESS;
}


/**
 * Worker common to RTManifestQueryAttr and RTManifestEntryQueryAttr.
 *
 * @returns IPRT status code.
 * @param   pEntry              The entry.
 * @param   pszAttr             The attribute name.  If NULL, it will be
 *                              selected by @a fType alone.
 * @param   fType               The attribute types the entry should match. Pass
 *                              Pass RTMANIFEST_ATTR_ANY match any.  If more
 *                              than one is given, the first matching one is
 *                              returned.
 * @param   pszValue            Where to return value.
 * @param   cbValue             The size of the buffer @a pszValue points to.
 * @param   pfType              Where to return the attribute type value.
 */
static int rtManifestQueryAttrWorker(PRTMANIFESTENTRY pEntry, const char *pszAttr, uint32_t fType,
                                     char *pszValue, size_t cbValue, uint32_t *pfType)
{
    /*
     * Find the requested attribute.
     */
    PRTMANIFESTATTR pAttr;
    if (pszAttr)
    {
        /* By name. */
        pAttr = (PRTMANIFESTATTR)RTStrSpaceGet(&pEntry->Attributes, pszAttr);
        if (!pAttr)
            return VERR_MANIFEST_ATTR_NOT_FOUND;
        if (!(pAttr->fType & fType))
            return VERR_MANIFEST_ATTR_TYPE_MISMATCH;
    }
    else
    {
        /* By type. */
        RTMANIFESTQUERYATTRARGS Args;
        Args.fType = fType;
        Args.pAttr = NULL;
        int rc = RTStrSpaceEnumerate(&pEntry->Attributes, rtMainfestQueryAttrEnumCallback, &Args);
        AssertRCReturn(rc, rc);
        pAttr = Args.pAttr;
        if (!pAttr)
            return VERR_MANIFEST_ATTR_TYPE_NOT_FOUND;
    }

    /*
     * Set the return values.
     */
    if (cbValue || pszValue)
    {
        size_t cbNeeded = strlen(pAttr->pszValue) + 1;
        if (cbNeeded > cbValue)
            return VERR_BUFFER_OVERFLOW;
        memcpy(pszValue, pAttr->pszValue, cbNeeded);
    }

    if (pfType)
        *pfType = pAttr->fType;

    return VINF_SUCCESS;
}


RTDECL(int) RTManifestQueryAttr(RTMANIFEST hManifest, const char *pszAttr, uint32_t fType,
                                char *pszValue, size_t cbValue, uint32_t *pfType)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    AssertPtrNull(pszAttr);
    AssertPtr(pszValue);

    return rtManifestQueryAttrWorker(&pThis->SelfEntry, pszAttr, fType, pszValue, cbValue, pfType);
}


/**
 * Validates the name entry.
 *
 * @returns IPRT status code.
 * @param   pszEntry            The entry name to validate.
 * @param   pfNeedNormalization Where to return whether it needs normalization
 *                              or not.  Optional.
 * @param   pcchEntry           Where to return the length.  Optional.
 */
static int rtManifestValidateNameEntry(const char *pszEntry, bool *pfNeedNormalization, size_t *pcchEntry)
{
    int         rc;
    bool        fNeedNormalization = false;
    const char *pszCur             = pszEntry;

    for (;;)
    {
        RTUNICP uc;
        rc = RTStrGetCpEx(&pszCur, &uc);
        if (RT_FAILURE(rc))
            return rc;
        if (!uc)
            break;
        if (uc == '\\')
            fNeedNormalization = true;
        else if (uc < 32 || uc == ':' || uc == '(' || uc == ')')
            return VERR_INVALID_NAME;
    }

    if (pfNeedNormalization)
        *pfNeedNormalization = fNeedNormalization;

    size_t cchEntry = pszCur - pszEntry - 1;
    if (!cchEntry)
        rc = VERR_INVALID_NAME;
    if (pcchEntry)
        *pcchEntry = cchEntry;

    return rc;
}


/**
 * Normalizes a entry name.
 *
 * @param   pszEntry            The entry name to normalize.
 */
static void rtManifestNormalizeEntry(char *pszEntry)
{
    char  ch;
    while ((ch = *pszEntry))
    {
        if (ch == '\\')
            *pszEntry = '/';
        pszEntry++;
    }
}


/**
 * Gets an entry.
 *
 * @returns IPRT status code.
 * @param   pThis               The manifest to work with.
 * @param   pszEntry            The entry name.
 * @param   fNeedNormalization  Whether rtManifestValidateNameEntry said it
 *                              needed normalization.
 * @param   cchEntry            The length of the name.
 * @param   ppEntry             Where to return the entry pointer on success.
 */
static int rtManifestGetEntry(RTMANIFESTINT *pThis, const char *pszEntry, bool fNeedNormalization, size_t cchEntry,
                              PRTMANIFESTENTRY *ppEntry)
{
    PRTMANIFESTENTRY pEntry;

    AssertCompileMemberOffset(RTMANIFESTATTR, StrCore, 0);
    if (!fNeedNormalization)
        pEntry = (PRTMANIFESTENTRY)RTStrSpaceGet(&pThis->Entries, pszEntry);
    else
    {
        char *pszCopy = (char *)RTMemTmpAlloc(cchEntry + 1);
        if (RT_UNLIKELY(!pszCopy))
            return VERR_NO_TMP_MEMORY;
        memcpy(pszCopy, pszEntry, cchEntry + 1);
        rtManifestNormalizeEntry(pszCopy);

        pEntry = (PRTMANIFESTENTRY)RTStrSpaceGet(&pThis->Entries, pszCopy);
        RTMemTmpFree(pszCopy);
    }

    *ppEntry = pEntry;
    return pEntry ? VINF_SUCCESS : VERR_NOT_FOUND;
}


/**
 * Sets an attribute of a manifest entry.
 *
 * @returns IPRT status code.
 * @param   hManifest           The manifest handle.
 * @param   pszEntry            The entry name.  This will automatically be
 *                              added if there was no previous call to
 *                              RTManifestEntryAdd for this name.  See
 *                              RTManifestEntryAdd for the entry name rules.
 * @param   pszAttr             The attribute name.  If this already exists,
 *                              its value will be replaced.
 * @param   pszValue            The value string.
 * @param   fType               The attribute type, pass
 *                              RTMANIFEST_ATTR_UNKNOWN if not known.
 */
RTDECL(int) RTManifestEntrySetAttr(RTMANIFEST hManifest, const char *pszEntry, const char *pszAttr,
                                   const char *pszValue, uint32_t fType)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    AssertPtr(pszEntry);
    AssertPtr(pszAttr);
    AssertPtr(pszValue);
    AssertReturn(RT_IS_POWER_OF_TWO(fType) && fType < RTMANIFEST_ATTR_END, VERR_INVALID_PARAMETER);

    bool    fNeedNormalization;
    size_t  cchEntry;
    int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
    AssertRCReturn(rc, rc);

    /*
     * Resolve the entry, adding one if necessary.
     */
    PRTMANIFESTENTRY pEntry;
    rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
    if (rc == VERR_NOT_FOUND)
    {
        pEntry = (PRTMANIFESTENTRY)RTMemAlloc(RT_OFFSETOF(RTMANIFESTENTRY, szName[cchEntry + 1]));
        if (!pEntry)
            return VERR_NO_MEMORY;

        pEntry->StrCore.cchString = cchEntry;
        pEntry->StrCore.pszString = pEntry->szName;
        pEntry->Attributes = NULL;
        pEntry->cAttributes = 0;
        memcpy(pEntry->szName, pszEntry, cchEntry + 1);
        if (fNeedNormalization)
            rtManifestNormalizeEntry(pEntry->szName);

        if (!RTStrSpaceInsert(&pThis->Entries, &pEntry->StrCore))
        {
            RTMemFree(pEntry);
            return VERR_INTERNAL_ERROR_4;
        }
        pThis->cEntries++;
    }
    else if (RT_FAILURE(rc))
        return rc;

    return rtManifestSetAttrWorker(pEntry, pszAttr, pszValue, fType);
}


/**
 * Unsets (removes) an attribute of a manifest entry if they both exist.
 *
 * @returns IPRT status code.
 * @retval  VWRN_NOT_FOUND if not found.
 *
 * @param   hManifest           The manifest handle.
 * @param   pszEntry            The entry name.
 * @param   pszAttr             The attribute name.
 */
RTDECL(int) RTManifestEntryUnsetAttr(RTMANIFEST hManifest, const char *pszEntry, const char *pszAttr)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    AssertPtr(pszEntry);
    AssertPtr(pszAttr);

    bool    fNeedNormalization;
    size_t  cchEntry;
    int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
    AssertRCReturn(rc, rc);

    /*
     * Resolve the entry and hand it over to the worker.
     */
    PRTMANIFESTENTRY pEntry;
    rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
    if (RT_SUCCESS(rc))
        rc = rtManifestUnsetAttrWorker(pEntry, pszAttr);
    return rc;
}


RTDECL(int) RTManifestEntryQueryAttr(RTMANIFEST hManifest, const char *pszEntry, const char *pszAttr, uint32_t fType,
                                     char *pszValue, size_t cbValue, uint32_t *pfType)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    AssertPtr(pszEntry);
    AssertPtrNull(pszAttr);
    AssertPtr(pszValue);

    /*
     * Look up the entry.
     */
    bool    fNeedNormalization;
    size_t  cchEntry;
    int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
    AssertRCReturn(rc, rc);

    PRTMANIFESTENTRY pEntry;
    rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
    if (RT_SUCCESS(rc))
        rc = rtManifestQueryAttrWorker(pEntry, pszAttr, fType, pszValue, cbValue, pfType);
    return rc;
}


/**
 * Adds a new entry to a manifest.
 *
 * The entry name rules:
 *     - The entry name can contain any character defined by unicode, except
 *       control characters, ':', '(' and ')'.  The exceptions are mainly there
 *       because of uncertainty around how various formats handles these.
 *     - It is considered case sensitive.
 *     - Forward (unix) and backward (dos) slashes are considered path
 *       separators and converted to forward slashes.
 *
 * @returns IPRT status code.
 * @retval  VWRN_ALREADY_EXISTS if the entry already exists.
 *
 * @param   hManifest           The manifest handle.
 * @param   pszEntry            The entry name (UTF-8).
 *
 * @remarks Some manifest formats will not be able to store an entry without
 *          any attributes.  So, this is just here in case it comes in handy
 *          when dealing with formats which can.
 */
RTDECL(int) RTManifestEntryAdd(RTMANIFEST hManifest, const char *pszEntry)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    AssertPtr(pszEntry);

    bool    fNeedNormalization;
    size_t  cchEntry;
    int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
    AssertRCReturn(rc, rc);

    /*
     * Only add one if it does not already exist.
     */
    PRTMANIFESTENTRY pEntry;
    rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
    if (rc == VERR_NOT_FOUND)
    {
        pEntry = (PRTMANIFESTENTRY)RTMemAlloc(RT_OFFSETOF(RTMANIFESTENTRY, szName[cchEntry + 1]));
        if (pEntry)
        {
            pEntry->StrCore.cchString = cchEntry;
            pEntry->StrCore.pszString = pEntry->szName;
            pEntry->Attributes = NULL;
            pEntry->cAttributes = 0;
            memcpy(pEntry->szName, pszEntry, cchEntry + 1);
            if (fNeedNormalization)
                rtManifestNormalizeEntry(pEntry->szName);

            if (RTStrSpaceInsert(&pThis->Entries, &pEntry->StrCore))
            {
                pThis->cEntries++;
                rc = VINF_SUCCESS;
            }
            else
            {
                RTMemFree(pEntry);
                rc = VERR_INTERNAL_ERROR_4;
            }
        }
        else
            rc = VERR_NO_MEMORY;
    }
    else if (RT_SUCCESS(rc))
        rc = VWRN_ALREADY_EXISTS;

    return rc;
}


/**
 * Removes an entry.
 *
 * @returns IPRT status code.
 * @param   hManifest           The manifest handle.
 * @param   pszEntry            The entry name.
 */
RTDECL(int) RTManifestEntryRemove(RTMANIFEST hManifest, const char *pszEntry)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
    AssertPtr(pszEntry);

    bool    fNeedNormalization;
    size_t  cchEntry;
    int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
    AssertRCReturn(rc, rc);

    /*
     * Look it up before removing it.
     */
    PRTMANIFESTENTRY pEntry;
    rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
    if (RT_SUCCESS(rc))
    {
        PRTSTRSPACECORE pStrCore = RTStrSpaceRemove(&pThis->Entries, pEntry->StrCore.pszString);
        AssertReturn(pStrCore, VERR_INTERNAL_ERROR_3);
        pThis->cEntries--;
        rtManifestDestroyEntry(pStrCore, pThis);
    }

    return rc;
}


RTDECL(bool) RTManifestEntryExists(RTMANIFEST hManifest, const char *pszEntry)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, false);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, false);
    AssertPtr(pszEntry);

    bool    fNeedNormalization;
    size_t  cchEntry;
    int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
    AssertRCReturn(rc, false);

    /*
     * Check if it exists.
     */
    PRTMANIFESTENTRY pEntry;
    rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
    return RT_SUCCESS_NP(rc);
}


/**
 * Reads a line from a VFS I/O stream.
 *
 * @todo    Replace this with a buffered I/O stream layer.
 *
 * @returns IPRT status code.  VERR_EOF when trying to read beyond the stream
 *          end.
 * @param   hVfsIos             The I/O stream to read from.
 * @param   pszLine             Where to store what we've read.
 * @param   cbLine              The number of bytes to read.
 */
static int rtManifestReadLine(RTVFSIOSTREAM hVfsIos, char *pszLine, size_t cbLine)
{
    /* This is horribly slow right now, but it's not a biggy as the input is
       usually cached in memory somewhere... */
    *pszLine = '\0';
    while (cbLine > 1)
    {
        char ch;
        int rc = RTVfsIoStrmRead(hVfsIos, &ch, 1, true /*fBLocking*/, NULL);
        if (RT_FAILURE(rc))
            return rc;

        /* \r\n */
        if (ch == '\r')
        {
            if (cbLine <= 2)
            {
                pszLine[0] = ch;
                pszLine[1] = '\0';
                return VINF_BUFFER_OVERFLOW;
            }

            rc = RTVfsIoStrmRead(hVfsIos, &ch, 1, true /*fBLocking*/, NULL);
            if (RT_SUCCESS(rc) && ch == '\n')
                return VINF_SUCCESS;
            pszLine[0] = '\r';
            pszLine[1] = ch;
            pszLine[2] = '\0';
            if (RT_FAILURE(rc))
                return rc == VERR_EOF ? VINF_EOF : rc;
        }

        /* \n */
        if (ch == '\n')
            return VINF_SUCCESS;

        /* add character. */
        pszLine[0] = ch;
        pszLine[1] = '\0';

        /* advance */
        pszLine++;
        cbLine--;
    }

    return VINF_BUFFER_OVERFLOW;
}


RTDECL(int) RTManifestReadStandardEx(RTMANIFEST hManifest, RTVFSIOSTREAM hVfsIos, char *pszErr, size_t cbErr)
{
    /*
     * Validate input.
     */
    AssertPtrNull(pszErr);
    if (pszErr && cbErr)
        *pszErr = '\0';
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);

    /*
     * Process the stream line by line.
     */
    uint32_t iLine = 0;
    for (;;)
    {
        /*
         * Read a line from the input stream.
         */
        iLine++;
        char szLine[RTPATH_MAX + RTSHA512_DIGEST_LEN + 32];
        int rc = rtManifestReadLine(hVfsIos, szLine, sizeof(szLine));
        if (RT_FAILURE(rc))
        {
            if (rc == VERR_EOF)
                return VINF_SUCCESS;
            RTStrPrintf(pszErr, cbErr, "Error reading line #u: %Rrc", iLine, rc);
            return rc;
        }
        if (rc != VINF_SUCCESS)
        {
            RTStrPrintf(pszErr, cbErr, "Line number %u is too long", iLine);
            return VERR_OUT_OF_RANGE;
        }

        /*
         * Strip it and skip if empty.
         */
        char *psz = RTStrStrip(szLine);
        if (!*psz)
            continue;

        /*
         * Read the attribute name.
         */
        const char * const pszAttr = psz;
        do
            psz++;
        while (!RT_C_IS_BLANK(*psz) && *psz);
        if (*psz)
            *psz++ = '\0';

        /*
         * The entry name is enclosed in parenthesis and followed by a '='.
         */
        psz = RTStrStripL(psz);
        if (*psz != '(')
        {
            RTStrPrintf(pszErr, cbErr, "Expected '(' after %zu on line %u", psz - szLine, iLine);
            return VERR_PARSE_ERROR;
        }
        const char * const pszName = ++psz;
        while (*psz)
        {
            if (*psz == ')')
            {
                char *psz2 = RTStrStripL(psz + 1);
                if (*psz2 == '=')
                {
                    *psz = '\0';
                    psz = psz2;
                    break;
                }
            }
            psz++;
        }

        if (*psz != '=')
        {
            RTStrPrintf(pszErr, cbErr, "Expected ')=' at %zu on line %u", psz - szLine, iLine);
            return VERR_PARSE_ERROR;
        }

        /*
         * The value.
         */
        psz = RTStrStrip(psz + 1);
        const char * const pszValue = psz;
        if (!*psz)
        {
            RTStrPrintf(pszErr, cbErr, "Expected value at %zu on line %u", psz - szLine, iLine);
            return VERR_PARSE_ERROR;
        }

        /*
         * Detect attribute type and sanity check the value.
         */
        uint32_t fType = RTMANIFEST_ATTR_UNKNOWN;
        static const struct
        {
            const char *pszAttr;
            uint32_t    fType;
            unsigned    cBits;
            unsigned    uBase;
        } s_aDecAttrs[] =
        {
            { "SIZE", RTMANIFEST_ATTR_SIZE, 64,  10}
        };
        for (unsigned i = 0; i < RT_ELEMENTS(s_aDecAttrs); i++)
            if (!strcmp(s_aDecAttrs[i].pszAttr, pszAttr))
            {
                fType = s_aDecAttrs[i].fType;
                rc = RTStrToUInt64Full(pszValue, s_aDecAttrs[i].uBase, NULL);
                if (rc != VINF_SUCCESS)
                {
                    RTStrPrintf(pszErr, cbErr, "Malformed value ('%s') at %zu on line %u: %Rrc", pszValue, psz - szLine, iLine, rc);
                    return VERR_PARSE_ERROR;
                }
                break;
            }

        if (fType == RTMANIFEST_ATTR_UNKNOWN)
        {
            static const struct
            {
                const char *pszAttr;
                uint32_t    fType;
                unsigned    cchHex;
            } s_aHexAttrs[] =
            {
                { "MD5",        RTMANIFEST_ATTR_MD5,        RTMD5_DIGEST_LEN    },
                { "SHA1",       RTMANIFEST_ATTR_SHA1,       RTSHA1_DIGEST_LEN   },
                { "SHA256",     RTMANIFEST_ATTR_SHA256,     RTSHA256_DIGEST_LEN },
                { "SHA512",     RTMANIFEST_ATTR_SHA512,     RTSHA512_DIGEST_LEN }
            };
            for (unsigned i = 0; i < RT_ELEMENTS(s_aHexAttrs); i++)
                if (!strcmp(s_aHexAttrs[i].pszAttr, pszAttr))
                {
                    fType = s_aHexAttrs[i].fType;
                    for (unsigned off = 0; off < s_aHexAttrs[i].cchHex; off++)
                        if (!RT_C_IS_XDIGIT(pszValue[off]))
                        {
                            RTStrPrintf(pszErr, cbErr, "Expected hex digit at %zu on line %u (value '%s', pos %u)",
                                        pszValue - szLine + off, iLine, pszValue, off);
                            return VERR_PARSE_ERROR;
                        }
                    break;
                }
        }

        /*
         * Finally, add it.
         */
        rc = RTManifestEntrySetAttr(hManifest, pszName, pszAttr, pszValue, fType);
        if (RT_FAILURE(rc))
        {
            RTStrPrintf(pszErr, cbErr, "RTManifestEntrySetAttr(,'%s','%s', '%s', %#x) failed on line %u: %Rrc",
                        pszName, pszAttr, pszValue, fType, iLine, rc);
            return rc;
        }
    }
}

RTDECL(int) RTManifestReadStandard(RTMANIFEST hManifest, RTVFSIOSTREAM hVfsIos)
{
    return RTManifestReadStandardEx(hManifest, hVfsIos, NULL, 0);
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Writes RTMANIFESTATTR.}
 */
static DECLCALLBACK(int) rtManifestWriteStdAttr(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTATTR         pAttr = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);
    RTMANIFESTWRITESTDATTR *pArgs = (RTMANIFESTWRITESTDATTR *)pvUser;
    char    szLine[RTPATH_MAX + RTSHA512_DIGEST_LEN + 32];
    size_t  cchLine = RTStrPrintf(szLine, sizeof(szLine), "%s (%s) = %s\n", pAttr->szName, pArgs->pszEntry, pAttr->pszValue);
    if (cchLine >= sizeof(szLine) - 1)
        return VERR_BUFFER_OVERFLOW;
    return RTVfsIoStrmWrite(pArgs->hVfsIos, szLine, cchLine, true /*fBlocking*/, NULL);
}


/**
 * @callback_method_impl{FNRTSTRSPACECALLBACK, Writes RTMANIFESTENTRY.}
 */
static DECLCALLBACK(int) rtManifestWriteStdEntry(PRTSTRSPACECORE pStr, void *pvUser)
{
    PRTMANIFESTENTRY pEntry = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);

    RTMANIFESTWRITESTDATTR Args;
    Args.hVfsIos  = (RTVFSIOSTREAM)pvUser;
    Args.pszEntry = pStr->pszString;
    return RTStrSpaceEnumerate(&pEntry->Attributes, rtManifestWriteStdAttr, &Args);
}


RTDECL(int) RTManifestWriteStandard(RTMANIFEST hManifest, RTVFSIOSTREAM hVfsIos)
{
    RTMANIFESTINT *pThis = hManifest;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);

    RTMANIFESTWRITESTDATTR Args;
    Args.hVfsIos  = hVfsIos;
    Args.pszEntry = "main";
    int rc = RTStrSpaceEnumerate(&pThis->SelfEntry.Attributes, rtManifestWriteStdAttr, &Args);
    if (RT_SUCCESS(rc))
        rc = RTStrSpaceEnumerate(&pThis->Entries, rtManifestWriteStdEntry, hVfsIos);
    return rc;
}

