/* $Id: xarvfs.cpp 62477 2016-07-22 18:27:37Z vboxsync $ */ /** @file * IPRT - XAR Virtual Filesystem. */ /* * Copyright (C) 2010-2016 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 #include #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** @name Hash state * @{ */ #define RTZIPXAR_HASH_PENDING 0 #define RTZIPXAR_HASH_OK 1 #define RTZIPXAR_HASH_FAILED_ARCHIVED 2 #define RTZIPXAR_HASH_FAILED_EXTRACTED 3 /** @} */ /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Hash digest value union for the supported XAR hash functions. * @todo This could be generalized in iprt/checksum.h or somewhere. */ typedef union RTZIPXARHASHDIGEST { uint8_t abMd5[RTMD5_HASH_SIZE]; uint8_t abSha1[RTSHA1_HASH_SIZE]; } RTZIPXARHASHDIGEST; /** Pointer to a XAR hash digest union. */ typedef RTZIPXARHASHDIGEST *PRTZIPXARHASHDIGEST; /** Pointer to a const XAR hash digest union. */ typedef RTZIPXARHASHDIGEST *PCRTZIPXARHASHDIGEST; /** * Hash context union. */ typedef union RTZIPXARHASHCTX { RTMD5CONTEXT Md5; RTSHA1CONTEXT Sha1; } RTZIPXARHASHCTX; /** Pointer to a hash context union. */ typedef RTZIPXARHASHCTX *PRTZIPXARHASHCTX; /** * XAR reader instance data. */ typedef struct RTZIPXARREADER { /** The TOC XML element. */ xml::ElementNode const *pToc; /** The TOC XML document. */ xml::Document *pDoc; /** The current file. */ xml::ElementNode const *pCurFile; /** The depth of the current file, with 0 being the root level. */ uint32_t cCurDepth; } RTZIPXARREADER; /** Pointer to the XAR reader instance data. */ typedef RTZIPXARREADER *PRTZIPXARREADER; /** * Xar directory, character device, block device, fifo socket or symbolic link. */ typedef struct RTZIPXARBASEOBJ { /** The file TOC element. */ xml::ElementNode const *pFileElem; /** RTFS_TYPE_XXX value for the object. */ RTFMODE fModeType; } RTZIPXARBASEOBJ; /** Pointer to a XAR filesystem stream base object. */ typedef RTZIPXARBASEOBJ *PRTZIPXARBASEOBJ; /** * XAR data encoding. */ typedef enum RTZIPXARENCODING { RTZIPXARENCODING_INVALID = 0, RTZIPXARENCODING_STORE, RTZIPXARENCODING_GZIP, RTZIPXARENCODING_UNSUPPORTED, RTZIPXARENCODING_END } RTZIPXARENCODING; /** * Data stream attributes. */ typedef struct RTZIPXARDATASTREAM { /** Offset of the data in the stream. * @remarks The I/O stream and file constructor will adjust this so that it * relative to the start of the input stream, instead of the first byte * after the TOC. */ RTFOFF offData; /** The size of the archived data. */ RTFOFF cbDataArchived; /** The size of the extracted data. */ RTFOFF cbDataExtracted; /** The encoding of the archived ata. */ RTZIPXARENCODING enmEncoding; /** The hash function used for the archived data. */ uint8_t uHashFunArchived; /** The hash function used for the extracted data. */ uint8_t uHashFunExtracted; /** The digest of the archived data. */ RTZIPXARHASHDIGEST DigestArchived; /** The digest of the extracted data. */ RTZIPXARHASHDIGEST DigestExtracted; } RTZIPXARDATASTREAM; /** Pointer to XAR data stream attributes. */ typedef RTZIPXARDATASTREAM *PRTZIPXARDATASTREAM; /** * Xar file represented as a VFS I/O stream. */ typedef struct RTZIPXARIOSTREAM { /** The basic XAR object data. */ RTZIPXARBASEOBJ BaseObj; /** The attributes of the primary data stream. */ RTZIPXARDATASTREAM DataAttr; /** The current file position in the archived file. */ RTFOFF offCurPos; /** The input I/O stream. */ RTVFSIOSTREAM hVfsIos; /** Set if we've reached the end of the file or if the next object in the * file system stream has been requested. */ bool fEndOfStream; /** Whether the stream is seekable. */ bool fSeekable; /** Hash state. */ uint8_t uHashState; /** The size of the file that we've currently hashed. * We use this to check whether the user skips part of the file while reading * and when to compare the digests. */ RTFOFF cbDigested; /** The digest of the archived data. */ RTZIPXARHASHCTX CtxArchived; /** The digest of the extracted data. */ RTZIPXARHASHCTX CtxExtracted; } RTZIPXARIOSTREAM; /** Pointer to a the private data of a XAR file I/O stream. */ typedef RTZIPXARIOSTREAM *PRTZIPXARIOSTREAM; /** * Xar file represented as a VFS file. */ typedef struct RTZIPXARFILE { /** The XAR I/O stream data. */ RTZIPXARIOSTREAM Ios; /** The input file. */ RTVFSFILE hVfsFile; } RTZIPXARFILE; /** Pointer to the private data of a XAR file. */ typedef RTZIPXARFILE *PRTZIPXARFILE; /** * Decompressed I/O stream instance. * * This is just a front that checks digests and other sanity stuff. */ typedef struct RTZIPXARDECOMPIOS { /** The decompressor I/O stream. */ RTVFSIOSTREAM hVfsIosDecompressor; /** The raw XAR I/O stream. */ RTVFSIOSTREAM hVfsIosRaw; /** Pointer to the raw XAR I/O stream instance data. */ PRTZIPXARIOSTREAM pIosRaw; /** The current file position in the archived file. */ RTFOFF offCurPos; /** The hash function to use on the extracted data. */ uint8_t uHashFunExtracted; /** Hash state on the extracted data. */ uint8_t uHashState; /** The digest of the extracted data. */ RTZIPXARHASHCTX CtxExtracted; /** The expected digest of the extracted data. */ RTZIPXARHASHDIGEST DigestExtracted; } RTZIPXARDECOMPIOS; /** Pointer to the private data of a XAR decompressed I/O stream. */ typedef RTZIPXARDECOMPIOS *PRTZIPXARDECOMPIOS; /** * Xar filesystem stream private data. */ typedef struct RTZIPXARFSSTREAM { /** The input I/O stream. */ RTVFSIOSTREAM hVfsIos; /** The input file, if the stream is actually a file. */ RTVFSFILE hVfsFile; /** The start offset in the input I/O stream. */ RTFOFF offStart; /** The zero offset in the file which all others are relative to. */ RTFOFF offZero; /** The hash function we're using (XAR_HASH_XXX). */ uint8_t uHashFunction; /** The size of the digest produced by the hash function we're using. */ uint8_t cbHashDigest; /** Set if we've reached the end of the stream. */ bool fEndOfStream; /** Set if we've encountered a fatal error. */ int rcFatal; /** The XAR reader instance data. */ RTZIPXARREADER XarReader; } RTZIPXARFSSTREAM; /** Pointer to a the private data of a XAR filesystem stream. */ typedef RTZIPXARFSSTREAM *PRTZIPXARFSSTREAM; /** * Hashes a block of data. * * @param uHashFunction The hash function to use. * @param pvSrc The data to hash. * @param cbSrc The size of the data to hash. * @param pHashDigest Where to return the message digest. */ static void rtZipXarCalcHash(uint32_t uHashFunction, void const *pvSrc, size_t cbSrc, PRTZIPXARHASHDIGEST pHashDigest) { switch (uHashFunction) { case XAR_HASH_SHA1: RTSha1(pvSrc, cbSrc, pHashDigest->abSha1); break; case XAR_HASH_MD5: RTMd5(pvSrc, cbSrc, pHashDigest->abMd5); break; default: RT_ZERO(*pHashDigest); break; } } /** * Initializes a hash context. * * @param pCtx Pointer to the context union. * @param uHashFunction The hash function to use. */ static void rtZipXarHashInit(PRTZIPXARHASHCTX pCtx, uint32_t uHashFunction) { switch (uHashFunction) { case XAR_HASH_SHA1: RTSha1Init(&pCtx->Sha1); break; case XAR_HASH_MD5: RTMd5Init(&pCtx->Md5);; break; default: RT_ZERO(*pCtx); break; } } /** * Adds a block to the hash calculation. * * @param pCtx Pointer to the context union. * @param uHashFunction The hash function to use. * @param pvSrc The data to add to the hash. * @param cbSrc The size of the data. */ static void rtZipXarHashUpdate(PRTZIPXARHASHCTX pCtx, uint32_t uHashFunction, void const *pvSrc, size_t cbSrc) { switch (uHashFunction) { case XAR_HASH_SHA1: RTSha1Update(&pCtx->Sha1, pvSrc, cbSrc); break; case XAR_HASH_MD5: RTMd5Update(&pCtx->Md5, pvSrc, cbSrc); break; } } /** * Finalizes the hash, producing the message digest. * * @param pCtx Pointer to the context union. * @param uHashFunction The hash function to use. * @param pHashDigest Where to return the message digest. */ static void rtZipXarHashFinal(PRTZIPXARHASHCTX pCtx, uint32_t uHashFunction, PRTZIPXARHASHDIGEST pHashDigest) { switch (uHashFunction) { case XAR_HASH_SHA1: RTSha1Final(&pCtx->Sha1, pHashDigest->abSha1); break; case XAR_HASH_MD5: RTMd5Final(pHashDigest->abMd5, &pCtx->Md5); break; default: RT_ZERO(*pHashDigest); break; } } /** * Compares two hash digests. * * @returns true if equal, false if not. * @param uHashFunction The hash function to use. * @param pHashDigest1 The first hash digest. * @param pHashDigest2 The second hash digest. */ static bool rtZipXarHashIsEqual(uint32_t uHashFunction, PRTZIPXARHASHDIGEST pHashDigest1, PRTZIPXARHASHDIGEST pHashDigest2) { switch (uHashFunction) { case XAR_HASH_SHA1: return memcmp(pHashDigest1->abSha1, pHashDigest2->abSha1, sizeof(pHashDigest1->abSha1)) == 0; case XAR_HASH_MD5: return memcmp(pHashDigest1->abMd5, pHashDigest2->abMd5, sizeof(pHashDigest1->abMd5)) == 0; default: return true; } } /** * Gets the 'offset', 'size' and optionally 'length' sub elements. * * @returns IPRT status code. * @param pElement The parent element. * @param poff Where to return the offset value. * @param pcbSize Where to return the size value. * @param pcbLength Where to return the length value, optional. */ static int rtZipXarGetOffsetSizeLengthFromElem(xml::ElementNode const *pElement, PRTFOFF poff, PRTFOFF pcbSize, PRTFOFF pcbLength) { /* * The offset. */ xml::ElementNode const *pElem = pElement->findChildElement("offset"); if (!pElem) return VERR_XAR_MISSING_OFFSET_ELEMENT; const char *pszValue = pElem->getValue(); if (!pszValue) return VERR_XAR_BAD_OFFSET_ELEMENT; int rc = RTStrToInt64Full(pszValue, 0, poff); if ( RT_FAILURE(rc) || rc == VWRN_NUMBER_TOO_BIG || *poff > RTFOFF_MAX / 2 /* make sure to not overflow calculating offsets. */ || *poff < 0) return VERR_XAR_BAD_OFFSET_ELEMENT; /* * The 'size' stored in the archive. */ pElem = pElement->findChildElement("size"); if (!pElem) return VERR_XAR_MISSING_SIZE_ELEMENT; pszValue = pElem->getValue(); if (!pszValue) return VERR_XAR_BAD_SIZE_ELEMENT; rc = RTStrToInt64Full(pszValue, 0, pcbSize); if ( RT_FAILURE(rc) || rc == VWRN_NUMBER_TOO_BIG || *pcbSize >= RTFOFF_MAX - _1M || *pcbSize < 0) return VERR_XAR_BAD_SIZE_ELEMENT; AssertCompile(RTFOFF_MAX == UINT64_MAX / 2); /* * The 'length' of the uncompressed data. Not present for checksums, so * the caller might not want it. */ if (pcbLength) { pElem = pElement->findChildElement("length"); if (!pElem) return VERR_XAR_MISSING_LENGTH_ELEMENT; pszValue = pElem->getValue(); if (!pszValue) return VERR_XAR_BAD_LENGTH_ELEMENT; rc = RTStrToInt64Full(pszValue, 0, pcbLength); if ( RT_FAILURE(rc) || rc == VWRN_NUMBER_TOO_BIG || *pcbLength >= RTFOFF_MAX - _1M || *pcbLength < 0) return VERR_XAR_BAD_LENGTH_ELEMENT; AssertCompile(RTFOFF_MAX == UINT64_MAX / 2); } return VINF_SUCCESS; } /** * Convers a checksum style value into a XAR hash function number. * * @returns IPRT status code. * @param pszStyle The XAR checksum style. * @param puHashFunction Where to return the hash function number on success. */ static int rtZipXarParseChecksumStyle(const char *pszStyle, uint8_t *puHashFunction) { size_t cchStyle = strlen(pszStyle); if ( cchStyle == 4 && (pszStyle[0] == 's' || pszStyle[0] == 'S') && (pszStyle[1] == 'h' || pszStyle[1] == 'H') && (pszStyle[2] == 'a' || pszStyle[2] == 'A') && pszStyle[3] == '1' ) *puHashFunction = XAR_HASH_SHA1; else if ( cchStyle == 3 && (pszStyle[0] == 'm' || pszStyle[0] == 'M') && (pszStyle[1] == 'd' || pszStyle[1] == 'D') && pszStyle[2] == '5' ) *puHashFunction = XAR_HASH_MD5; else if ( cchStyle == 4 && (pszStyle[0] == 'n' || pszStyle[0] == 'N') && (pszStyle[1] == 'o' || pszStyle[1] == 'O') && (pszStyle[2] == 'n' || pszStyle[2] == 'N') && (pszStyle[3] == 'e' || pszStyle[3] == 'E') ) *puHashFunction = XAR_HASH_NONE; else { *puHashFunction = UINT8_MAX; return VERR_XAR_BAD_CHECKSUM_ELEMENT; } return VINF_SUCCESS; } /** * Parses a checksum element typically found under 'data'. * * @returns IPRT status code. * @param pParentElem The parent element ('data'). * @param pszName The name of the element, like 'checksum-archived' or * 'checksum-extracted'. * @param puHashFunction Where to return the XAR hash function number. * @param pDigest Where to return the expected message digest. */ static int rtZipXarParseChecksumElem(xml::ElementNode const *pParentElem, const char *pszName, uint8_t *puHashFunction, PRTZIPXARHASHDIGEST pDigest) { /* Default is no checksum. */ *puHashFunction = XAR_HASH_NONE; RT_ZERO(*pDigest); xml::ElementNode const *pChecksumElem = pParentElem->findChildElement(pszName); if (!pChecksumElem) return VINF_SUCCESS; /* The style. */ const char *pszStyle = pChecksumElem->findAttributeValue("style"); if (!pszStyle) return VERR_XAR_BAD_CHECKSUM_ELEMENT; int rc = rtZipXarParseChecksumStyle(pszStyle, puHashFunction); if (RT_FAILURE(rc)) return rc; if (*puHashFunction == XAR_HASH_NONE) return VINF_SUCCESS; /* The digest. */ const char *pszDigest = pChecksumElem->getValue(); if (!pszDigest) return VERR_XAR_BAD_CHECKSUM_ELEMENT; switch (*puHashFunction) { case XAR_HASH_SHA1: rc = RTSha1FromString(pszDigest, pDigest->abSha1); break; case XAR_HASH_MD5: rc = RTMd5FromString(pszDigest, pDigest->abMd5); break; default: rc = VERR_INTERNAL_ERROR_2; } return rc; } /** * Gets all the attributes of the primary data stream. * * @returns IPRT status code. * @param pFileElem The file element, we'll be parsing the 'data' * sub element of this. * @param pDataAttr Where to return the attributes. */ static int rtZipXarGetDataStreamAttributes(xml::ElementNode const *pFileElem, PRTZIPXARDATASTREAM pDataAttr) { /* * Get the data element. */ xml::ElementNode const *pDataElem = pFileElem->findChildElement("data"); if (!pDataElem) return VERR_XAR_MISSING_DATA_ELEMENT; /* * Checksums. */ int rc = rtZipXarParseChecksumElem(pDataElem, "extracted-checksum", &pDataAttr->uHashFunExtracted, &pDataAttr->DigestExtracted); if (RT_FAILURE(rc)) return rc; rc = rtZipXarParseChecksumElem(pDataElem, "archived-checksum", &pDataAttr->uHashFunArchived, &pDataAttr->DigestArchived); if (RT_FAILURE(rc)) return rc; /* * The encoding. */ const char *pszEncoding = pDataElem->findChildElementAttributeValueP("encoding", "style"); if (!pszEncoding) return VERR_XAR_NO_ENCODING; if (!strcmp(pszEncoding, "application/octet-stream")) pDataAttr->enmEncoding = RTZIPXARENCODING_STORE; else if (!strcmp(pszEncoding, "application/x-gzip")) pDataAttr->enmEncoding = RTZIPXARENCODING_GZIP; else pDataAttr->enmEncoding = RTZIPXARENCODING_UNSUPPORTED; /* * The data offset and the compressed and uncompressed sizes. */ rc = rtZipXarGetOffsetSizeLengthFromElem(pDataElem, &pDataAttr->offData, &pDataAttr->cbDataExtracted, &pDataAttr->cbDataArchived); if (RT_FAILURE(rc)) return rc; /* No zero padding or other alignment crap, please. */ if ( pDataAttr->enmEncoding == RTZIPXARENCODING_STORE && pDataAttr->cbDataExtracted != pDataAttr->cbDataArchived) return VERR_XAR_ARCHIVED_AND_EXTRACTED_SIZES_MISMATCH; return VINF_SUCCESS; } /** * Parses a timestamp. * * We consider all timestamps optional, and will only fail (return @c false) on * parse errors. If the specified element isn't found, we'll return epoc time. * * @returns boolean success indicator. * @param pParent The parent element (typically 'file'). * @param pszChild The name of the child element. * @param pTimeSpec Where to return the timespec on success. */ static bool rtZipXarParseTimestamp(const xml::ElementNode *pParent, const char *pszChild, PRTTIMESPEC pTimeSpec) { const char *pszValue = pParent->findChildElementValueP(pszChild); if (pszValue) { if (RTTimeSpecFromString(pTimeSpec, pszValue)) return true; return false; } RTTimeSpecSetNano(pTimeSpec, 0); return true; } /** * Gets the next file element in the TOC. * * @returns Pointer to the next file, NULL if we've reached the end. * @param pCurFile The current element. * @param pcCurDepth Depth gauge we update when decending and * acending thru the tree. */ static xml::ElementNode const *rtZipXarGetNextFileElement(xml::ElementNode const *pCurFile, uint32_t *pcCurDepth) { /* * Consider children first. */ xml::ElementNode const *pChild = pCurFile->findChildElement("file"); if (pChild) { *pcCurDepth += 1; return pChild; } /* * Siblings and ancestor siblings. */ for (;;) { xml::ElementNode const *pSibling = pCurFile->findNextSibilingElement("file"); if (pSibling != NULL) return pSibling; if (*pcCurDepth == 0) break; *pcCurDepth -= 1; pCurFile = static_cast(pCurFile->getParent()); AssertBreak(pCurFile); Assert(pCurFile->nameEquals("file")); } return NULL; } /* * * T h e V F S F i l e s y s t e m S t r e a m B i t s. * T h e V F S F i l e s y s t e m S t r e a m B i t s. * T h e V F S F i l e s y s t e m S t r e a m B i t s. * */ /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtZipXarFssBaseObj_Close(void *pvThis) { PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis; /* Currently there is nothing we really have to do here. */ NOREF(pThis); return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtZipXarFssBaseObj_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis; /* * Get the common data. */ /* Sizes. */ if (pThis->fModeType == RTFS_TYPE_FILE) { PRTZIPXARIOSTREAM pThisIos = RT_FROM_MEMBER(pThis, RTZIPXARIOSTREAM, BaseObj); pObjInfo->cbObject = pThisIos->DataAttr.cbDataArchived; /* Modified by decomp ios. */ pObjInfo->cbAllocated = pThisIos->DataAttr.cbDataArchived; } else { pObjInfo->cbObject = 0; pObjInfo->cbAllocated = 0; } /* The file mode. */ if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("mode", 0755, &pObjInfo->Attr.fMode))) return VERR_XAR_BAD_FILE_MODE; if (pObjInfo->Attr.fMode & RTFS_TYPE_MASK) return VERR_XAR_BAD_FILE_MODE; pObjInfo->Attr.fMode &= RTFS_UNIX_MASK & ~RTFS_TYPE_MASK; pObjInfo->Attr.fMode |= pThis->fModeType; /* File times. */ if (RT_UNLIKELY(!rtZipXarParseTimestamp(pThis->pFileElem, "atime", &pObjInfo->AccessTime))) return VERR_XAR_BAD_FILE_TIMESTAMP; if (RT_UNLIKELY(!rtZipXarParseTimestamp(pThis->pFileElem, "ctime", &pObjInfo->ChangeTime))) return VERR_XAR_BAD_FILE_TIMESTAMP; if (RT_UNLIKELY(!rtZipXarParseTimestamp(pThis->pFileElem, "mtime", &pObjInfo->ModificationTime))) return VERR_XAR_BAD_FILE_TIMESTAMP; pObjInfo->BirthTime = RTTimeSpecGetNano(&pObjInfo->AccessTime) <= RTTimeSpecGetNano(&pObjInfo->ChangeTime) ? pObjInfo->AccessTime : pObjInfo->ChangeTime; if (RTTimeSpecGetNano(&pObjInfo->BirthTime) > RTTimeSpecGetNano(&pObjInfo->ModificationTime)) pObjInfo->BirthTime = pObjInfo->ModificationTime; /* * Copy the desired data. */ switch (enmAddAttr) { case RTFSOBJATTRADD_NOTHING: case RTFSOBJATTRADD_UNIX: pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX; if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("uid", 0, &pObjInfo->Attr.u.Unix.uid))) return VERR_XAR_BAD_FILE_UID; if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("gid", 0, &pObjInfo->Attr.u.Unix.gid))) return VERR_XAR_BAD_FILE_GID; if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("deviceno", 0, &pObjInfo->Attr.u.Unix.INodeIdDevice))) return VERR_XAR_BAD_FILE_DEVICE_NO; if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("inode", 0, &pObjInfo->Attr.u.Unix.INodeId))) return VERR_XAR_BAD_FILE_INODE; pObjInfo->Attr.u.Unix.cHardlinks = 1; pObjInfo->Attr.u.Unix.fFlags = 0; pObjInfo->Attr.u.Unix.GenerationId = 0; pObjInfo->Attr.u.Unix.Device = 0; break; case RTFSOBJATTRADD_UNIX_OWNER: { pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER; if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("uid", 0, &pObjInfo->Attr.u.Unix.uid))) return VERR_XAR_BAD_FILE_UID; const char *pszUser = pThis->pFileElem->findChildElementValueP("user"); if (pszUser) RTStrCopy(pObjInfo->Attr.u.UnixOwner.szName, sizeof(pObjInfo->Attr.u.UnixOwner.szName), pszUser); else pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; break; } case RTFSOBJATTRADD_UNIX_GROUP: { pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP; if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("gid", 0, &pObjInfo->Attr.u.Unix.gid))) return VERR_XAR_BAD_FILE_GID; const char *pszGroup = pThis->pFileElem->findChildElementValueP("group"); if (pszGroup) RTStrCopy(pObjInfo->Attr.u.UnixGroup.szName, sizeof(pObjInfo->Attr.u.UnixGroup.szName), pszGroup); else pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; break; } case RTFSOBJATTRADD_EASIZE: pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE; RT_ZERO(pObjInfo->Attr.u); break; default: return VERR_NOT_SUPPORTED; } return VINF_SUCCESS; } /** * Xar filesystem base object operations. */ static const RTVFSOBJOPS g_rtZipXarFssBaseObjOps = { RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_BASE, "XarFsStream::Obj", rtZipXarFssBaseObj_Close, rtZipXarFssBaseObj_QueryInfo, RTVFSOBJOPS_VERSION }; /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtZipXarFssIos_Close(void *pvThis) { PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis; RTVfsIoStrmRelease(pThis->hVfsIos); pThis->hVfsIos = NIL_RTVFSIOSTREAM; return rtZipXarFssBaseObj_Close(&pThis->BaseObj); } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtZipXarFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis; return rtZipXarFssBaseObj_QueryInfo(&pThis->BaseObj, pObjInfo, enmAddAttr); } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} */ static DECLCALLBACK(int) rtZipXarFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) { PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis; AssertReturn(off >= -1, VERR_INVALID_PARAMETER); AssertReturn(pSgBuf->cSegs == 1, VERR_INVALID_PARAMETER); /* * Fend of reads beyond the end of the stream here. If */ if (off == -1) off = pThis->offCurPos; if (off < 0 || off > pThis->DataAttr.cbDataArchived) return VERR_EOF; if (pThis->fEndOfStream) { if (off >= pThis->DataAttr.cbDataArchived) return pcbRead ? VINF_EOF : VERR_EOF; if (!pThis->fSeekable) return VERR_SEEK_ON_DEVICE; pThis->fEndOfStream = false; } size_t cbToRead = pSgBuf->paSegs[0].cbSeg; uint64_t cbLeft = pThis->DataAttr.cbDataArchived - off; if (cbToRead > cbLeft) { if (!pcbRead) return VERR_EOF; cbToRead = (size_t)cbLeft; } /* * Do the reading. */ size_t cbReadStack = 0; if (!pcbRead) pcbRead = &cbReadStack; int rc = RTVfsIoStrmReadAt(pThis->hVfsIos, off + pThis->DataAttr.offData, pSgBuf->paSegs[0].pvSeg, cbToRead, fBlocking, pcbRead); /* Feed the hashes. */ size_t cbActuallyRead = *pcbRead; if (pThis->uHashState == RTZIPXAR_HASH_PENDING) { if (pThis->offCurPos == pThis->cbDigested) { rtZipXarHashUpdate(&pThis->CtxArchived, pThis->DataAttr.uHashFunArchived, pSgBuf->paSegs[0].pvSeg, cbActuallyRead); rtZipXarHashUpdate(&pThis->CtxExtracted, pThis->DataAttr.uHashFunExtracted, pSgBuf->paSegs[0].pvSeg, cbActuallyRead); pThis->cbDigested += cbActuallyRead; } else if ( pThis->cbDigested > pThis->offCurPos && pThis->cbDigested < (RTFOFF)(pThis->offCurPos + cbActuallyRead)) { size_t offHash = pThis->cbDigested - pThis->offCurPos; void const *pvHash = (uint8_t const *)pSgBuf->paSegs[0].pvSeg + offHash; size_t cbHash = cbActuallyRead - offHash; rtZipXarHashUpdate(&pThis->CtxArchived, pThis->DataAttr.uHashFunArchived, pvHash, cbHash); rtZipXarHashUpdate(&pThis->CtxExtracted, pThis->DataAttr.uHashFunExtracted, pvHash, cbHash); pThis->cbDigested += cbHash; } } /* Update the file position. */ pThis->offCurPos += cbActuallyRead; /* * Check for end of stream, also check the hash. */ if (pThis->offCurPos >= pThis->DataAttr.cbDataArchived) { Assert(pThis->offCurPos == pThis->DataAttr.cbDataArchived); pThis->fEndOfStream = true; /* Check hash. */ if ( pThis->uHashState == RTZIPXAR_HASH_PENDING && pThis->cbDigested == pThis->DataAttr.cbDataArchived) { RTZIPXARHASHDIGEST Digest; rtZipXarHashFinal(&pThis->CtxArchived, pThis->DataAttr.uHashFunArchived, &Digest); if (rtZipXarHashIsEqual(pThis->DataAttr.uHashFunArchived, &Digest, &pThis->DataAttr.DigestArchived)) { rtZipXarHashFinal(&pThis->CtxExtracted, pThis->DataAttr.uHashFunExtracted, &Digest); if (rtZipXarHashIsEqual(pThis->DataAttr.uHashFunExtracted, &Digest, &pThis->DataAttr.DigestExtracted)) pThis->uHashState = RTZIPXAR_HASH_OK; else { pThis->uHashState = RTZIPXAR_HASH_FAILED_EXTRACTED; rc = VERR_XAR_EXTRACTED_HASH_MISMATCH; } } else { pThis->uHashState = RTZIPXAR_HASH_FAILED_ARCHIVED; rc = VERR_XAR_ARCHIVED_HASH_MISMATCH; } } else if (pThis->uHashState == RTZIPXAR_HASH_FAILED_ARCHIVED) rc = VERR_XAR_ARCHIVED_HASH_MISMATCH; else if (pThis->uHashState == RTZIPXAR_HASH_FAILED_EXTRACTED) rc = VERR_XAR_EXTRACTED_HASH_MISMATCH; } return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite} */ static DECLCALLBACK(int) rtZipXarFssIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) { /* Cannot write to a read-only I/O stream. */ NOREF(pvThis); NOREF(off); NOREF(pSgBuf); NOREF(fBlocking); NOREF(pcbWritten); return VERR_ACCESS_DENIED; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} */ static DECLCALLBACK(int) rtZipXarFssIos_Flush(void *pvThis) { /* It's a read only stream, nothing dirty to flush. */ NOREF(pvThis); return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne} */ static DECLCALLBACK(int) rtZipXarFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents) { PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis; /* When we've reached the end, read will be set to indicate it. */ if ( (fEvents & RTPOLL_EVT_READ) && pThis->fEndOfStream) { int rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, 0, fIntr, pfRetEvents); if (RT_SUCCESS(rc)) *pfRetEvents |= RTPOLL_EVT_READ; else *pfRetEvents = RTPOLL_EVT_READ; return VINF_SUCCESS; } return RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents); } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} */ static DECLCALLBACK(int) rtZipXarFssIos_Tell(void *pvThis, PRTFOFF poffActual) { PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis; *poffActual = pThis->offCurPos; return VINF_SUCCESS; } /** * Xar I/O stream operations. */ static const RTVFSIOSTREAMOPS g_rtZipXarFssIosOps = { { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_IO_STREAM, "XarFsStream::IoStream", rtZipXarFssIos_Close, rtZipXarFssIos_QueryInfo, RTVFSOBJOPS_VERSION }, RTVFSIOSTREAMOPS_VERSION, 0, rtZipXarFssIos_Read, rtZipXarFssIos_Write, rtZipXarFssIos_Flush, rtZipXarFssIos_PollOne, rtZipXarFssIos_Tell, NULL /*Skip*/, NULL /*ZeroFill*/, RTVFSIOSTREAMOPS_VERSION }; /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtZipXarFssFile_Close(void *pvThis) { PRTZIPXARFILE pThis = (PRTZIPXARFILE)pvThis; RTVfsFileRelease(pThis->hVfsFile); pThis->hVfsFile = NIL_RTVFSFILE; return rtZipXarFssIos_Close(&pThis->Ios); } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} */ static DECLCALLBACK(int) rtZipXarFssFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) { NOREF(pvThis); NOREF(fMode); NOREF(fMask); return VERR_NOT_SUPPORTED; } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} */ static DECLCALLBACK(int) rtZipXarFssFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) { NOREF(pvThis); NOREF(pAccessTime); NOREF(pModificationTime); NOREF(pChangeTime); NOREF(pBirthTime); return VERR_NOT_SUPPORTED; } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} */ static DECLCALLBACK(int) rtZipXarFssFile_SetOwner(void *pvThis, RTUID uid, RTGID gid) { NOREF(pvThis); NOREF(uid); NOREF(gid); return VERR_NOT_SUPPORTED; } /** * @interface_method_impl{RTVFSFILEOPS,pfnSeek} */ static DECLCALLBACK(int) rtZipXarFssFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual) { PRTZIPXARFILE pThis = (PRTZIPXARFILE)pvThis; /* Recalculate the request to RTFILE_SEEK_BEGIN. */ switch (uMethod) { case RTFILE_SEEK_BEGIN: break; case RTFILE_SEEK_CURRENT: offSeek += pThis->Ios.offCurPos; break; case RTFILE_SEEK_END: offSeek = pThis->Ios.DataAttr.cbDataArchived + offSeek; break; default: AssertFailedReturn(VERR_INVALID_PARAMETER); } /* Do limit checks. */ if (offSeek < 0) offSeek = 0; else if (offSeek > pThis->Ios.DataAttr.cbDataArchived) offSeek = pThis->Ios.DataAttr.cbDataArchived; /* Apply and return. */ pThis->Ios.fEndOfStream = (offSeek >= pThis->Ios.DataAttr.cbDataArchived); pThis->Ios.offCurPos = offSeek; if (poffActual) *poffActual = offSeek; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize} */ static DECLCALLBACK(int) rtZipXarFssFile_QuerySize(void *pvThis, uint64_t *pcbFile) { PRTZIPXARFILE pThis = (PRTZIPXARFILE)pvThis; *pcbFile = pThis->Ios.DataAttr.cbDataArchived; return VINF_SUCCESS; } /** * Xar file operations. */ static const RTVFSFILEOPS g_rtZipXarFssFileOps = { { /* I/O stream */ { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_FILE, "XarFsStream::File", rtZipXarFssFile_Close, rtZipXarFssIos_QueryInfo, RTVFSOBJOPS_VERSION }, RTVFSIOSTREAMOPS_VERSION, RTVFSIOSTREAMOPS_FEAT_NO_SG, rtZipXarFssIos_Read, rtZipXarFssIos_Write, rtZipXarFssIos_Flush, rtZipXarFssIos_PollOne, rtZipXarFssIos_Tell, NULL /*Skip*/, NULL /*ZeroFill*/, RTVFSIOSTREAMOPS_VERSION }, RTVFSFILEOPS_VERSION, 0, { /* ObjSet */ RTVFSOBJSETOPS_VERSION, RT_OFFSETOF(RTVFSFILEOPS, Stream.Obj) - RT_OFFSETOF(RTVFSFILEOPS, ObjSet), rtZipXarFssFile_SetMode, rtZipXarFssFile_SetTimes, rtZipXarFssFile_SetOwner, RTVFSOBJSETOPS_VERSION }, rtZipXarFssFile_Seek, rtZipXarFssFile_QuerySize, RTVFSFILEOPS_VERSION, }; /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtZipXarFssDecompIos_Close(void *pvThis) { PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis; RTVfsIoStrmRelease(pThis->hVfsIosDecompressor); pThis->hVfsIosDecompressor = NIL_RTVFSIOSTREAM; RTVfsIoStrmRelease(pThis->hVfsIosRaw); pThis->hVfsIosRaw = NIL_RTVFSIOSTREAM; pThis->pIosRaw = NULL; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtZipXarFssDecompIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis; int rc = rtZipXarFssBaseObj_QueryInfo(&pThis->pIosRaw->BaseObj, pObjInfo, enmAddAttr); pObjInfo->cbObject = pThis->pIosRaw->DataAttr.cbDataExtracted; return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} */ static DECLCALLBACK(int) rtZipXarFssDecompIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) { PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis; AssertReturn(pSgBuf->cSegs == 1, VERR_INVALID_PARAMETER); /* * Enforce the cbDataExtracted limit. */ if (pThis->offCurPos > pThis->pIosRaw->DataAttr.cbDataExtracted) return VERR_XAR_EXTRACTED_SIZE_EXCEEDED; /* * Read the data. * * ASSUMES the decompressor stream isn't seekable, so we don't have to * validate off wrt data digest updating. */ int rc = RTVfsIoStrmReadAt(pThis->hVfsIosDecompressor, off, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, fBlocking, pcbRead); if (RT_FAILURE(rc)) return rc; /* * Hash the data. When reaching the end match against the expected digest. */ size_t cbActuallyRead = pcbRead ? *pcbRead : pSgBuf->paSegs[0].cbSeg; pThis->offCurPos += cbActuallyRead; rtZipXarHashUpdate(&pThis->CtxExtracted, pThis->uHashFunExtracted, pSgBuf->paSegs[0].pvSeg, cbActuallyRead); if (rc == VINF_EOF) { if (pThis->offCurPos == pThis->pIosRaw->DataAttr.cbDataExtracted) { if (pThis->uHashState == RTZIPXAR_HASH_PENDING) { RTZIPXARHASHDIGEST Digest; rtZipXarHashFinal(&pThis->CtxExtracted, pThis->uHashFunExtracted, &Digest); if (rtZipXarHashIsEqual(pThis->uHashFunExtracted, &Digest, &pThis->DigestExtracted)) pThis->uHashState = RTZIPXAR_HASH_OK; else { pThis->uHashState = RTZIPXAR_HASH_FAILED_EXTRACTED; rc = VERR_XAR_EXTRACTED_HASH_MISMATCH; } } else if (pThis->uHashState != RTZIPXAR_HASH_OK) rc = VERR_XAR_EXTRACTED_HASH_MISMATCH; } else rc = VERR_XAR_EXTRACTED_SIZE_EXCEEDED; /* Ensure that the raw stream is also at the end so that both message digests are checked. */ if (RT_SUCCESS(rc)) { if ( pThis->pIosRaw->offCurPos < pThis->pIosRaw->DataAttr.cbDataArchived || pThis->pIosRaw->uHashState == RTZIPXAR_HASH_PENDING) rc = VERR_XAR_UNUSED_ARCHIVED_DATA; else if (pThis->pIosRaw->uHashState != RTZIPXAR_HASH_OK) rc = VERR_XAR_ARCHIVED_HASH_MISMATCH; } } return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite} */ static DECLCALLBACK(int) rtZipXarFssDecompIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) { /* Cannot write to a read-only I/O stream. */ NOREF(pvThis); NOREF(off); NOREF(pSgBuf); NOREF(fBlocking); NOREF(pcbWritten); return VERR_ACCESS_DENIED; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} */ static DECLCALLBACK(int) rtZipXarFssDecompIos_Flush(void *pvThis) { /* It's a read only stream, nothing dirty to flush. */ NOREF(pvThis); return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne} */ static DECLCALLBACK(int) rtZipXarFssDecompIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents) { PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis; return RTVfsIoStrmPoll(pThis->hVfsIosDecompressor, fEvents, cMillies, fIntr, pfRetEvents); } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} */ static DECLCALLBACK(int) rtZipXarFssDecompIos_Tell(void *pvThis, PRTFOFF poffActual) { PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis; *poffActual = pThis->offCurPos; return VINF_SUCCESS; } /** * Xar I/O stream operations. */ static const RTVFSIOSTREAMOPS g_rtZipXarFssDecompIosOps = { { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_IO_STREAM, "XarFsStream::DecompIoStream", rtZipXarFssDecompIos_Close, rtZipXarFssDecompIos_QueryInfo, RTVFSOBJOPS_VERSION }, RTVFSIOSTREAMOPS_VERSION, 0, rtZipXarFssDecompIos_Read, rtZipXarFssDecompIos_Write, rtZipXarFssDecompIos_Flush, rtZipXarFssDecompIos_PollOne, rtZipXarFssDecompIos_Tell, NULL /*Skip*/, NULL /*ZeroFill*/, RTVFSIOSTREAMOPS_VERSION }; /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtZipXarFssSym_Close(void *pvThis) { PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis; return rtZipXarFssBaseObj_Close(pThis); } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtZipXarFssSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis; return rtZipXarFssBaseObj_QueryInfo(pThis, pObjInfo, enmAddAttr); } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} */ static DECLCALLBACK(int) rtZipXarFssSym_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) { NOREF(pvThis); NOREF(fMode); NOREF(fMask); return VERR_ACCESS_DENIED; } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} */ static DECLCALLBACK(int) rtZipXarFssSym_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) { NOREF(pvThis); NOREF(pAccessTime); NOREF(pModificationTime); NOREF(pChangeTime); NOREF(pBirthTime); return VERR_ACCESS_DENIED; } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} */ static DECLCALLBACK(int) rtZipXarFssSym_SetOwner(void *pvThis, RTUID uid, RTGID gid) { NOREF(pvThis); NOREF(uid); NOREF(gid); return VERR_ACCESS_DENIED; } /** * @interface_method_impl{RTVFSSYMLINKOPS,pfnRead} */ static DECLCALLBACK(int) rtZipXarFssSym_Read(void *pvThis, char *pszTarget, size_t cbXarget) { PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis; #if 0 return RTStrCopy(pszTarget, cbXarget, pThis->pXarReader->szTarget); #else return VERR_NOT_IMPLEMENTED; #endif } /** * Xar symbolic (and hardlink) operations. */ static const RTVFSSYMLINKOPS g_rtZipXarFssSymOps = { { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_SYMLINK, "XarFsStream::Symlink", rtZipXarFssSym_Close, rtZipXarFssSym_QueryInfo, RTVFSOBJOPS_VERSION }, RTVFSSYMLINKOPS_VERSION, 0, { /* ObjSet */ RTVFSOBJSETOPS_VERSION, RT_OFFSETOF(RTVFSSYMLINKOPS, Obj) - RT_OFFSETOF(RTVFSSYMLINKOPS, ObjSet), rtZipXarFssSym_SetMode, rtZipXarFssSym_SetTimes, rtZipXarFssSym_SetOwner, RTVFSOBJSETOPS_VERSION }, rtZipXarFssSym_Read, RTVFSSYMLINKOPS_VERSION }; /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtZipXarFss_Close(void *pvThis) { PRTZIPXARFSSTREAM pThis = (PRTZIPXARFSSTREAM)pvThis; RTVfsIoStrmRelease(pThis->hVfsIos); pThis->hVfsIos = NIL_RTVFSIOSTREAM; RTVfsFileRelease(pThis->hVfsFile); pThis->hVfsFile = NIL_RTVFSFILE; if (pThis->XarReader.pDoc) delete pThis->XarReader.pDoc; pThis->XarReader.pDoc = NULL; /* The other XarReader fields only point to elements within pDoc. */ pThis->XarReader.pToc = NULL; pThis->XarReader.cCurDepth = 0; pThis->XarReader.pCurFile = NULL; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtZipXarFss_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTZIPXARFSSTREAM pThis = (PRTZIPXARFSSTREAM)pvThis; /* Take the lazy approach here, with the sideffect of providing some info that is actually kind of useful. */ return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr); } /** * @interface_method_impl{RTVFSFSSTREAMOPS,pfnNext} */ static DECLCALLBACK(int) rtZipXarFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj) { PRTZIPXARFSSTREAM pThis = (PRTZIPXARFSSTREAM)pvThis; /* * Check if we've already reached the end in some way. */ if (pThis->fEndOfStream) return VERR_EOF; if (pThis->rcFatal != VINF_SUCCESS) return pThis->rcFatal; /* * Get the next file element. */ xml::ElementNode const *pCurFile = pThis->XarReader.pCurFile; if (pCurFile) pThis->XarReader.pCurFile = pCurFile = rtZipXarGetNextFileElement(pCurFile, &pThis->XarReader.cCurDepth); else if (!pThis->fEndOfStream) { pThis->XarReader.cCurDepth = 0; pThis->XarReader.pCurFile = pCurFile = pThis->XarReader.pToc->findChildElement("file"); } if (!pCurFile) { pThis->fEndOfStream = true; return VERR_EOF; } /* * Retrive the fundamental attributes (elements actually). */ const char *pszName = pCurFile->findChildElementValueP("name"); const char *pszType = pCurFile->findChildElementValueP("type"); if (RT_UNLIKELY(!pszName || !pszType)) return pThis->rcFatal = VERR_XAR_BAD_FILE_ELEMENT; /* * Validate the filename. Being a little too paranoid here, perhaps, wrt * path separators and escapes... */ if ( !*pszName || strchr(pszName, '/') || strchr(pszName, '\\') || strchr(pszName, ':') || !strcmp(pszName, "..") ) return pThis->rcFatal = VERR_XAR_INVALID_FILE_NAME; /* * Gather any additional attributes that are essential to the file type, * then create the VFS object we're going to return. */ int rc; RTVFSOBJ hVfsObj; RTVFSOBJTYPE enmType; if (!strcmp(pszType, "file")) { RTZIPXARDATASTREAM DataAttr; rc = rtZipXarGetDataStreamAttributes(pCurFile, &DataAttr); if (RT_FAILURE(rc)) return pThis->rcFatal = rc; DataAttr.offData += pThis->offZero + pThis->offStart; if ( pThis->hVfsFile != NIL_RTVFSFILE && DataAttr.enmEncoding == RTZIPXARENCODING_STORE) { /* * The input is seekable and the XAR file isn't compressed, so we * can provide a seekable file to the user. */ RTVFSFILE hVfsFile; PRTZIPXARFILE pFileData; rc = RTVfsNewFile(&g_rtZipXarFssFileOps, sizeof(*pFileData), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, NIL_RTVFS, NIL_RTVFSLOCK, &hVfsFile, (void **)&pFileData); if (RT_FAILURE(rc)) return pThis->rcFatal = rc; pFileData->Ios.BaseObj.pFileElem = pCurFile; pFileData->Ios.BaseObj.fModeType = RTFS_TYPE_FILE; pFileData->Ios.DataAttr = DataAttr; pFileData->Ios.offCurPos = 0; pFileData->Ios.fEndOfStream = false; pFileData->Ios.fSeekable = true; pFileData->Ios.uHashState = RTZIPXAR_HASH_PENDING; pFileData->Ios.cbDigested = 0; rtZipXarHashInit(&pFileData->Ios.CtxArchived, pFileData->Ios.DataAttr.uHashFunArchived); rtZipXarHashInit(&pFileData->Ios.CtxExtracted, pFileData->Ios.DataAttr.uHashFunExtracted); pFileData->Ios.hVfsIos = pThis->hVfsIos; RTVfsIoStrmRetain(pFileData->Ios.hVfsIos); pFileData->hVfsFile = pThis->hVfsFile; RTVfsFileRetain(pFileData->hVfsFile); /* Try avoid double content hashing. */ if (pFileData->Ios.DataAttr.uHashFunArchived == pFileData->Ios.DataAttr.uHashFunExtracted) pFileData->Ios.DataAttr.uHashFunExtracted = XAR_HASH_NONE; enmType = RTVFSOBJTYPE_FILE; hVfsObj = RTVfsObjFromFile(hVfsFile); RTVfsFileRelease(hVfsFile); } else { RTVFSIOSTREAM hVfsIosRaw; PRTZIPXARIOSTREAM pIosData; rc = RTVfsNewIoStream(&g_rtZipXarFssIosOps, sizeof(*pIosData), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, NIL_RTVFS, NIL_RTVFSLOCK, &hVfsIosRaw, (void **)&pIosData); if (RT_FAILURE(rc)) return pThis->rcFatal = rc; pIosData->BaseObj.pFileElem = pCurFile; pIosData->BaseObj.fModeType = RTFS_TYPE_FILE; pIosData->DataAttr = DataAttr; pIosData->offCurPos = 0; pIosData->fEndOfStream = false; pIosData->fSeekable = pThis->hVfsFile != NIL_RTVFSFILE; pIosData->uHashState = RTZIPXAR_HASH_PENDING; pIosData->cbDigested = 0; rtZipXarHashInit(&pIosData->CtxArchived, pIosData->DataAttr.uHashFunArchived); rtZipXarHashInit(&pIosData->CtxExtracted, pIosData->DataAttr.uHashFunExtracted); pIosData->hVfsIos = pThis->hVfsIos; RTVfsIoStrmRetain(pThis->hVfsIos); if ( pIosData->DataAttr.enmEncoding != RTZIPXARENCODING_STORE && pIosData->DataAttr.enmEncoding != RTZIPXARENCODING_UNSUPPORTED) { /* * We need to set up a decompression chain. */ RTVFSIOSTREAM hVfsIosDecomp; PRTZIPXARDECOMPIOS pIosDecompData; rc = RTVfsNewIoStream(&g_rtZipXarFssDecompIosOps, sizeof(*pIosDecompData), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN, NIL_RTVFS, NIL_RTVFSLOCK, &hVfsIosDecomp, (void **)&pIosDecompData); if (RT_FAILURE(rc)) { RTVfsIoStrmRelease(hVfsIosRaw); return pThis->rcFatal = rc; } pIosDecompData->hVfsIosDecompressor = NIL_RTVFSIOSTREAM; pIosDecompData->hVfsIosRaw = hVfsIosRaw; pIosDecompData->pIosRaw = pIosData; pIosDecompData->offCurPos = 0; pIosDecompData->uHashFunExtracted = DataAttr.uHashFunExtracted; pIosDecompData->uHashState = RTZIPXAR_HASH_PENDING; rtZipXarHashInit(&pIosDecompData->CtxExtracted, pIosDecompData->uHashFunExtracted); pIosDecompData->DigestExtracted = DataAttr.DigestExtracted; /* Tell the raw end to only hash the archived data. */ pIosData->DataAttr.uHashFunExtracted = XAR_HASH_NONE; /* * Hook up the decompressor. */ switch (DataAttr.enmEncoding) { case RTZIPXARENCODING_GZIP: /* Must allow zlib header, all examples I've got seems to be using it rather than the gzip one. Makes sense as there is no need to repeat the file name and the attributes. */ rc = RTZipGzipDecompressIoStream(hVfsIosRaw, RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR, &pIosDecompData->hVfsIosDecompressor); break; default: rc = VERR_INTERNAL_ERROR_5; break; } if (RT_FAILURE(rc)) { RTVfsIoStrmRelease(hVfsIosDecomp); return pThis->rcFatal = rc; } /* What to return. */ hVfsObj = RTVfsObjFromIoStream(hVfsIosDecomp); RTVfsIoStrmRelease(hVfsIosDecomp); } else { /* Try avoid double content hashing. */ if (pIosData->DataAttr.uHashFunArchived == pIosData->DataAttr.uHashFunExtracted) pIosData->DataAttr.uHashFunExtracted = XAR_HASH_NONE; /* What to return. */ hVfsObj = RTVfsObjFromIoStream(hVfsIosRaw); RTVfsIoStrmRelease(hVfsIosRaw); } enmType = RTVFSOBJTYPE_IO_STREAM; } } else if (!strcmp(pszType, "directory")) { PRTZIPXARBASEOBJ pBaseObjData; rc = RTVfsNewBaseObj(&g_rtZipXarFssBaseObjOps, sizeof(*pBaseObjData), NIL_RTVFS, NIL_RTVFSLOCK, &hVfsObj, (void **)&pBaseObjData); if (RT_FAILURE(rc)) return pThis->rcFatal = rc; pBaseObjData->pFileElem = pCurFile; pBaseObjData->fModeType = RTFS_TYPE_DIRECTORY; enmType = RTVFSOBJTYPE_BASE; } else if (!strcmp(pszType, "symlink")) { RTVFSSYMLINK hVfsSym; PRTZIPXARBASEOBJ pBaseObjData; rc = RTVfsNewSymlink(&g_rtZipXarFssSymOps, sizeof(*pBaseObjData), NIL_RTVFS, NIL_RTVFSLOCK, &hVfsSym, (void **)&pBaseObjData); if (RT_FAILURE(rc)) return pThis->rcFatal = rc; pBaseObjData->pFileElem = pCurFile; pBaseObjData->fModeType = RTFS_TYPE_SYMLINK; enmType = RTVFSOBJTYPE_SYMLINK; hVfsObj = RTVfsObjFromSymlink(hVfsSym); RTVfsSymlinkRelease(hVfsSym); } else return pThis->rcFatal = VERR_XAR_UNKNOWN_FILE_TYPE; /* * Set the return data and we're done. */ if (ppszName) { /* Figure the length. */ size_t const cbCurName = strlen(pszName) + 1; size_t cbFullName = cbCurName; const xml::ElementNode *pAncestor = pCurFile; uint32_t cLeft = pThis->XarReader.cCurDepth; while (cLeft-- > 0) { pAncestor = (const xml::ElementNode *)pAncestor->getParent(); Assert(pAncestor); const char *pszAncestorName = pAncestor->findChildElementValueP("name"); Assert(pszAncestorName); cbFullName += strlen(pszAncestorName) + 1; } /* Allocate a buffer. */ char *psz = *ppszName = RTStrAlloc(cbFullName); if (!psz) { RTVfsObjRelease(hVfsObj); return VERR_NO_STR_MEMORY; } /* Construct it, from the end. */ psz += cbFullName; psz -= cbCurName; memcpy(psz, pszName, cbCurName); pAncestor = pCurFile; cLeft = pThis->XarReader.cCurDepth; while (cLeft-- > 0) { pAncestor = (const xml::ElementNode *)pAncestor->getParent(); Assert(pAncestor); const char *pszAncestorName = pAncestor->findChildElementValueP("name"); Assert(pszAncestorName); *--psz = '/'; size_t cchAncestorName = strlen(pszAncestorName); psz -= cchAncestorName; memcpy(psz, pszAncestorName, cchAncestorName); } Assert(*ppszName == psz); } if (phVfsObj) *phVfsObj = hVfsObj; else RTVfsObjRelease(hVfsObj); if (penmType) *penmType = enmType; return VINF_SUCCESS; } /** * Xar filesystem stream operations. */ static const RTVFSFSSTREAMOPS rtZipXarFssOps = { { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_FS_STREAM, "XarFsStream", rtZipXarFss_Close, rtZipXarFss_QueryInfo, RTVFSOBJOPS_VERSION }, RTVFSFSSTREAMOPS_VERSION, 0, rtZipXarFss_Next, RTVFSFSSTREAMOPS_VERSION }; /** * TOC validation part 2. * * Will advance the input stream past the TOC hash and signature data. * * @returns IPRT status code. * @param pThis The FS stream instance being created. * @param pXarHdr The XAR header. * @param pTocDigest The TOC input data digest. */ static int rtZipXarValidateTocPart2(PRTZIPXARFSSTREAM pThis, PCXARHEADER pXarHdr, PCRTZIPXARHASHDIGEST pTocDigest) { int rc; /* * Check that the hash function in the TOC matches the one in the XAR header. */ const xml::ElementNode *pChecksumElem = pThis->XarReader.pToc->findChildElement("checksum"); if (pChecksumElem) { const xml::AttributeNode *pAttr = pChecksumElem->findAttribute("style"); if (!pAttr) return VERR_XAR_BAD_CHECKSUM_ELEMENT; const char *pszStyle = pAttr->getValue(); if (!pszStyle) return VERR_XAR_BAD_CHECKSUM_ELEMENT; uint8_t uHashFunction; rc = rtZipXarParseChecksumStyle(pszStyle, &uHashFunction); if (RT_FAILURE(rc)) return rc; if (uHashFunction != pThis->uHashFunction) return VERR_XAR_HASH_FUNCTION_MISMATCH; /* * Verify the checksum if we got one. */ if (pThis->uHashFunction != XAR_HASH_NONE) { RTFOFF offChecksum; RTFOFF cbChecksum; rc = rtZipXarGetOffsetSizeLengthFromElem(pChecksumElem, &offChecksum, &cbChecksum, NULL); if (RT_FAILURE(rc)) return rc; if (cbChecksum != (RTFOFF)pThis->cbHashDigest) return VERR_XAR_BAD_DIGEST_LENGTH; if (offChecksum != 0 && pThis->hVfsFile == NIL_RTVFSFILE) return VERR_XAR_NOT_STREAMBLE_ELEMENT_ORDER; RTZIPXARHASHDIGEST StoredDigest; rc = RTVfsIoStrmReadAt(pThis->hVfsIos, pThis->offZero + offChecksum, &StoredDigest, pThis->cbHashDigest, true /*fBlocking*/, NULL /*pcbRead*/); if (RT_FAILURE(rc)) return rc; if (memcmp(&StoredDigest, pTocDigest, pThis->cbHashDigest)) return VERR_XAR_TOC_DIGEST_MISMATCH; } } else if (pThis->uHashFunction != XAR_HASH_NONE) return VERR_XAR_BAD_CHECKSUM_ELEMENT; /* * Check the signature, if we got one. */ /** @todo signing. */ return VINF_SUCCESS; } /** * Reads and validates the table of content. * * @returns IPRT status code. * @param hVfsIosIn The input stream. * @param pXarHdr The XAR header. * @param pDoc The TOC XML document. * @param ppTocElem Where to return the pointer to the TOC element on * success. * @param pTocDigest Where to return the TOC digest on success. */ static int rtZipXarReadAndValidateToc(RTVFSIOSTREAM hVfsIosIn, PCXARHEADER pXarHdr, xml::Document *pDoc, xml::ElementNode const **ppTocElem, PRTZIPXARHASHDIGEST pTocDigest) { /* * Decompress it, calculating the hash while doing so. */ char *pszOutput = (char *)RTMemTmpAlloc(pXarHdr->cbTocUncompressed + 1); if (!pszOutput) return VERR_NO_TMP_MEMORY; int rc = VERR_NO_TMP_MEMORY; void *pvInput = RTMemTmpAlloc(pXarHdr->cbTocCompressed); if (pvInput) { rc = RTVfsIoStrmRead(hVfsIosIn, pvInput, pXarHdr->cbTocCompressed, true /*fBlocking*/, NULL); if (RT_SUCCESS(rc)) { rtZipXarCalcHash(pXarHdr->uHashFunction, pvInput, pXarHdr->cbTocCompressed, pTocDigest); size_t cbActual; rc = RTZipBlockDecompress(RTZIPTYPE_ZLIB, 0 /*fFlags*/, pvInput, pXarHdr->cbTocCompressed, NULL, pszOutput, pXarHdr->cbTocUncompressed, &cbActual); if (RT_SUCCESS(rc) && cbActual != pXarHdr->cbTocUncompressed) rc = VERR_XAR_TOC_UNCOMP_SIZE_MISMATCH; } RTMemTmpFree(pvInput); } if (RT_SUCCESS(rc)) { pszOutput[pXarHdr->cbTocUncompressed] = '\0'; /* * Parse the TOC (XML document) and do some basic validations. */ size_t cchToc = strlen(pszOutput); if ( cchToc == pXarHdr->cbTocUncompressed || cchToc + 1 == pXarHdr->cbTocUncompressed) { rc = RTStrValidateEncoding(pszOutput); if (RT_SUCCESS(rc)) { xml::XmlMemParser Parser; try { Parser.read(pszOutput, cchToc, RTCString("xar-toc.xml"), *pDoc); } catch (xml::XmlError Err) { rc = VERR_XAR_TOC_XML_PARSE_ERROR; } catch (...) { rc = VERR_NO_MEMORY; } if (RT_SUCCESS(rc)) { xml::ElementNode const *pRootElem = pDoc->getRootElement(); xml::ElementNode const *pTocElem = NULL; if (pRootElem && pRootElem->nameEquals("xar")) pTocElem = pRootElem ? pRootElem->findChildElement("toc") : NULL; if (pTocElem) { #ifndef USE_STD_LIST_FOR_CHILDREN Assert(pRootElem->getParent() == NULL); Assert(pTocElem->getParent() == pRootElem); if ( !pTocElem->getNextSibiling() && !pTocElem->getPrevSibiling()) #endif { /* * Further parsing and validation is done after the * caller has created an file system stream instance. */ *ppTocElem = pTocElem; RTMemTmpFree(pszOutput); return VINF_SUCCESS; } rc = VERR_XML_TOC_ELEMENT_HAS_SIBLINGS; } else rc = VERR_XML_TOC_ELEMENT_MISSING; } } else rc = VERR_XAR_TOC_UTF8_ENCODING; } else rc = VERR_XAR_TOC_STRLEN_MISMATCH; } RTMemTmpFree(pszOutput); return rc; } /** * Reads and validates the XAR header. * * @returns IPRT status code. * @param hVfsIosIn The input stream. * @param pXarHdr Where to return the XAR header in host byte order. */ static int rtZipXarReadAndValidateHeader(RTVFSIOSTREAM hVfsIosIn, PXARHEADER pXarHdr) { /* * Read it and check the signature. */ int rc = RTVfsIoStrmRead(hVfsIosIn, pXarHdr, sizeof(*pXarHdr), true /*fBlocking*/, NULL); if (RT_FAILURE(rc)) return rc; if (pXarHdr->u32Magic != XAR_HEADER_MAGIC) return VERR_XAR_WRONG_MAGIC; /* * Correct the byte order. */ pXarHdr->cbHeader = RT_BE2H_U16(pXarHdr->cbHeader); pXarHdr->uVersion = RT_BE2H_U16(pXarHdr->uVersion); pXarHdr->cbTocCompressed = RT_BE2H_U64(pXarHdr->cbTocCompressed); pXarHdr->cbTocUncompressed = RT_BE2H_U64(pXarHdr->cbTocUncompressed); pXarHdr->uHashFunction = RT_BE2H_U32(pXarHdr->uHashFunction); /* * Validate the header. */ if (pXarHdr->uVersion > XAR_HEADER_VERSION) return VERR_XAR_UNSUPPORTED_VERSION; if (pXarHdr->cbHeader < sizeof(XARHEADER)) return VERR_XAR_BAD_HDR_SIZE; if (pXarHdr->uHashFunction > XAR_HASH_MAX) return VERR_XAR_UNSUPPORTED_HASH_FUNCTION; if (pXarHdr->cbTocUncompressed < 16) return VERR_XAR_TOC_TOO_SMALL; if (pXarHdr->cbTocUncompressed > _4M) return VERR_XAR_TOC_TOO_BIG; if (pXarHdr->cbTocCompressed > _4M) return VERR_XAR_TOC_TOO_BIG_COMPRESSED; /* * Skip over bytes we don't understand (could be padding). */ if (pXarHdr->cbHeader > sizeof(XARHEADER)) { rc = RTVfsIoStrmSkip(hVfsIosIn, pXarHdr->cbHeader - sizeof(XARHEADER)); if (RT_FAILURE(rc)) return rc; } return VINF_SUCCESS; } RTDECL(int) RTZipXarFsStreamFromIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss) { /* * Input validation. */ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE); *phVfsFss = NIL_RTVFSFSSTREAM; AssertPtrReturn(hVfsIosIn, VERR_INVALID_HANDLE); AssertReturn(!fFlags, VERR_INVALID_PARAMETER); RTFOFF const offStart = RTVfsIoStrmTell(hVfsIosIn); AssertReturn(offStart >= 0, (int)offStart); uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn); AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); /* * Read and validate the header, then uncompress the TOC. */ XARHEADER XarHdr; int rc = rtZipXarReadAndValidateHeader(hVfsIosIn, &XarHdr); if (RT_SUCCESS(rc)) { xml::Document *pDoc = NULL; try { pDoc = new xml::Document(); } catch (...) { } if (pDoc) { RTZIPXARHASHDIGEST TocDigest; xml::ElementNode const *pTocElem = NULL; rc = rtZipXarReadAndValidateToc(hVfsIosIn, &XarHdr, pDoc, &pTocElem, &TocDigest); if (RT_SUCCESS(rc)) { size_t offZero = RTVfsIoStrmTell(hVfsIosIn); if (offZero > 0) { /* * Create a file system stream before we continue the parsing. */ PRTZIPXARFSSTREAM pThis; RTVFSFSSTREAM hVfsFss; rc = RTVfsNewFsStream(&rtZipXarFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, &hVfsFss, (void **)&pThis); if (RT_SUCCESS(rc)) { pThis->hVfsIos = hVfsIosIn; pThis->hVfsFile = RTVfsIoStrmToFile(hVfsIosIn); pThis->offStart = offStart; pThis->offZero = offZero; pThis->uHashFunction = (uint8_t)XarHdr.uHashFunction; switch (pThis->uHashFunction) { case XAR_HASH_MD5: pThis->cbHashDigest = sizeof(TocDigest.abMd5); break; case XAR_HASH_SHA1: pThis->cbHashDigest = sizeof(TocDigest.abSha1); break; default: pThis->cbHashDigest = 0; break; } pThis->fEndOfStream = false; pThis->rcFatal = VINF_SUCCESS; pThis->XarReader.pDoc = pDoc; pThis->XarReader.pToc = pTocElem; pThis->XarReader.pCurFile = 0; pThis->XarReader.cCurDepth = 0; /* * Next validation step. */ rc = rtZipXarValidateTocPart2(pThis, &XarHdr, &TocDigest); if (RT_SUCCESS(rc)) { *phVfsFss = hVfsFss; return VINF_SUCCESS; } RTVfsFsStrmRelease(hVfsFss); return rc; } } else rc = (int)offZero; } delete pDoc; } else rc = VERR_NO_MEMORY; } RTVfsIoStrmRelease(hVfsIosIn); return rc; }