summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/zip
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Runtime/common/zip
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Runtime/common/zip')
-rw-r--r--src/VBox/Runtime/common/zip/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/zip/cpiovfs.cpp1146
-rw-r--r--src/VBox/Runtime/common/zip/cpiovfsreader.h169
-rw-r--r--src/VBox/Runtime/common/zip/gzipcmd.cpp606
-rw-r--r--src/VBox/Runtime/common/zip/gzipvfs.cpp1035
-rw-r--r--src/VBox/Runtime/common/zip/pkzip.cpp259
-rw-r--r--src/VBox/Runtime/common/zip/pkzipvfs.cpp1296
-rw-r--r--src/VBox/Runtime/common/zip/tar.h146
-rw-r--r--src/VBox/Runtime/common/zip/tarcmd.cpp1960
-rw-r--r--src/VBox/Runtime/common/zip/tarvfs.cpp1477
-rw-r--r--src/VBox/Runtime/common/zip/tarvfsreader.h179
-rw-r--r--src/VBox/Runtime/common/zip/tarvfswriter.cpp2363
-rw-r--r--src/VBox/Runtime/common/zip/unzipcmd.cpp480
-rw-r--r--src/VBox/Runtime/common/zip/xarvfs.cpp2156
-rw-r--r--src/VBox/Runtime/common/zip/zip.cpp2016
15 files changed, 15288 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/zip/Makefile.kup b/src/VBox/Runtime/common/zip/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/Makefile.kup
diff --git a/src/VBox/Runtime/common/zip/cpiovfs.cpp b/src/VBox/Runtime/common/zip/cpiovfs.cpp
new file mode 100644
index 00000000..04dc8675
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/cpiovfs.cpp
@@ -0,0 +1,1146 @@
+/* $Id: cpiovfs.cpp $ */
+/** @file
+ * IPRT - CPIO Virtual Filesystem, Reader.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/poll.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include "cpiovfsreader.h"
+
+
+/**
+ * Converts a octal numeric header field to the C native type.
+ *
+ * @returns IPRT status code.
+ * @param pachField The CPIO header field.
+ * @param cchField The length of the field.
+ * @param pi64 Where to store the value.
+ */
+static int rtZipCpioHdrOctalFieldToNum(const char *pachField, size_t cchField, int64_t *pi64)
+{
+ /*
+ * Skip leading zeros to save a few slower loops below.
+ */
+ while (cchField > 0 && *pachField == '0')
+ cchField--, pachField++;
+
+ /*
+ * Convert octal digits.
+ */
+ int64_t i64 = 0;
+ while (cchField > 0)
+ {
+ unsigned char uDigit = *pachField - '0';
+ if (uDigit >= 8)
+ return VERR_TAR_BAD_NUM_FIELD;
+ i64 <<= 3;
+ i64 |= uDigit;
+
+ pachField++;
+ cchField--;
+ }
+ *pi64 = i64;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts a hex character to the appropriate nibble.
+ *
+ * @returns Nibble of the character.
+ * @param chVal The value to convert.
+ */
+static inline uint8_t rtZipCpioHexToNibble(char chVal)
+{
+ if (chVal >= '0' && chVal <= '9')
+ return chVal - '0';
+ else if (chVal >= 'a' && chVal <= 'f')
+ return chVal - 'a' + 10;
+ else if (chVal >= 'A' && chVal <= 'F')
+ return chVal - 'A' + 10;
+
+ return 0xff;
+}
+
+
+/**
+ * Converts a hexadecimal numeric header field to the C native type.
+ *
+ * @returns IPRT status code.
+ * @param pachField The CPIO header field.
+ * @param cchField The length of the field.
+ * @param pi64 Where to store the value.
+ */
+static int rtZipCpioHdrHexFieldToNum(const char *pachField, size_t cchField, int64_t *pi64)
+{
+ uint64_t u64 = 0;
+
+ while (cchField-- > 0)
+ {
+ uint8_t bNb = rtZipCpioHexToNibble(*pachField++);
+
+ if (RT_LIKELY(bNb != 0xff))
+ u64 = (u64 << 4) | bNb;
+ else
+ return VERR_TAR_BAD_NUM_FIELD;
+ }
+
+ *pi64 = (int64_t)u64;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses the given ancient binary header and converts it to an FS object info structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CPIO reader state.
+ * @param pHdr The header to convert.
+ * @param pcbFilePath Where to store the file path size on success.
+ * @param pcbPad Where to store the number of bytes padded after the header and file path
+ * before the content begins.
+ */
+static int rtZipCpioReaderParseHeaderAncientBin(PRTZIPCPIOREADER pThis, PCCPIOHDRBIN pHdr,
+ uint32_t *pcbFilePath, uint32_t *pcbPad)
+{
+ RT_NOREF(pThis, pHdr, pcbFilePath, pcbPad);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * Parses the given SuSv2 ASCII header and converts it to an FS object info structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CPIO reader state.
+ * @param pHdr The header to convert.
+ * @param pcbFilePath Where to store the file path size on success.
+ * @param pcbPad Where to store the number of bytes padded after the header and file path
+ * before the content begins.
+ */
+static int rtZipCpioReaderParseHeaderAsciiSusV2(PRTZIPCPIOREADER pThis, PCCPIOHDRSUSV2 pHdr,
+ uint32_t *pcbFilePath, uint32_t *pcbPad)
+{
+ PRTFSOBJINFO pObjInfo = &pThis->ObjInfo;
+ int rc;
+ int64_t i64Tmp;
+ int64_t c64SecModTime;
+
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+
+#define GET_CPIO_NUMERIC_FIELD_RET(a_Var, a_Field) \
+ do { \
+ rc = rtZipCpioHdrOctalFieldToNum(a_Field, sizeof(a_Field), &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = i64Tmp; \
+ if ((a_Var) != i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+#define GET_CPIO_NUMERIC_FIELD_RET_U64(a_Var, a_Field) \
+ do { \
+ rc = rtZipCpioHdrOctalFieldToNum(a_Field, sizeof(a_Field), &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = (uint64_t)i64Tmp; \
+ if ((a_Var) != (uint64_t)i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.fMode, pHdr->achMode);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.uid, pHdr->achUid);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.gid, pHdr->achGid);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.cHardlinks, pHdr->achNLinks);
+ GET_CPIO_NUMERIC_FIELD_RET_U64(pObjInfo->Attr.u.Unix.INodeId, pHdr->achInode);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.Device, pHdr->achDev);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->cbObject, pHdr->achFileSize);
+ pObjInfo->cbAllocated = pObjInfo->cbObject;
+ GET_CPIO_NUMERIC_FIELD_RET( c64SecModTime, pHdr->achMTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ChangeTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ModificationTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->AccessTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->BirthTime, c64SecModTime);
+ if (c64SecModTime != RTTimeSpecGetSeconds(&pObjInfo->ModificationTime))
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+
+ GET_CPIO_NUMERIC_FIELD_RET(*pcbFilePath, pHdr->achNameSize);
+
+ /* There is never any padding. */
+ *pcbPad = 0;
+
+#undef GET_CPIO_NUMERIC_FIELD_RET
+#undef GET_CPIO_NUMERIC_FIELD_RET_U64
+
+ return rc;
+}
+
+
+/**
+ * Parses the given "new" ASCII header and converts it to an FS object info structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CPIO reader state.
+ * @param pHdr The header to convert.
+ * @param fWithChksum Flag whether the header uses the checksum field.
+ * @param pcbFilePath Where to store the file path size on success.
+ * @param pcbPad Where to store the number of bytes padded after the header and file path
+ * before the content begins.
+ */
+static int rtZipCpioReaderParseHeaderAsciiNew(PRTZIPCPIOREADER pThis, PCCPIOHDRNEW pHdr, bool fWithChksum,
+ uint32_t *pcbFilePath, uint32_t *pcbPad)
+{
+ RT_NOREF(fWithChksum); /** @todo */
+ PRTFSOBJINFO pObjInfo = &pThis->ObjInfo;
+ int rc;
+ int64_t i64Tmp;
+ int64_t c64SecModTime;
+ uint32_t uMajor, uMinor;
+
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+
+#define GET_CPIO_NUMERIC_FIELD_RET(a_Var, a_Field) \
+ do { \
+ rc = rtZipCpioHdrHexFieldToNum(a_Field, sizeof(a_Field), &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = i64Tmp; \
+ if ((a_Var) != i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+#define GET_CPIO_NUMERIC_FIELD_RET_U64(a_Var, a_Field) \
+ do { \
+ rc = rtZipCpioHdrHexFieldToNum(a_Field, sizeof(a_Field), &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = (uint64_t)i64Tmp; \
+ if ((a_Var) != (uint64_t)i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.fMode, pHdr->achMode);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.uid, pHdr->achUid);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.gid, pHdr->achGid);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.cHardlinks, pHdr->achNLinks);
+ GET_CPIO_NUMERIC_FIELD_RET_U64(pObjInfo->Attr.u.Unix.INodeId, pHdr->achInode);
+ GET_CPIO_NUMERIC_FIELD_RET( uMajor, pHdr->achDevMajor);
+ GET_CPIO_NUMERIC_FIELD_RET( uMinor, pHdr->achDevMinor);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->cbObject, pHdr->achFileSize);
+ pObjInfo->cbAllocated = RT_ALIGN_64(pObjInfo->cbObject, 4);
+ GET_CPIO_NUMERIC_FIELD_RET( c64SecModTime, pHdr->achMTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ChangeTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ModificationTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->AccessTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->BirthTime, c64SecModTime);
+ if (c64SecModTime != RTTimeSpecGetSeconds(&pObjInfo->ModificationTime))
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+ pObjInfo->Attr.u.Unix.Device = RTDEV_MAKE(uMajor, uMinor);
+ if ( uMajor != RTDEV_MAJOR(pObjInfo->Attr.u.Unix.Device)
+ || uMinor != RTDEV_MINOR(pObjInfo->Attr.u.Unix.Device))
+ return VERR_TAR_DEV_VALUE_TOO_LARGE;
+
+ GET_CPIO_NUMERIC_FIELD_RET(*pcbFilePath, pHdr->achNameSize);
+
+ /* Header and file path are padded with 0 bytes to a 4 byte boundary. */
+ uint32_t cbComp = *pcbFilePath + sizeof(*pHdr);
+ *pcbPad = RT_ALIGN_32(cbComp, 4) - cbComp;
+
+#undef GET_CPIO_NUMERIC_FIELD_RET
+#undef GET_CPIO_NUMERIC_FIELD_RET_U64
+
+ return rc;
+}
+
+
+/**
+ * Parses and validates a CPIO header.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CPIO reader state.
+ * @param enmType The CPIO header type.
+ * @param pHdr The CPIO header that has been read.
+ * @param pcbFilePath Where to store the size of the file path on success.
+ * @param pcbPad Where to store the number of bytes padded after the header and file path
+ * before the content begins.
+ */
+static int rtZipCpioReaderParseHeader(PRTZIPCPIOREADER pThis, RTZIPCPIOTYPE enmType, PCCPIOHDR pHdr,
+ uint32_t *pcbFilePath, uint32_t *pcbPad)
+{
+ int rc;
+
+ switch (enmType)
+ {
+ case RTZIPCPIOTYPE_ANCIENT_BIN:
+ rc = rtZipCpioReaderParseHeaderAncientBin(pThis, &pHdr->AncientBin,
+ pcbFilePath, pcbPad);
+ break;
+ case RTZIPCPIOTYPE_ASCII_SUSV2:
+ rc = rtZipCpioReaderParseHeaderAsciiSusV2(pThis, &pHdr->AsciiSuSv2,
+ pcbFilePath, pcbPad);
+ break;
+ case RTZIPCPIOTYPE_ASCII_NEW:
+ rc = rtZipCpioReaderParseHeaderAsciiNew(pThis, &pHdr->AsciiNew, false /*fWithChksum*/,
+ pcbFilePath, pcbPad);
+ break;
+ case RTZIPCPIOTYPE_ASCII_NEW_CHKSUM:
+ rc = rtZipCpioReaderParseHeaderAsciiNew(pThis, &pHdr->AsciiNew, true /*fWithChksum*/,
+ pcbFilePath, pcbPad);
+ break;
+ default:
+ AssertMsgFailedBreakStmt(("Invalid CPIO type %d\n", enmType), rc = VERR_INTERNAL_ERROR);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Reads the file path from the CPIO archive stream.
+ *
+ * @returns IPRT status code.
+ * @param hVfsIos The I/O stream to read from.
+ * @param pThis The CPIO reader state.
+ * @param cbFilePath Size of the file path in bytes.
+ */
+static int rtZipCpioReaderReadPath(RTVFSIOSTREAM hVfsIos, PRTZIPCPIOREADER pThis, size_t cbFilePath)
+{
+ if (cbFilePath >= sizeof(pThis->szName))
+ return VERR_TAR_NAME_TOO_LONG;
+
+ size_t cbRead;
+ int rc = RTVfsIoStrmRead(hVfsIos, &pThis->szName[0], cbFilePath, true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbRead != cbFilePath)
+ return VERR_TAR_UNEXPECTED_EOS;
+
+ /* The read file name should be zero terminated at the end. */
+ if (pThis->szName[cbFilePath - 1] != '\0')
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+
+ return VINF_SUCCESS;
+}
+
+
+/*
+ *
+ * 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) rtZipCpioFssBaseObj_Close(void *pvThis)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+
+ /* Currently there is nothing we really have to do here. */
+ pThis->offHdr = -1;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipCpioFssBaseObj_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+
+ /*
+ * Copy the desired data.
+ */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER;
+ pObjInfo->Attr.u.UnixOwner.uid = pThis->ObjInfo.Attr.u.Unix.uid;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP;
+ pObjInfo->Attr.u.UnixGroup.gid = pThis->ObjInfo.Attr.u.Unix.gid;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE;
+ RT_ZERO(pObjInfo->Attr.u);
+ break;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tar filesystem base object operations.
+ */
+static const RTVFSOBJOPS g_rtZipCpioFssBaseObjOps =
+{
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_BASE,
+ "CpioFsStream::Obj",
+ rtZipCpioFssBaseObj_Close,
+ rtZipCpioFssBaseObj_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_Close(void *pvThis)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)pvThis;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return rtZipCpioFssBaseObj_Close(&pThis->BaseObj);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)pvThis;
+ return rtZipCpioFssBaseObj_QueryInfo(&pThis->BaseObj, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)pvThis;
+ Assert(pSgBuf->cSegs == 1);
+
+ /*
+ * Make offset into a real offset so it's possible to do random access
+ * on CPIO files that are seekable. Fend of reads beyond the end of the
+ * stream.
+ */
+ if (off < 0)
+ off = pThis->offFile;
+ if (off >= pThis->cbFile)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+
+ Assert(pThis->cbFile >= pThis->offFile);
+ uint64_t cbLeft = (uint64_t)(pThis->cbFile - off);
+ size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
+ 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, pThis->offStart + off, pSgBuf->paSegs[0].pvSeg, cbToRead, fBlocking, pcbRead);
+ pThis->offFile = off + *pcbRead;
+ if (pThis->offFile >= pThis->cbFile)
+ {
+ Assert(pThis->offFile == pThis->cbFile);
+ pThis->fEndOfStream = true;
+ RTVfsIoStrmSkip(pThis->hVfsIos, pThis->cbPadding);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_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) rtZipCpioFssIos_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) rtZipCpioFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)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) rtZipCpioFssIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tar I/O stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_rtZipCpioFssIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "CpioFsStream::IoStream",
+ rtZipCpioFssIos_Close,
+ rtZipCpioFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipCpioFssIos_Read,
+ rtZipCpioFssIos_Write,
+ rtZipCpioFssIos_Flush,
+ rtZipCpioFssIos_PollOne,
+ rtZipCpioFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipCpioFssSym_Close(void *pvThis)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+ return rtZipCpioFssBaseObj_Close(pThis);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipCpioFssSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+ return rtZipCpioFssBaseObj_QueryInfo(pThis, pObjInfo, enmAddAttr);
+}
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtZipCpioFssSym_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) rtZipCpioFssSym_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) rtZipCpioFssSym_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) rtZipCpioFssSym_Read(void *pvThis, char *pszTarget, size_t cbTarget)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+ return RTStrCopy(pszTarget, cbTarget, pThis->pCpioReader->szTarget);
+}
+
+
+/**
+ * CPIO symbolic (and hardlink) operations.
+ */
+static const RTVFSSYMLINKOPS g_rtZipCpioFssSymOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_SYMLINK,
+ "CpioFsStream::Symlink",
+ rtZipCpioFssSym_Close,
+ rtZipCpioFssSym_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSSYMLINKOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj),
+ rtZipCpioFssSym_SetMode,
+ rtZipCpioFssSym_SetTimes,
+ rtZipCpioFssSym_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtZipCpioFssSym_Read,
+ RTVFSSYMLINKOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipCpioFss_Close(void *pvThis)
+{
+ PRTZIPCPIOFSSTREAM pThis = (PRTZIPCPIOFSSTREAM)pvThis;
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipCpioFss_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPCPIOFSSTREAM pThis = (PRTZIPCPIOFSSTREAM)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}
+ */
+DECL_HIDDEN_CALLBACK(int) rtZipCpioFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ PRTZIPCPIOFSSTREAM pThis = (PRTZIPCPIOFSSTREAM)pvThis;
+
+ /*
+ * Dispense with the current object.
+ */
+ if (pThis->hVfsCurObj != NIL_RTVFSOBJ)
+ {
+ if (pThis->pCurIosData)
+ {
+ pThis->pCurIosData->fEndOfStream = true;
+ pThis->pCurIosData->offFile = pThis->pCurIosData->cbFile;
+ pThis->pCurIosData = NULL;
+ }
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ }
+
+ /*
+ * 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;
+
+ /*
+ * Make sure the input stream is in the right place.
+ */
+ RTFOFF offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ while ( offHdr >= 0
+ && offHdr < pThis->offNextHdr)
+ {
+ int rc = RTVfsIoStrmSkip(pThis->hVfsIos, pThis->offNextHdr - offHdr);
+ if (RT_FAILURE(rc))
+ {
+ /** @todo Ignore if we're at the end of the stream? */
+ return pThis->rcFatal = rc;
+ }
+
+ offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ }
+
+ if (offHdr < 0)
+ return pThis->rcFatal = (int)offHdr;
+ if (offHdr > pThis->offNextHdr)
+ return pThis->rcFatal = VERR_INTERNAL_ERROR_3;
+ Assert(pThis->offNextHdr == offHdr);
+ pThis->offCurHdr = offHdr;
+
+ /*
+ * Consume CPIO headers.
+ */
+ size_t cbHdr = 0;
+ /*
+ * Read the next header.
+ *
+ * Read the first 6 bytes to determine the header type and continue reading the
+ * rest of the header.
+ */
+ CPIOHDR Hdr;
+ RTZIPCPIOTYPE enmHdrType = RTZIPCPIOTYPE_INVALID;
+ size_t cbRead;
+ int rc = RTVfsIoStrmRead(pThis->hVfsIos, &Hdr.ab[0], sizeof(Hdr.AsciiNew.achMagic), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ if (rc == VINF_EOF && cbRead == 0)
+ {
+ pThis->fEndOfStream = true;
+ return VERR_EOF;
+ }
+ if (cbRead != sizeof(Hdr.AsciiNew.achMagic))
+ return pThis->rcFatal = VERR_TAR_UNEXPECTED_EOS;
+
+ if (Hdr.AncientBin.u16Magic == CPIO_HDR_BIN_MAGIC)
+ {
+ cbHdr = sizeof(Hdr.AncientBin);
+ enmHdrType = RTZIPCPIOTYPE_ANCIENT_BIN;
+ }
+ else if (!strncmp(&Hdr.AsciiSuSv2.achMagic[0], CPIO_HDR_SUSV2_MAGIC, sizeof(Hdr.AsciiSuSv2.achMagic)))
+ {
+ cbHdr = sizeof(Hdr.AsciiSuSv2);
+ enmHdrType = RTZIPCPIOTYPE_ASCII_SUSV2;
+ }
+ else if (!strncmp(&Hdr.AsciiNew.achMagic[0], CPIO_HDR_NEW_MAGIC, sizeof(Hdr.AsciiNew.achMagic)))
+ {
+ cbHdr = sizeof(Hdr.AsciiNew);
+ enmHdrType = RTZIPCPIOTYPE_ASCII_NEW;
+ }
+ else if (!strncmp(&Hdr.AsciiNew.achMagic[0], CPIO_HDR_NEW_CHKSUM_MAGIC, sizeof(Hdr.AsciiNew.achMagic)))
+ {
+ cbHdr = sizeof(Hdr.AsciiNew);
+ enmHdrType = RTZIPCPIOTYPE_ASCII_NEW_CHKSUM;
+ }
+ else
+ return pThis->rcFatal = VERR_TAR_UNKNOWN_TYPE_FLAG; /** @todo Dedicated status code. */
+
+ /* Read the rest of the header. */
+ size_t cbHdrLeft = cbHdr - sizeof(Hdr.AsciiNew.achMagic);
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, &Hdr.ab[sizeof(Hdr.AsciiNew.achMagic)], cbHdr - sizeof(Hdr.AsciiNew.achMagic), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ if (cbRead != cbHdrLeft)
+ return pThis->rcFatal = VERR_TAR_UNEXPECTED_EOS;
+
+ /*
+ * Parse it.
+ */
+ uint32_t cbFilePath = 0;
+ uint32_t cbPad = 0;
+ rc = rtZipCpioReaderParseHeader(&pThis->CpioReader, enmHdrType, &Hdr, &cbFilePath, &cbPad);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ /* Read the file path following the header. */
+ rc = rtZipCpioReaderReadPath(pThis->hVfsIos, &pThis->CpioReader, cbFilePath);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ if (cbPad)
+ RTVfsIoStrmSkip(pThis->hVfsIos, cbPad);
+ pThis->offNextHdr = offHdr + cbHdr + cbFilePath + cbPad;
+
+ /*
+ * CPIO uses a special trailer file record with a 0 mode and size and using a special
+ * marker filename. The filesystem stream is marked EOS When such a record is encountered
+ * to not try to read anything which might come behind it, imagine an initramfs image consisting
+ * of multiple archives which don't need to be necessarily be all of the CPIO kind (yes, this is
+ * a reality with ubuntu for example containing microcode updates as seperate CPIO archives
+ * coming before the main LZ4 compressed CPIO archive...).
+ */
+ PCRTFSOBJINFO pInfo = &pThis->CpioReader.ObjInfo;
+ if (RT_UNLIKELY( !pInfo->Attr.fMode
+ && !pInfo->cbAllocated
+ && !strcmp(&pThis->CpioReader.szName[0], CPIO_EOS_FILE_NAME)))
+ {
+ pThis->fEndOfStream = true;
+ return VERR_EOF;
+ }
+
+ /*
+ * Create an object of the appropriate type.
+ */
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ RTFMODE fType = pInfo->Attr.fMode & RTFS_TYPE_MASK;
+ switch (fType)
+ {
+ /*
+ * Files are represented by a VFS I/O stream, hardlinks have their content
+ * embedded as it is another file.
+ */
+ case RTFS_TYPE_FILE:
+ {
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPCPIOIOSTREAM pIosData;
+ rc = RTVfsNewIoStream(&g_rtZipCpioFssIosOps,
+ sizeof(*pIosData),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIos,
+ (void **)&pIosData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pIosData->BaseObj.offHdr = offHdr;
+ pIosData->BaseObj.offNextHdr = pThis->offNextHdr;
+ pIosData->BaseObj.pCpioReader = &pThis->CpioReader;
+ pIosData->BaseObj.ObjInfo = *pInfo;
+ pIosData->cbFile = pInfo->cbObject;
+ pIosData->offFile = 0;
+ pIosData->offStart = RTVfsIoStrmTell(pThis->hVfsIos);
+ pIosData->cbPadding = (uint32_t)(pInfo->cbAllocated - pInfo->cbObject);
+ pIosData->fEndOfStream = false;
+ pIosData->hVfsIos = pThis->hVfsIos;
+ RTVfsIoStrmRetain(pThis->hVfsIos);
+
+ pThis->pCurIosData = pIosData;
+ pThis->offNextHdr += pIosData->cbFile + pIosData->cbPadding;
+
+ enmType = RTVFSOBJTYPE_IO_STREAM;
+ hVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ break;
+ }
+
+ case RTFS_TYPE_SYMLINK:
+ {
+ RTVFSSYMLINK hVfsSym;
+ PRTZIPCPIOBASEOBJ pBaseObjData;
+ rc = RTVfsNewSymlink(&g_rtZipCpioFssSymOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsSym,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->offHdr = offHdr;
+ pBaseObjData->offNextHdr = pThis->offNextHdr;
+ pBaseObjData->pCpioReader = &pThis->CpioReader;
+ pBaseObjData->ObjInfo = *pInfo;
+
+ /* Read the body of the symlink (as normal file data). */
+ if (pInfo->cbObject + 1 > (RTFOFF)sizeof(pThis->CpioReader.szTarget))
+ return VERR_TAR_NAME_TOO_LONG;
+
+ cbPad = (uint32_t)(pInfo->cbAllocated - pInfo->cbObject);
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, &pThis->CpioReader.szTarget[0], pInfo->cbObject, true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ if (cbRead != (uint32_t)pInfo->cbObject)
+ return pThis->rcFatal = VERR_TAR_UNEXPECTED_EOS;
+
+ pThis->CpioReader.szTarget[pInfo->cbObject] = '\0';
+
+ if (cbPad)
+ rc = RTVfsIoStrmSkip(pThis->hVfsIos, cbPad);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pThis->offNextHdr += pInfo->cbAllocated;
+
+ enmType = RTVFSOBJTYPE_SYMLINK;
+ hVfsObj = RTVfsObjFromSymlink(hVfsSym);
+ RTVfsSymlinkRelease(hVfsSym);
+ break;
+ }
+
+ /*
+ * All other objects are repesented using a VFS base object since they
+ * carry no data streams.
+ */
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DIRECTORY:
+ case RTFS_TYPE_FIFO:
+ {
+ PRTZIPCPIOBASEOBJ pBaseObjData;
+ rc = RTVfsNewBaseObj(&g_rtZipCpioFssBaseObjOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsObj,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->offHdr = offHdr;
+ pBaseObjData->offNextHdr = pThis->offNextHdr;
+ pBaseObjData->pCpioReader = &pThis->CpioReader;
+ pBaseObjData->ObjInfo = *pInfo;
+
+ enmType = RTVFSOBJTYPE_BASE;
+ break;
+ }
+
+ default:
+ AssertFailed();
+ return pThis->rcFatal = VERR_INTERNAL_ERROR_5;
+ }
+ pThis->hVfsCurObj = hVfsObj;
+
+ /*
+ * Set the return data and we're done.
+ */
+ if (ppszName)
+ {
+ rc = RTStrDupEx(ppszName, pThis->CpioReader.szName);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (phVfsObj)
+ {
+ RTVfsObjRetain(hVfsObj);
+ *phVfsObj = hVfsObj;
+ }
+
+ if (penmType)
+ *penmType = enmType;
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * CPIO filesystem stream operations.
+ */
+static const RTVFSFSSTREAMOPS rtZipCpioFssOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "CpioFsStream",
+ rtZipCpioFss_Close,
+ rtZipCpioFss_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipCpioFss_Next,
+ NULL,
+ NULL,
+ NULL,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+/**
+ * Internal function use both by RTZipCpioFsStreamFromIoStream() and by
+ * RTZipCpioFsStreamForFile() in updating mode.
+ */
+DECLHIDDEN(void) rtZipCpioReaderInit(PRTZIPCPIOFSSTREAM pThis, RTVFSIOSTREAM hVfsIos, uint64_t offStart)
+{
+ pThis->hVfsIos = hVfsIos;
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+ pThis->offStart = offStart;
+ pThis->offNextHdr = offStart;
+ pThis->fEndOfStream = false;
+ pThis->rcFatal = VINF_SUCCESS;
+
+ /* Don't check if it's a CPIO stream here, do that in the
+ rtZipCpioFss_Next. */
+}
+
+
+RTDECL(int) RTZipCpioFsStreamFromIoStream(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);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPCPIOFSSTREAM pThis;
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&rtZipCpioFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_READ,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rtZipCpioReaderInit(pThis, hVfsIosIn, fFlags);
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosIn);
+ return rc;
+}
+
+
+/**
+ * Used by RTZipCpioFsStreamTruncate to resolve @a hVfsObj.
+ */
+DECLHIDDEN(PRTZIPCPIOBASEOBJ) rtZipCpioFsStreamBaseObjToPrivate(PRTZIPCPIOFSSTREAM pThis, RTVFSOBJ hVfsObj)
+{
+ PRTZIPCPIOBASEOBJ pThisObj;
+ RTVFSOBJTYPE enmType = RTVfsObjGetType(hVfsObj);
+ switch (enmType)
+ {
+ case RTVFSOBJTYPE_IO_STREAM:
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, NULL);
+ PRTZIPCPIOIOSTREAM pThisStrm = (PRTZIPCPIOIOSTREAM)RTVfsIoStreamToPrivate(hVfsIos, &g_rtZipCpioFssIosOps);
+ RTVfsIoStrmRelease(hVfsIos);
+ pThisObj = &pThisStrm->BaseObj;
+ break;
+ }
+
+ case RTVFSOBJTYPE_SYMLINK:
+ {
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, NULL);
+ pThisObj = (PRTZIPCPIOBASEOBJ)RTVfsSymlinkToPrivate(hVfsSymlink, &g_rtZipCpioFssSymOps);
+ RTVfsSymlinkRelease(hVfsSymlink);
+ break;
+ }
+
+ case RTVFSOBJTYPE_BASE:
+ pThisObj = (PRTZIPCPIOBASEOBJ)RTVfsObjToPrivate(hVfsObj, &g_rtZipCpioFssBaseObjOps);
+ break;
+
+ default:
+ /** @todo implement. */
+ AssertFailedReturn(NULL);
+ }
+
+ AssertReturn(pThisObj->pCpioReader == &pThis->CpioReader, NULL);
+ return pThisObj;
+}
+
diff --git a/src/VBox/Runtime/common/zip/cpiovfsreader.h b/src/VBox/Runtime/common/zip/cpiovfsreader.h
new file mode 100644
index 00000000..43db36e9
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/cpiovfsreader.h
@@ -0,0 +1,169 @@
+/* $Id: cpiovfsreader.h $ */
+/** @file
+ * IPRT - CPIO Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_zip_cpiovfsreader_h
+#define IPRT_INCLUDED_SRC_common_zip_cpiovfsreader_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/formats/cpio.h>
+
+
+/**
+ * CPIO archive type.
+ */
+typedef enum RTZIPCPIOTYPE
+{
+ /** Invalid type value. */
+ RTZIPCPIOTYPE_INVALID = 0,
+ /** Ancient binary archive. */
+ RTZIPCPIOTYPE_ANCIENT_BIN,
+ /** Portable ASCII format as defined by SuSV2. */
+ RTZIPCPIOTYPE_ASCII_SUSV2,
+ /** "New" ASCII format. */
+ RTZIPCPIOTYPE_ASCII_NEW,
+ /** "New" ASCII format with checksumming. */
+ RTZIPCPIOTYPE_ASCII_NEW_CHKSUM,
+ /** End of the valid type values (this is not valid). */
+ RTZIPCPIOTYPE_END,
+ /** The usual type blow up. */
+ RTZIPCPIOTYPE_32BIT_HACK = 0x7fffffff
+} RTZIPCPIOTYPE;
+typedef RTZIPCPIOTYPE *PRTZIPCPIOTYPE;
+
+
+/**
+ * CPIO reader instance data.
+ */
+typedef struct RTZIPCPIOREADER
+{
+ /** The object info with unix attributes. */
+ RTFSOBJINFO ObjInfo;
+ /** The path length. */
+ uint32_t cbPath;
+ /** The name of the current object. */
+ char szName[RTPATH_MAX];
+ /** The current link target if symlink. */
+ char szTarget[RTPATH_MAX];
+} RTZIPCPIOREADER;
+/** Pointer to the CPIO reader instance data. */
+typedef RTZIPCPIOREADER *PRTZIPCPIOREADER;
+
+/**
+ * CPIO directory, character device, block device, fifo socket or symbolic link.
+ */
+typedef struct RTZIPCPIOBASEOBJ
+{
+ /** The stream offset of the (first) header in the input stream/file. */
+ RTFOFF offHdr;
+ /** The stream offset of the first header of the next object (for truncating the
+ * tar file after this object (updating)). */
+ RTFOFF offNextHdr;
+ /** Pointer to the reader instance data (resides in the filesystem
+ * stream).
+ * @todo Fix this so it won't go stale... Back ref from this obj to fss? */
+ PRTZIPCPIOREADER pCpioReader;
+ /** The object info with unix attributes. */
+ RTFSOBJINFO ObjInfo;
+} RTZIPCPIOBASEOBJ;
+/** Pointer to a CPIO filesystem stream base object. */
+typedef RTZIPCPIOBASEOBJ *PRTZIPCPIOBASEOBJ;
+
+
+/**
+ * CPIO file represented as a VFS I/O stream.
+ */
+typedef struct RTZIPCPIOIOSTREAM
+{
+ /** The basic TAR object data. */
+ RTZIPCPIOBASEOBJ BaseObj;
+ /** The number of bytes in the file. */
+ RTFOFF cbFile;
+ /** The current file position. */
+ RTFOFF offFile;
+ /** The start position in the hVfsIos (for seekable hVfsIos). */
+ RTFOFF offStart;
+ /** The number of padding bytes following the file. */
+ uint32_t cbPadding;
+ /** Set if we've reached the end of this file. */
+ bool fEndOfStream;
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+} RTZIPCPIOIOSTREAM;
+/** Pointer to a the private data of a CPIO file I/O stream. */
+typedef RTZIPCPIOIOSTREAM *PRTZIPCPIOIOSTREAM;
+
+
+/**
+ * CPIO filesystem stream private data.
+ */
+typedef struct RTZIPCPIOFSSTREAM
+{
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+
+ /** The current object (referenced). */
+ RTVFSOBJ hVfsCurObj;
+ /** Pointer to the private data if hVfsCurObj is representing a file. */
+ PRTZIPCPIOIOSTREAM pCurIosData;
+
+ /** The start offset. */
+ RTFOFF offStart;
+ /** The offset of the next header. */
+ RTFOFF offNextHdr;
+ /** The offset of the first header for the current object.
+ * When reaching the end, this will be the same as offNextHdr which will be
+ * pointing to the first zero header */
+ RTFOFF offCurHdr;
+
+ /** Set if we've reached the end of the stream. */
+ bool fEndOfStream;
+ /** Set if we've encountered a fatal error. */
+ int rcFatal;
+
+ /** The CPIO reader instance data. */
+ RTZIPCPIOREADER CpioReader;
+} RTZIPCPIOFSSTREAM;
+/** Pointer to a the private data of a CPIO filesystem stream. */
+typedef RTZIPCPIOFSSTREAM *PRTZIPCPIOFSSTREAM;
+
+DECLHIDDEN(void) rtZipCpioReaderInit(PRTZIPCPIOFSSTREAM pThis, RTVFSIOSTREAM hVfsIos, uint64_t offStart);
+DECL_HIDDEN_CALLBACK(int) rtZipCpioFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj);
+DECLHIDDEN(PRTZIPCPIOBASEOBJ) rtZipCpioFsStreamBaseObjToPrivate(PRTZIPCPIOFSSTREAM pThis, RTVFSOBJ hVfsObj);
+
+#endif /* !IPRT_INCLUDED_SRC_common_zip_cpiovfsreader_h */
+
diff --git a/src/VBox/Runtime/common/zip/gzipcmd.cpp b/src/VBox/Runtime/common/zip/gzipcmd.cpp
new file mode 100644
index 00000000..0c00bf24
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/gzipcmd.cpp
@@ -0,0 +1,606 @@
+/* $Id: gzipcmd.cpp $ */
+/** @file
+ * IPRT - GZIP Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Gzip command options.
+ */
+typedef struct RTGZIPCMDOPTS
+{
+ bool fAscii;
+ bool fStdOut;
+ bool fDecompress;
+ bool fForce;
+ bool fKeep;
+ bool fList;
+ bool fName;
+ bool fQuiet;
+ bool fRecursive;
+ const char *pszSuff;
+ bool fTest;
+ unsigned uLevel;
+ /** The current output filename (for deletion). */
+ char szOutput[RTPATH_MAX];
+ /** The current input filename (for deletion and messages). */
+ const char *pszInput;
+} RTGZIPCMDOPTS;
+/** Pointer to GZIP options. */
+typedef RTGZIPCMDOPTS *PRTGZIPCMDOPTS;
+/** Pointer to const GZIP options. */
+typedef RTGZIPCMDOPTS const *PCRTGZIPCMDOPTS;
+
+
+
+/**
+ * Checks if the given standard handle is a TTY.
+ *
+ * @returns true / false
+ * @param enmStdHandle The standard handle.
+ */
+static bool gzipIsStdHandleATty(RTHANDLESTD enmStdHandle)
+{
+ /** @todo Add isatty() to IPRT. */
+ RT_NOREF1(enmStdHandle);
+ return false;
+}
+
+
+/**
+ * Pushes data from the input to the output I/O streams.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE.
+ * @param hVfsSrc The source I/O stream.
+ * @param hVfsDst The destination I/O stream.
+ */
+static RTEXITCODE gzipPush(RTVFSIOSTREAM hVfsSrc, RTVFSIOSTREAM hVfsDst)
+{
+ for (;;)
+ {
+ uint8_t abBuf[_64K];
+ size_t cbRead;
+ int rc = RTVfsIoStrmRead(hVfsSrc, abBuf, sizeof(abBuf), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmRead failed: %Rrc", rc);
+ if (rc == VINF_EOF && cbRead == 0)
+ return RTEXITCODE_SUCCESS;
+
+ rc = RTVfsIoStrmWrite(hVfsDst, abBuf, cbRead, true /*fBlocking*/, NULL /*cbWritten*/);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmWrite failed: %Rrc", rc);
+ }
+}
+
+
+/**
+ * Pushes the bytes from the input to the output stream, flushes the output
+ * stream and closes both of them.
+ *
+ * On failure, we will delete the output file, if it's a file. The input file
+ * may be deleted, if we're not told to keep it (--keep, --to-stdout).
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE.
+ * @param phVfsSrc The input stream. Set to NIL if closed.
+ * @param pOpts The options.
+ * @param phVfsDst The output stream. Set to NIL if closed.
+ */
+static RTEXITCODE gzipPushFlushAndClose(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsDst)
+{
+ /*
+ * Push bytes, flush and close the streams.
+ */
+ RTEXITCODE rcExit = gzipPush(*phVfsSrc, *phVfsDst);
+
+ RTVfsIoStrmRelease(*phVfsSrc);
+ *phVfsSrc = NIL_RTVFSIOSTREAM;
+
+ int rc = RTVfsIoStrmFlush(*phVfsDst);
+ if (RT_FAILURE(rc) && rc != VERR_INVALID_PARAMETER)
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to flush the output file: %Rrc", rc);
+ RTVfsIoStrmRelease(*phVfsDst);
+ *phVfsDst = NIL_RTVFSIOSTREAM;
+
+ /*
+ * Do the cleaning up, if needed. Remove the input file, if that's the
+ * desire of the user, or remove the output file on failure.
+ */
+ if (!pOpts->fStdOut)
+ {
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (!pOpts->fKeep)
+ {
+ rc = RTFileDelete(pOpts->pszInput);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to delete '%s': %Rrc", pOpts->pszInput, rc);
+ }
+ }
+ else
+ {
+ rc = RTFileDelete(pOpts->szOutput);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to delete '%s': %Rrc", pOpts->szOutput, rc);
+ }
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Compresses one stream to another.
+ *
+ * @returns Exit code.
+ * @param phVfsSrc The input stream. Set to NIL if closed.
+ * @param pOpts The options.
+ * @param phVfsDst The output stream. Set to NIL if closed.
+ */
+static RTEXITCODE gzipCompressFile(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsDst)
+{
+ /*
+ * Attach the ompressor to the output stream.
+ */
+ RTVFSIOSTREAM hVfsGzip;
+ int rc = RTZipGzipCompressIoStream(*phVfsDst, 0 /*fFlags*/, pOpts->uLevel, &hVfsGzip);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTZipGzipCompressIoStream failed: %Rrc", rc);
+
+ uint32_t cRefs = RTVfsIoStrmRelease(*phVfsDst);
+ Assert(cRefs > 0); RT_NOREF_PV(cRefs);
+ *phVfsDst = hVfsGzip;
+
+ return gzipPushFlushAndClose(phVfsSrc, pOpts, phVfsDst);
+}
+
+
+/**
+ * Attach a decompressor to the given source stream, replacing and releasing the
+ * input handle with the decompressor.
+ *
+ * @returns Exit code.
+ * @param phVfsSrc The input stream. Replaced on success.
+ */
+static RTEXITCODE gzipSetupDecompressor(PRTVFSIOSTREAM phVfsSrc)
+{
+ /*
+ * Attach the decompressor to the input stream.
+ */
+ uint32_t fFlags = 0;
+ fFlags |= RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR;
+ RTVFSIOSTREAM hVfsGunzip;
+ int rc = RTZipGzipDecompressIoStream(*phVfsSrc, fFlags, &hVfsGunzip);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTZipGzipDecompressIoStream failed: %Rrc", rc);
+
+ uint32_t cRefs = RTVfsIoStrmRelease(*phVfsSrc);
+ Assert(cRefs > 0); RT_NOREF_PV(cRefs);
+ *phVfsSrc = hVfsGunzip;
+
+#if 0
+ /* This is a good place for testing stuff. */
+ rc = RTVfsCreateReadAheadForIoStream(*phVfsSrc, 0, 16, _4K+1, &hVfsGunzip);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cRefs = RTVfsIoStrmRelease(*phVfsSrc);
+ Assert(cRefs > 0);
+ *phVfsSrc = hVfsGunzip;
+ }
+#endif
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Decompresses one stream to another.
+ *
+ * @returns Exit code.
+ * @param phVfsSrc The input stream. Set to NIL if closed.
+ * @param pOpts The options.
+ * @param phVfsDst The output stream. Set to NIL if closed.
+ */
+static RTEXITCODE gzipDecompressFile(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsDst)
+{
+ RTEXITCODE rcExit = gzipSetupDecompressor(phVfsSrc);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = gzipPushFlushAndClose(phVfsSrc, pOpts, phVfsDst);
+ return rcExit;
+}
+
+
+/**
+ * For testing the archive (todo).
+ *
+ * @returns Exit code.
+ * @param phVfsSrc The input stream. Set to NIL if closed.
+ * @param pOpts The options.
+ */
+static RTEXITCODE gzipTestFile(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts)
+{
+ RT_NOREF_PV(pOpts);
+
+ /*
+ * Read the whole stream.
+ */
+ RTEXITCODE rcExit = gzipSetupDecompressor(phVfsSrc);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ for (;;)
+ {
+ uint8_t abBuf[_64K];
+ size_t cbRead;
+ int rc = RTVfsIoStrmRead(*phVfsSrc, abBuf, sizeof(abBuf), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmRead failed: %Rrc", rc);
+ if (rc == VINF_EOF && cbRead == 0)
+ return RTEXITCODE_SUCCESS;
+ }
+ }
+ return rcExit;
+}
+
+
+static RTEXITCODE gzipListFile(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts)
+{
+ RT_NOREF2(phVfsSrc, pOpts);
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Listing has not been implemented");
+}
+
+
+/**
+ * Opens the output file.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ *
+ * @param pszFile The input filename.
+ * @param pOpts The options, szOutput will be filled in by this
+ * function on success.
+ * @param phVfsIos Where to return the output stream handle.
+ *
+ * @remarks This is actually not quite the way we need to do things.
+ *
+ * First of all, we need a GZIP file system stream for a real GZIP
+ * implementation, since there may be more than one file in the gzipped
+ * file.
+ *
+ * Second, we need to open the output files as we encounter files in the input
+ * file system stream. The gzip format contains timestamp and usually a
+ * filename, the default is to use this name (see the --no-name
+ * option).
+ */
+static RTEXITCODE gzipOpenOutput(const char *pszFile, PRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsIos)
+{
+ int rc;
+ if (!strcmp(pszFile, "-") || pOpts->fStdOut)
+ {
+ strcpy(pOpts->szOutput, "-");
+
+ if ( !pOpts->fForce
+ && !pOpts->fDecompress
+ && gzipIsStdHandleATty(RTHANDLESTD_OUTPUT))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Yeah, right. I'm not writing any compressed data to the terminal without --force.\n");
+
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT,
+ RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ true /*fLeaveOpen*/,
+ phVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening standard output: %Rrc", rc);
+ }
+ else
+ {
+ Assert(!RTVfsChainIsSpec(pszFile));
+
+ /* Construct an output filename. */
+ rc = RTStrCopy(pOpts->szOutput, sizeof(pOpts->szOutput), pszFile);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error constructing output filename: %Rrc", rc);
+ if (pOpts->fDecompress)
+ {
+ /** @todo take filename from archive? */
+ size_t cchSuff = strlen(pOpts->pszSuff); Assert(cchSuff > 0);
+ size_t cch = strlen(pOpts->szOutput);
+ if ( cch <= cchSuff
+ || strcmp(&pOpts->szOutput[cch - cchSuff], pOpts->pszSuff))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Input file does not end with: '%s'", pOpts->pszSuff);
+ pOpts->szOutput[cch - cchSuff] = '\0';
+ if (!RTPathFilename(pOpts->szOutput))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error constructing output filename: Input file name is all suffix.");
+ }
+ else
+ {
+ rc = RTStrCat(pOpts->szOutput, sizeof(pOpts->szOutput), pOpts->pszSuff);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error constructing output filename: %Rrc", rc);
+ }
+
+ /* Open the output file. */
+ uint32_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
+ if (pOpts->fForce)
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+ else
+ fOpen |= RTFILE_O_CREATE;
+ rc = RTVfsIoStrmOpenNormal(pOpts->szOutput, fOpen, phVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening output file '%s': %Rrc", pOpts->szOutput, rc);
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Opens the input file.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ *
+ * @param pszFile The input filename.
+ * @param pOpts The options, szOutput will be filled in by this
+ * function on success.
+ * @param phVfsIos Where to return the input stream handle.
+ */
+static RTEXITCODE gzipOpenInput(const char *pszFile, PRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsIos)
+{
+ int rc;
+
+ pOpts->pszInput = pszFile;
+ if (!strcmp(pszFile, "-"))
+ {
+ if ( !pOpts->fForce
+ && pOpts->fDecompress
+ && gzipIsStdHandleATty(RTHANDLESTD_OUTPUT))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Yeah, right. I'm not reading any compressed data from the terminal without --force.\n");
+
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ true /*fLeaveOpen*/,
+ phVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening standard input: %Rrc", rc);
+ }
+ else
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE,
+ phVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszFile, rc, offError, &ErrInfo.Core);
+ }
+
+ return RTEXITCODE_SUCCESS;
+
+}
+
+
+/**
+ * A mini GZIP program.
+ *
+ * @returns Program exit code.
+ *
+ * @param cArgs The number of arguments.
+ * @param papszArgs The argument vector. (Note that this may be
+ * reordered, so the memory must be writable.)
+ */
+RTDECL(RTEXITCODE) RTZipGzipCmd(unsigned cArgs, char **papszArgs)
+{
+
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--ascii", 'a', RTGETOPT_REQ_NOTHING },
+ { "--stdout", 'c', RTGETOPT_REQ_NOTHING },
+ { "--to-stdout", 'c', RTGETOPT_REQ_NOTHING },
+ { "--decompress", 'd', RTGETOPT_REQ_NOTHING },
+ { "--uncompress", 'd', RTGETOPT_REQ_NOTHING },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ { "--keep", 'k', RTGETOPT_REQ_NOTHING },
+ { "--list", 'l', RTGETOPT_REQ_NOTHING },
+ { "--no-name", 'n', RTGETOPT_REQ_NOTHING },
+ { "--name", 'N', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ { "--recursive", 'r', RTGETOPT_REQ_NOTHING },
+ { "--suffix", 'S', RTGETOPT_REQ_STRING },
+ { "--test", 't', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--fast", '1', RTGETOPT_REQ_NOTHING },
+ { "-1", '1', RTGETOPT_REQ_NOTHING },
+ { "-2", '2', RTGETOPT_REQ_NOTHING },
+ { "-3", '3', RTGETOPT_REQ_NOTHING },
+ { "-4", '4', RTGETOPT_REQ_NOTHING },
+ { "-5", '5', RTGETOPT_REQ_NOTHING },
+ { "-6", '6', RTGETOPT_REQ_NOTHING },
+ { "-7", '7', RTGETOPT_REQ_NOTHING },
+ { "-8", '8', RTGETOPT_REQ_NOTHING },
+ { "-9", '9', RTGETOPT_REQ_NOTHING },
+ { "--best", '9', RTGETOPT_REQ_NOTHING }
+ };
+
+ RTGZIPCMDOPTS Opts;
+ Opts.fAscii = false;
+ Opts.fStdOut = false;
+ Opts.fDecompress = false;
+ Opts.fForce = false;
+ Opts.fKeep = false;
+ Opts.fList = false;
+ Opts.fName = true;
+ Opts.fQuiet = false;
+ Opts.fRecursive = false;
+ Opts.pszSuff = ".gz";
+ Opts.fTest = false;
+ Opts.uLevel = 6;
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned cProcessed = 0;
+ //RTVFSIOSTREAM hVfsStdOut= NIL_RTVFSIOSTREAM;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+
+ for (;;)
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ /*
+ * If we've processed any files we're done. Otherwise take
+ * input from stdin and write the output to stdout.
+ */
+ if (cProcessed > 0)
+ return rcExit;
+ ValueUnion.psz = "-";
+ Opts.fStdOut = true;
+ RT_FALL_THRU();
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ if (!*Opts.pszSuff && !Opts.fStdOut)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --suffix option specified an empty string");
+ if (!Opts.fStdOut && RTVfsChainIsSpec(ValueUnion.psz))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Must use standard out with VFS chain specifications");
+ if ( Opts.fName
+ && !Opts.fList
+ && !Opts.fTest
+ && !Opts.fDecompress)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --name option has not yet been implemented. Use --no-name.");
+ if (Opts.fAscii)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --ascii option has not yet been implemented.");
+ if (Opts.fRecursive)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --recursive option has not yet been implemented.");
+
+ /* Open the input file. */
+ RTVFSIOSTREAM hVfsSrc;
+ RTEXITCODE rcExit2 = gzipOpenInput(ValueUnion.psz, &Opts, &hVfsSrc);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ if (Opts.fList)
+ rcExit2 = gzipListFile(&hVfsSrc, &Opts);
+ else if (Opts.fTest)
+ rcExit2 = gzipTestFile(&hVfsSrc, &Opts);
+ else
+ {
+ RTVFSIOSTREAM hVfsDst;
+ rcExit2 = gzipOpenOutput(ValueUnion.psz, &Opts, &hVfsDst);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ if (Opts.fDecompress)
+ rcExit2 = gzipDecompressFile(&hVfsSrc, &Opts, &hVfsDst);
+ else
+ rcExit2 = gzipCompressFile(&hVfsSrc, &Opts, &hVfsDst);
+ RTVfsIoStrmRelease(hVfsDst);
+ }
+ }
+ RTVfsIoStrmRelease(hVfsSrc);
+ }
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+
+ cProcessed++;
+ break;
+ }
+
+ case 'a': Opts.fAscii = true; break;
+ case 'c':
+ Opts.fStdOut = true;
+ Opts.fKeep = true;
+ break;
+ case 'd': Opts.fDecompress = true; break;
+ case 'f': Opts.fForce = true; break;
+ case 'k': Opts.fKeep = true; break;
+ case 'l': Opts.fList = true; break;
+ case 'n': Opts.fName = false; break;
+ case 'N': Opts.fName = true; break;
+ case 'q': Opts.fQuiet = true; break;
+ case 'r': Opts.fRecursive = true; break;
+ case 'S': Opts.pszSuff = ValueUnion.psz; break;
+ case 't': Opts.fTest = true; break;
+ case 'v': Opts.fQuiet = false; break;
+ case '1': Opts.uLevel = 1; break;
+ case '2': Opts.uLevel = 2; break;
+ case '3': Opts.uLevel = 3; break;
+ case '4': Opts.uLevel = 4; break;
+ case '5': Opts.uLevel = 5; break;
+ case '6': Opts.uLevel = 6; break;
+ case '7': Opts.uLevel = 7; break;
+ case '8': Opts.uLevel = 8; break;
+ case '9': Opts.uLevel = 9; break;
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(chOpt, &ValueUnion);
+ }
+ }
+}
+
diff --git a/src/VBox/Runtime/common/zip/gzipvfs.cpp b/src/VBox/Runtime/common/zip/gzipvfs.cpp
new file mode 100644
index 00000000..de38938a
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/gzipvfs.cpp
@@ -0,0 +1,1035 @@
+/* $Id: gzipvfs.cpp $ */
+/** @file
+ * IPRT - GZIP Compressor and Decompressor I/O Stream.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/vfslowlevel.h>
+
+#include <zlib.h>
+
+#if defined(RT_OS_OS2) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS)
+/**
+ * Drag in the missing zlib symbols.
+ */
+PFNRT g_apfnRTZlibDeps[] =
+{
+ (PFNRT)gzrewind,
+ (PFNRT)gzread,
+ (PFNRT)gzopen,
+ (PFNRT)gzwrite,
+ (PFNRT)gzclose,
+ (PFNRT)gzdopen,
+ NULL
+};
+#endif /* RT_OS_OS2 || RT_OS_SOLARIS || RT_OS_WINDOWS */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+#pragma pack(1)
+typedef struct RTZIPGZIPHDR
+{
+ /** RTZIPGZIPHDR_ID1. */
+ uint8_t bId1;
+ /** RTZIPGZIPHDR_ID2. */
+ uint8_t bId2;
+ /** CM - The compression method. */
+ uint8_t bCompressionMethod;
+ /** FLG - Flags. */
+ uint8_t fFlags;
+ /** Modification time of the source file or the timestamp at the time the
+ * compression took place. Can also be zero. Is the number of seconds since
+ * unix epoch. */
+ uint32_t u32ModTime;
+ /** Flags specific to the compression method. */
+ uint8_t bXtraFlags;
+ /** An ID indicating which OS or FS gzip ran on. */
+ uint8_t bOS;
+} RTZIPGZIPHDR;
+#pragma pack()
+AssertCompileSize(RTZIPGZIPHDR, 10);
+/** Pointer to a const gzip header. */
+typedef RTZIPGZIPHDR const *PCRTZIPGZIPHDR;
+
+/** gzip header identification no 1. */
+#define RTZIPGZIPHDR_ID1 0x1f
+/** gzip header identification no 2. */
+#define RTZIPGZIPHDR_ID2 0x8b
+/** gzip deflate compression method. */
+#define RTZIPGZIPHDR_CM_DEFLATE 8
+
+/** @name gzip header flags
+ * @{ */
+/** Probably a text file */
+#define RTZIPGZIPHDR_FLG_TEXT UINT8_C(0x01)
+/** Header CRC present (crc32 of header cast to uint16_t). */
+#define RTZIPGZIPHDR_FLG_HDR_CRC UINT8_C(0x02)
+/** Length prefixed xtra field is present. */
+#define RTZIPGZIPHDR_FLG_EXTRA UINT8_C(0x04)
+/** A name field is present (latin-1). */
+#define RTZIPGZIPHDR_FLG_NAME UINT8_C(0x08)
+/** A comment field is present (latin-1). */
+#define RTZIPGZIPHDR_FLG_COMMENT UINT8_C(0x10)
+/** Mask of valid flags. */
+#define RTZIPGZIPHDR_FLG_VALID_MASK UINT8_C(0x1f)
+/** @} */
+
+/** @name gzip default xtra flag values
+ * @{ */
+#define RTZIPGZIPHDR_XFL_DEFLATE_MAX UINT8_C(0x02)
+#define RTZIPGZIPHDR_XFL_DEFLATE_FASTEST UINT8_C(0x04)
+/** @} */
+
+/** @name Operating system / Filesystem IDs
+ * @{ */
+#define RTZIPGZIPHDR_OS_FAT UINT8_C(0x00)
+#define RTZIPGZIPHDR_OS_AMIGA UINT8_C(0x01)
+#define RTZIPGZIPHDR_OS_VMS UINT8_C(0x02)
+#define RTZIPGZIPHDR_OS_UNIX UINT8_C(0x03)
+#define RTZIPGZIPHDR_OS_VM_CMS UINT8_C(0x04)
+#define RTZIPGZIPHDR_OS_ATARIS_TOS UINT8_C(0x05)
+#define RTZIPGZIPHDR_OS_HPFS UINT8_C(0x06)
+#define RTZIPGZIPHDR_OS_MACINTOSH UINT8_C(0x07)
+#define RTZIPGZIPHDR_OS_Z_SYSTEM UINT8_C(0x08)
+#define RTZIPGZIPHDR_OS_CPM UINT8_C(0x09)
+#define RTZIPGZIPHDR_OS_TOPS_20 UINT8_C(0x0a)
+#define RTZIPGZIPHDR_OS_NTFS UINT8_C(0x0b)
+#define RTZIPGZIPHDR_OS_QDOS UINT8_C(0x0c)
+#define RTZIPGZIPHDR_OS_ACORN_RISCOS UINT8_C(0x0d)
+#define RTZIPGZIPHDR_OS_UNKNOWN UINT8_C(0xff)
+/** @} */
+
+
+/**
+ * The internal data of a GZIP I/O stream.
+ */
+typedef struct RTZIPGZIPSTREAM
+{
+ /** The stream we're reading or writing the compressed data from or to. */
+ RTVFSIOSTREAM hVfsIos;
+ /** Set if it's a decompressor, clear if it's a compressor. */
+ bool fDecompress;
+ /** Set if zlib reported a fatal error. */
+ bool fFatalError;
+ /** Set if we've reached the end of the zlib stream. */
+ bool fEndOfStream;
+ /** The stream offset for pfnTell, always the uncompressed data. */
+ RTFOFF offStream;
+ /** The zlib stream. */
+ z_stream Zlib;
+ /** The data buffer. */
+ uint8_t abBuffer[_64K];
+ /** Scatter gather segment describing abBuffer. */
+ RTSGSEG SgSeg;
+ /** Scatter gather buffer describing abBuffer. */
+ RTSGBUF SgBuf;
+ /** The original file name (decompressor only). */
+ char *pszOrgName;
+ /** The comment (decompressor only). */
+ char *pszComment;
+ /** The gzip header. */
+ RTZIPGZIPHDR Hdr;
+} RTZIPGZIPSTREAM;
+/** Pointer to a the internal data of a GZIP I/O stream. */
+typedef RTZIPGZIPSTREAM *PRTZIPGZIPSTREAM;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtZipGzip_FlushIt(PRTZIPGZIPSTREAM pThis, uint8_t fFlushType);
+
+
+/**
+ * Convert from zlib to IPRT status codes.
+ *
+ * This will also set the fFatalError flag when appropriate.
+ *
+ * @returns IPRT status code.
+ * @param pThis The gzip I/O stream instance data.
+ * @param rc Zlib error code.
+ */
+static int rtZipGzipConvertErrFromZlib(PRTZIPGZIPSTREAM pThis, int rc)
+{
+ switch (rc)
+ {
+ case Z_OK:
+ return VINF_SUCCESS;
+
+ case Z_BUF_ERROR:
+ /* This isn't fatal. */
+ return VINF_SUCCESS; /** @todo The code in zip.cpp treats Z_BUF_ERROR as fatal... */
+
+ case Z_STREAM_ERROR:
+ pThis->fFatalError = true;
+ return VERR_ZIP_CORRUPTED;
+
+ case Z_DATA_ERROR:
+ pThis->fFatalError = true;
+ return pThis->fDecompress ? VERR_ZIP_CORRUPTED : VERR_ZIP_ERROR;
+
+ case Z_MEM_ERROR:
+ pThis->fFatalError = true;
+ return VERR_ZIP_NO_MEMORY;
+
+ case Z_VERSION_ERROR:
+ pThis->fFatalError = true;
+ return VERR_ZIP_UNSUPPORTED_VERSION;
+
+ case Z_ERRNO: /* We shouldn't see this status! */
+ default:
+ AssertMsgFailed(("%d\n", rc));
+ if (rc >= 0)
+ return VINF_SUCCESS;
+ pThis->fFatalError = true;
+ return VERR_ZIP_ERROR;
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipGzip_Close(void *pvThis)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+
+ int rc;
+ if (pThis->fDecompress)
+ {
+ rc = inflateEnd(&pThis->Zlib);
+ if (rc != Z_OK)
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc);
+ }
+ else
+ {
+ /* Flush the compression stream before terminating it. */
+ rc = VINF_SUCCESS;
+ if (!pThis->fFatalError)
+ rc = rtZipGzip_FlushIt(pThis, Z_FINISH);
+
+ int rc2 = deflateEnd(&pThis->Zlib);
+ if (RT_SUCCESS(rc) && rc2 != Z_OK)
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc);
+ }
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+ RTStrFree(pThis->pszOrgName);
+ pThis->pszOrgName = NULL;
+ RTStrFree(pThis->pszComment);
+ pThis->pszComment = NULL;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipGzip_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+ return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * Reads one segment.
+ *
+ * @returns IPRT status code.
+ * @param pThis The gzip I/O stream instance data.
+ * @param pvBuf Where to put the read bytes.
+ * @param cbToRead The number of bytes to read.
+ * @param fBlocking Whether to block or not.
+ * @param pcbRead Where to store the number of bytes actually read.
+ */
+static int rtZipGzip_ReadOneSeg(PRTZIPGZIPSTREAM pThis, void *pvBuf, size_t cbToRead, bool fBlocking, size_t *pcbRead)
+{
+ /*
+ * This simplifies life a wee bit below.
+ */
+ if (pThis->fEndOfStream)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+ /*
+ * Set up the output buffer.
+ */
+ pThis->Zlib.next_out = (Bytef *)pvBuf;
+ pThis->Zlib.avail_out = (uInt)cbToRead;
+ AssertReturn(pThis->Zlib.avail_out == cbToRead, VERR_OUT_OF_RANGE);
+
+ /*
+ * Be greedy reading input, even if no output buffer is left. It's possible
+ * that it's just the end of stream marker which needs to be read. Happens
+ * for incompressible blocks just larger than the input buffer size.
+ */
+ int rc = VINF_SUCCESS;
+ while ( pThis->Zlib.avail_out > 0
+ || pThis->Zlib.avail_in == 0 /* greedy */)
+ {
+ /*
+ * Read more input?
+ *
+ * N.B. The assertions here validate the RTVfsIoStrmSgRead behavior
+ * since the API is new and untested. They could be removed later
+ * but, better leaving them in.
+ */
+ if (pThis->Zlib.avail_in == 0)
+ {
+ size_t cbReadIn = ~(size_t)0;
+ rc = RTVfsIoStrmSgRead(pThis->hVfsIos, -1 /*off*/, &pThis->SgBuf, fBlocking, &cbReadIn);
+ if (rc != VINF_SUCCESS)
+ {
+ AssertMsg(RT_FAILURE(rc) || rc == VINF_TRY_AGAIN || rc == VINF_EOF, ("%Rrc\n", rc));
+ if (rc == VERR_INTERRUPTED)
+ {
+ Assert(cbReadIn == 0);
+ continue;
+ }
+ if (RT_FAILURE(rc) || rc == VINF_TRY_AGAIN || cbReadIn == 0)
+ {
+ Assert(cbReadIn == 0);
+ break;
+ }
+ AssertMsg(rc == VINF_EOF, ("%Rrc\n", rc));
+ }
+ AssertMsgBreakStmt(cbReadIn > 0 && cbReadIn <= sizeof(pThis->abBuffer), ("%zu %Rrc\n", cbReadIn, rc),
+ rc = VERR_INTERNAL_ERROR_4);
+
+ pThis->Zlib.avail_in = (uInt)cbReadIn;
+ pThis->Zlib.next_in = &pThis->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ rc = inflate(&pThis->Zlib, Z_NO_FLUSH);
+ if (rc != Z_OK && rc != Z_BUF_ERROR)
+ {
+ if (rc == Z_STREAM_END)
+ {
+ pThis->fEndOfStream = true;
+ if (pThis->Zlib.avail_out == 0)
+ rc = VINF_SUCCESS;
+ else
+ rc = pcbRead ? VINF_EOF : VERR_EOF;
+ }
+ else
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc);
+ break;
+ }
+ rc = VINF_SUCCESS;
+ }
+
+ /*
+ * Update the read counters before returning.
+ */
+ size_t const cbRead = cbToRead - pThis->Zlib.avail_out;
+ pThis->offStream += cbRead;
+ if (pcbRead)
+ *pcbRead = cbRead;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipGzip_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+
+ Assert(pSgBuf->cSegs == 1);
+ if (!pThis->fDecompress)
+ return VERR_ACCESS_DENIED;
+ AssertReturn(off == -1 || off == pThis->offStream , VERR_INVALID_PARAMETER);
+
+ return rtZipGzip_ReadOneSeg(pThis, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, fBlocking, pcbRead);
+}
+
+
+/**
+ * Internal helper for rtZipGzip_Write, rtZipGzip_Flush and rtZipGzip_Close.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS
+ * @retval VINF_TRY_AGAIN - the only informational status.
+ * @retval VERR_INTERRUPTED - call again.
+ *
+ * @param pThis The gzip I/O stream instance data.
+ * @param fBlocking Whether to block or not.
+ */
+static int rtZipGzip_WriteOutputBuffer(PRTZIPGZIPSTREAM pThis, bool fBlocking)
+{
+ /*
+ * Anything to write? No, then just return immediately.
+ */
+ size_t cbToWrite = sizeof(pThis->abBuffer) - pThis->Zlib.avail_out;
+ if (cbToWrite == 0)
+ {
+ Assert(pThis->Zlib.next_out == &pThis->abBuffer[0]);
+ return VINF_SUCCESS;
+ }
+ Assert(cbToWrite <= sizeof(pThis->abBuffer));
+
+ /*
+ * Loop write on VERR_INTERRUPTED.
+ *
+ * Note! Asserting a bit extra here to make sure the
+ * RTVfsIoStrmSgWrite works correctly.
+ */
+ int rc;
+ size_t cbWrittenOut;
+ for (;;)
+ {
+ /* Set up the buffer. */
+ pThis->SgSeg.cbSeg = cbToWrite;
+ Assert(pThis->SgSeg.pvSeg == &pThis->abBuffer[0]);
+ RTSgBufReset(&pThis->SgBuf);
+
+ cbWrittenOut = ~(size_t)0;
+ rc = RTVfsIoStrmSgWrite(pThis->hVfsIos, -1 /*off*/, &pThis->SgBuf, fBlocking, &cbWrittenOut);
+ if (rc != VINF_SUCCESS)
+ {
+ AssertMsg(RT_FAILURE(rc) || rc == VINF_TRY_AGAIN, ("%Rrc\n", rc));
+ if (rc == VERR_INTERRUPTED)
+ {
+ Assert(cbWrittenOut == 0);
+ continue;
+ }
+ if (RT_FAILURE(rc) || rc == VINF_TRY_AGAIN || cbWrittenOut == 0)
+ {
+ AssertReturn(cbWrittenOut == 0, VERR_INTERNAL_ERROR_3);
+ AssertReturn(rc != VINF_SUCCESS, VERR_IPE_UNEXPECTED_INFO_STATUS);
+ return rc;
+ }
+ }
+ break;
+ }
+ AssertMsgReturn(cbWrittenOut > 0 && cbWrittenOut <= sizeof(pThis->abBuffer),
+ ("%zu %Rrc\n", cbWrittenOut, rc),
+ VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Adjust the Zlib output buffer members.
+ */
+ if (cbWrittenOut == pThis->SgBuf.paSegs[0].cbSeg)
+ {
+ pThis->Zlib.avail_out = sizeof(pThis->abBuffer);
+ pThis->Zlib.next_out = &pThis->abBuffer[0];
+ }
+ else
+ {
+ Assert(cbWrittenOut <= pThis->SgBuf.paSegs[0].cbSeg);
+ size_t cbLeft = pThis->SgBuf.paSegs[0].cbSeg - cbWrittenOut;
+ memmove(&pThis->abBuffer[0], &pThis->abBuffer[cbWrittenOut], cbLeft);
+ pThis->Zlib.avail_out += (uInt)cbWrittenOut;
+ pThis->Zlib.next_out = &pThis->abBuffer[cbWrittenOut];
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes all available input.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pThis The gzip I/O stream instance data.
+ * @param fBlocking Whether to block or not.
+ */
+static int rtZipGzip_CompressIt(PRTZIPGZIPSTREAM pThis, bool fBlocking)
+{
+ /*
+ * Processes all the intput currently lined up for us.
+ */
+ while (pThis->Zlib.avail_in > 0)
+ {
+ /* Make sure there is some space in the output buffer before calling
+ deflate() so we don't waste time filling up the corners. */
+ static const size_t s_cbFlushThreshold = 4096;
+ AssertCompile(sizeof(pThis->abBuffer) >= s_cbFlushThreshold * 4);
+ if (pThis->Zlib.avail_out < s_cbFlushThreshold)
+ {
+ int rc = rtZipGzip_WriteOutputBuffer(pThis, fBlocking);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ Assert(pThis->Zlib.avail_out >= s_cbFlushThreshold);
+ }
+
+ int rcZlib = deflate(&pThis->Zlib, Z_NO_FLUSH);
+ if (rcZlib != Z_OK)
+ return rtZipGzipConvertErrFromZlib(pThis, rcZlib);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipGzip_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+
+ Assert(pSgBuf->cSegs == 1); NOREF(fBlocking);
+ if (pThis->fDecompress)
+ return VERR_ACCESS_DENIED;
+ AssertReturn(off == -1 || off == pThis->offStream , VERR_INVALID_PARAMETER);
+
+ /*
+ * Write out the input buffer. Using a loop here because of potential
+ * integer type overflow since avail_in is uInt and cbSeg is size_t.
+ */
+ int rc = VINF_SUCCESS;
+ size_t cbWritten = 0;
+ uint8_t const *pbSrc = (uint8_t const *)pSgBuf->paSegs[0].pvSeg;
+ size_t cbLeft = pSgBuf->paSegs[0].cbSeg;
+ if (cbLeft > 0)
+ for (;;)
+ {
+ size_t cbThis = cbLeft < ~(uInt)0 ? cbLeft : ~(uInt)0 / 2;
+ pThis->Zlib.next_in = (Bytef * )pbSrc;
+ pThis->Zlib.avail_in = (uInt)cbThis;
+ rc = rtZipGzip_CompressIt(pThis, fBlocking);
+
+ Assert(cbThis >= pThis->Zlib.avail_in);
+ cbThis -= pThis->Zlib.avail_in;
+ cbWritten += cbThis;
+ if (cbLeft == cbThis || rc != VINF_SUCCESS)
+ break;
+ pbSrc += cbThis;
+ cbLeft -= cbThis;
+ }
+
+ pThis->offStream += cbWritten;
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ return rc;
+}
+
+
+/**
+ * Processes all available input.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pThis The gzip I/O stream instance data.
+ * @param fFlushType The flush type to pass to deflate().
+ */
+static int rtZipGzip_FlushIt(PRTZIPGZIPSTREAM pThis, uint8_t fFlushType)
+{
+ /*
+ * Tell Zlib to flush until it stops producing more output.
+ */
+ int rc;
+ bool fMaybeMore = true;
+ for (;;)
+ {
+ /* Write the entire output buffer. */
+ do
+ {
+ rc = rtZipGzip_WriteOutputBuffer(pThis, true /*fBlocking*/);
+ if (RT_FAILURE(rc))
+ return rc;
+ Assert(rc == VINF_SUCCESS);
+ } while (pThis->Zlib.avail_out < sizeof(pThis->abBuffer));
+
+ if (!fMaybeMore)
+ return VINF_SUCCESS;
+
+ /* Do the flushing. */
+ pThis->Zlib.next_in = NULL;
+ pThis->Zlib.avail_in = 0;
+ int rcZlib = deflate(&pThis->Zlib, fFlushType);
+ if (rcZlib == Z_OK)
+ fMaybeMore = pThis->Zlib.avail_out < 64 || fFlushType == Z_FINISH;
+ else if (rcZlib == Z_STREAM_END)
+ fMaybeMore = false;
+ else
+ {
+ rtZipGzip_WriteOutputBuffer(pThis, true /*fBlocking*/);
+ return rtZipGzipConvertErrFromZlib(pThis, rcZlib);
+ }
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtZipGzip_Flush(void *pvThis)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+ if (!pThis->fDecompress)
+ {
+ int rc = rtZipGzip_FlushIt(pThis, Z_SYNC_FLUSH);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return RTVfsIoStrmFlush(pThis->hVfsIos);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipGzip_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+
+ /*
+ * Collect our own events first and see if that satisfies the request. If
+ * not forward the call to the compressed stream.
+ */
+ uint32_t fRetEvents = 0;
+ if (pThis->fFatalError)
+ fRetEvents |= RTPOLL_EVT_ERROR;
+ if (pThis->fDecompress)
+ {
+ fEvents &= ~RTPOLL_EVT_WRITE;
+ if (pThis->Zlib.avail_in > 0)
+ fRetEvents = RTPOLL_EVT_READ;
+ }
+ else
+ {
+ fEvents &= ~RTPOLL_EVT_READ;
+ if (pThis->Zlib.avail_out > 0)
+ fRetEvents = RTPOLL_EVT_WRITE;
+ }
+
+ int rc = VINF_SUCCESS;
+ fRetEvents &= fEvents;
+ if (!fRetEvents)
+ rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtZipGzip_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+ *poffActual = pThis->offStream;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The GZIP I/O stream vtable.
+ */
+static RTVFSIOSTREAMOPS g_rtZipGzipOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "gzip",
+ rtZipGzip_Close,
+ rtZipGzip_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipGzip_Read,
+ rtZipGzip_Write,
+ rtZipGzip_Flush,
+ rtZipGzip_PollOne,
+ rtZipGzip_Tell,
+ NULL /* Skip */,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+};
+
+
+RTDECL(int) RTZipGzipDecompressIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSIOSTREAM phVfsIosOut)
+{
+ AssertPtrReturn(hVfsIosIn, VERR_INVALID_HANDLE);
+ AssertReturn(!(fFlags & ~RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phVfsIosOut, VERR_INVALID_POINTER);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create the decompression I/O stream.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPGZIPSTREAM pThis;
+ int rc = RTVfsNewIoStream(&g_rtZipGzipOps, sizeof(RTZIPGZIPSTREAM), RTFILE_O_READ, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsIos, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIosIn;
+ pThis->offStream = 0;
+ pThis->fDecompress = true;
+ pThis->SgSeg.pvSeg = &pThis->abBuffer[0];
+ pThis->SgSeg.cbSeg = sizeof(pThis->abBuffer);
+ RTSgBufInit(&pThis->SgBuf, &pThis->SgSeg, 1);
+
+ memset(&pThis->Zlib, 0, sizeof(pThis->Zlib));
+ pThis->Zlib.opaque = pThis;
+ rc = inflateInit2(&pThis->Zlib, MAX_WBITS | RT_BIT(5) /* autodetect gzip header */);
+ if (rc >= 0)
+ {
+ /*
+ * Read the gzip header from the input stream to check that it's
+ * a gzip stream as specified by the user.
+ *
+ * Note! Since we've told zlib to check for the gzip header, we
+ * prebuffer what we read in the input buffer so it can
+ * be handed on to zlib later on.
+ */
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, pThis->abBuffer, sizeof(RTZIPGZIPHDR), true /*fBlocking*/, NULL /*pcbRead*/);
+ if (RT_SUCCESS(rc))
+ {
+ /* Validate the header and make a copy of it. */
+ PCRTZIPGZIPHDR pHdr = (PCRTZIPGZIPHDR)pThis->abBuffer;
+ if ( pHdr->bId1 == RTZIPGZIPHDR_ID1
+ && pHdr->bId2 == RTZIPGZIPHDR_ID2
+ && !(pHdr->fFlags & ~RTZIPGZIPHDR_FLG_VALID_MASK))
+ {
+ if (pHdr->bCompressionMethod == RTZIPGZIPHDR_CM_DEFLATE)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_ZIP_UNSUPPORTED_METHOD;
+ }
+ else if ( (fFlags & RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR)
+ && (RT_MAKE_U16(pHdr->bId2, pHdr->bId1) % 31) == 0
+ && (pHdr->bId1 & 0xf) == RTZIPGZIPHDR_CM_DEFLATE )
+ {
+ pHdr = NULL;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_ZIP_BAD_HEADER;
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Zlib.avail_in = sizeof(RTZIPGZIPHDR);
+ pThis->Zlib.next_in = &pThis->abBuffer[0];
+ if (pHdr)
+ {
+ pThis->Hdr = *pHdr;
+ /* Parse on if there are names or comments. */
+ if (pHdr->fFlags & (RTZIPGZIPHDR_FLG_NAME | RTZIPGZIPHDR_FLG_COMMENT))
+ {
+ /** @todo Can implement this when someone needs the
+ * name or comment for something useful. */
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsIosOut = hVfsIos;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ else
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc); /** @todo cleaning up in this situation is going to go wrong. */
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else
+ RTVfsIoStrmRelease(hVfsIosIn);
+ return rc;
+}
+
+
+RTDECL(int) RTZipGzipCompressIoStream(RTVFSIOSTREAM hVfsIosDst, uint32_t fFlags, uint8_t uLevel, PRTVFSIOSTREAM phVfsIosZip)
+{
+ AssertPtrReturn(hVfsIosDst, VERR_INVALID_HANDLE);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phVfsIosZip, VERR_INVALID_POINTER);
+ AssertReturn(uLevel > 0 && uLevel <= 9, VERR_INVALID_PARAMETER);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosDst);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create the compression I/O stream.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPGZIPSTREAM pThis;
+ int rc = RTVfsNewIoStream(&g_rtZipGzipOps, sizeof(RTZIPGZIPSTREAM), RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsIos, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIosDst;
+ pThis->offStream = 0;
+ pThis->fDecompress = false;
+ pThis->SgSeg.pvSeg = &pThis->abBuffer[0];
+ pThis->SgSeg.cbSeg = sizeof(pThis->abBuffer);
+ RTSgBufInit(&pThis->SgBuf, &pThis->SgSeg, 1);
+
+ RT_ZERO(pThis->Zlib);
+ pThis->Zlib.opaque = pThis;
+ pThis->Zlib.next_out = &pThis->abBuffer[0];
+ pThis->Zlib.avail_out = sizeof(pThis->abBuffer);
+
+ rc = deflateInit2(&pThis->Zlib,
+ uLevel,
+ Z_DEFLATED,
+ 15 /* Windows Size */ + 16 /* GZIP header */,
+ 9 /* Max memory level for optimal speed */,
+ Z_DEFAULT_STRATEGY);
+
+ if (rc >= 0)
+ {
+ *phVfsIosZip = hVfsIos;
+ return VINF_SUCCESS;
+ }
+
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc); /** @todo cleaning up in this situation is going to go wrong. */
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else
+ RTVfsIoStrmRelease(hVfsIosDst);
+ return rc;
+}
+
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainGunzip_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, poffError, pErrInfo);
+
+ if (pElement->enmType != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_ONLY_IOS;
+ if (pElement->enmTypeIn == RTVFSOBJTYPE_INVALID)
+ return VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT;
+ if ( pElement->enmTypeIn != RTVFSOBJTYPE_FILE
+ && pElement->enmTypeIn != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_TAKES_FILE_OR_IOS;
+ if (pSpec->fOpenFile & RTFILE_O_WRITE)
+ return VERR_VFS_CHAIN_READ_ONLY_IOS;
+ if (pElement->cArgs != 0)
+ return VERR_VFS_CHAIN_NO_ARGS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainGunzip_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ RTVFSIOSTREAM hVfsIosIn = RTVfsObjToIoStream(hPrevVfsObj);
+ if (hVfsIosIn == NIL_RTVFSIOSTREAM)
+ return VERR_VFS_CHAIN_CAST_FAILED;
+
+ RTVFSIOSTREAM hVfsIos = NIL_RTVFSIOSTREAM;
+ int rc = RTZipGzipDecompressIoStream(hVfsIosIn, 0 /*fFlags*/, &hVfsIos);
+ RTVfsObjFromIoStream(hVfsIosIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainGunzip_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement);
+ return false;
+}
+
+
+/** VFS chain element 'gunzip'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainGunzipReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "gunzip",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Takes an I/O stream and gunzips it. No arguments.",
+ /* pfnValidate = */ rtVfsChainGunzip_Validate,
+ /* pfnInstantiate = */ rtVfsChainGunzip_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainGunzip_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainGunzipReg, rtVfsChainGunzipReg);
+
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainGzip_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basics.
+ */
+ if (pElement->enmType != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_ONLY_IOS;
+ if (pElement->enmTypeIn == RTVFSOBJTYPE_INVALID)
+ return VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT;
+ if ( pElement->enmTypeIn != RTVFSOBJTYPE_FILE
+ && pElement->enmTypeIn != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_TAKES_FILE_OR_IOS;
+ if (pSpec->fOpenFile & RTFILE_O_READ)
+ return VERR_VFS_CHAIN_WRITE_ONLY_IOS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Optional argument 1..9 indicating the compression level.
+ * We store it in pSpec->uProvider.
+ */
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (!*psz || !strcmp(psz, "default"))
+ pElement->uProvider = 6;
+ else if (!strcmp(psz, "fast"))
+ pElement->uProvider = 3;
+ else if ( RT_C_IS_DIGIT(*psz)
+ && *psz != '0'
+ && *RTStrStripL(psz + 1) == '\0')
+ pElement->uProvider = *psz - '0';
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected compression level: 1-9, default, or fast");
+ }
+ }
+ else
+ pElement->uProvider = 6;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainGzip_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ RTVFSIOSTREAM hVfsIosOut = RTVfsObjToIoStream(hPrevVfsObj);
+ if (hVfsIosOut == NIL_RTVFSIOSTREAM)
+ return VERR_VFS_CHAIN_CAST_FAILED;
+
+ RTVFSIOSTREAM hVfsIos = NIL_RTVFSIOSTREAM;
+ int rc = RTZipGzipCompressIoStream(hVfsIosOut, 0 /*fFlags*/, pElement->uProvider, &hVfsIos);
+ RTVfsObjFromIoStream(hVfsIosOut);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainGzip_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement);
+ return false;
+}
+
+
+/** VFS chain element 'gzip'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainGzipReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "gzip",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Takes an I/O stream and gzips it.\n"
+ "Optional argument specifying compression level: 1-9, default, fast",
+ /* pfnValidate = */ rtVfsChainGzip_Validate,
+ /* pfnInstantiate = */ rtVfsChainGzip_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainGzip_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainGzipReg, rtVfsChainGzipReg);
+
diff --git a/src/VBox/Runtime/common/zip/pkzip.cpp b/src/VBox/Runtime/common/zip/pkzip.cpp
new file mode 100644
index 00000000..fbee651b
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/pkzip.cpp
@@ -0,0 +1,259 @@
+/* $Id: pkzip.cpp $ */
+/** @file
+ * IPRT - PKZIP archive I/O.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/fs.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Memory stream private data.
+ */
+typedef struct MEMIOSTREAM
+{
+ /** Size of the memory buffer. */
+ size_t cbBuf;
+ /** Pointer to the memory buffer. */
+ uint8_t *pu8Buf;
+ /** Current offset. */
+ size_t off;
+} MEMIOSTREAM;
+typedef MEMIOSTREAM *PMEMIOSTREAM;
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) memFssIos_Close(void *pvThis)
+{
+ NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) memFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PMEMIOSTREAM pThis = (PMEMIOSTREAM)pvThis;
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ RT_ZERO(*pObjInfo);
+ pObjInfo->cbObject = pThis->cbBuf;
+ break;
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) memFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PMEMIOSTREAM pThis = (PMEMIOSTREAM)pvThis;
+ Assert(pSgBuf->cSegs == 1);
+ RT_NOREF_PV(fBlocking);
+
+ if (off < 0)
+ off = pThis->off;
+ if (off >= (RTFOFF)pThis->cbBuf)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+ size_t cbLeft = pThis->cbBuf - off;
+ size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
+ if (cbToRead > cbLeft)
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ cbToRead = (size_t)cbLeft;
+ }
+
+ memcpy(pSgBuf->paSegs[0].pvSeg, pThis->pu8Buf + off, cbToRead);
+ pThis->off = off + cbToRead;
+ if (pcbRead)
+ *pcbRead = cbToRead;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) memFssIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ RT_NOREF_PV(pvThis); RT_NOREF_PV(off); RT_NOREF_PV(pSgBuf); RT_NOREF_PV(fBlocking); RT_NOREF_PV(pcbWritten);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) memFssIos_Flush(void *pvThis)
+{
+ RT_NOREF_PV(pvThis);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) memFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents)
+{
+ RT_NOREF_PV(pvThis); RT_NOREF_PV(fEvents); RT_NOREF_PV(cMillies); RT_NOREF_PV(fIntr); RT_NOREF_PV(pfRetEvents);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) memFssIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PMEMIOSTREAM pThis = (PMEMIOSTREAM)pvThis;
+ *poffActual = pThis->off;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Memory I/O object stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_memFssIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "MemFsStream::IoStream",
+ memFssIos_Close,
+ memFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ memFssIos_Read,
+ memFssIos_Write,
+ memFssIos_Flush,
+ memFssIos_PollOne,
+ memFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+RTDECL(int) RTZipPkzipMemDecompress(void **ppvDst, size_t *pcbDst, const void *pvSrc, size_t cbSrc, const char *pszObject)
+{
+ PMEMIOSTREAM pIosData;
+ RTVFSIOSTREAM hVfsIos;
+ int rc = RTVfsNewIoStream(&g_memFssIosOps,
+ sizeof(*pIosData),
+ RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIos,
+ (void **)&pIosData);
+ if (RT_SUCCESS(rc))
+ {
+ pIosData->pu8Buf = (uint8_t*)pvSrc;
+ pIosData->cbBuf = cbSrc;
+ pIosData->off = 0;
+ RTVFSFSSTREAM hVfsFss;
+ rc = RTZipPkzipFsStreamFromIoStream(hVfsIos, 0 /*fFlags*/, &hVfsFss);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Loop through all objects. Actually this wouldn't be required
+ * for .zip files but we opened it as I/O stream.
+ */
+ for (bool fFound = false; !fFound;)
+ {
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ rc = RTVfsFsStrmNext(hVfsFss, &pszName, NULL /*penmType*/, &hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ fFound = !strcmp(pszName, pszObject);
+ if (fFound)
+ {
+ RTFSOBJINFO UnixInfo;
+ rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cb = UnixInfo.cbObject;
+ void *pv = RTMemAlloc(cb);
+ if (pv)
+ {
+ RTVFSIOSTREAM hVfsIosObj = RTVfsObjToIoStream(hVfsObj);
+ if (hVfsIos != NIL_RTVFSIOSTREAM)
+ {
+ rc = RTVfsIoStrmRead(hVfsIosObj, pv, cb, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvDst = pv;
+ *pcbDst = cb;
+ }
+ }
+ else
+ rc = VERR_INTERNAL_ERROR_4;
+ if (RT_FAILURE(rc))
+ RTMemFree(pv);
+ }
+ }
+ }
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ }
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+ }
+ return rc;
+}
diff --git a/src/VBox/Runtime/common/zip/pkzipvfs.cpp b/src/VBox/Runtime/common/zip/pkzipvfs.cpp
new file mode 100644
index 00000000..5831687a
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/pkzipvfs.cpp
@@ -0,0 +1,1296 @@
+/* $Id: pkzipvfs.cpp $ */
+/** @file
+ * IPRT - PKZIP Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/stream.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/* See http://www.pkware.com/documents/casestudies/APPNOTE.TXT */
+
+/**
+ * PKZip Local File Header.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIPLOCALFILEHDR
+{
+ /** Magic value, see RTZIPPKZIPLOCALFILEHDR_MAGIC. */
+ uint32_t u32Magic;
+ /** Minimum version needed to extract. */
+ uint16_t u16Version;
+ /** General purpose bit flag. */
+ uint16_t fFlags;
+ /** Compression method. See RTZIPPKZIP_COMP_METHOD_XXX. */
+ uint16_t u16ComprMethod;
+ /** Last modified time, MS-DOS format: HHHHHMMM MMMSSSSS, multiply seconds by 2 */
+ uint16_t u16LastModifiedTime;
+ /** Last modified date, MS-DOS format: YYYYYYYM MMMDDDDD, year starts at 1980 */
+ uint16_t u16LastModifiedDate;
+ /** Checksum. */
+ uint32_t u32Crc;
+ /** Compressed size. */
+ uint32_t cbCompressed;
+ /** Uncompressed size. */
+ uint32_t cbUncompressed;
+ /** Length of the file name. */
+ uint16_t cbFilename;
+ /** Length of the extra field. */
+ uint16_t cbExtra;
+ /** Start of the file name. */
+ uint8_t u8Filename;
+} RTZIPPKZIPLOCALFILEHDR;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIPLOCALFILEHDR, 30+1);
+/** Pointer to PKZip Local File Header. */
+typedef RTZIPPKZIPLOCALFILEHDR *PRTZIPPKZIPLOCALFILEHDR;
+
+#define RTZIPPKZIPLOCALFILEHDR_MAGIC RT_MAKE_U32_FROM_U8('P','K','\003','\004')
+
+/**
+ * PKZip compression method.
+ */
+typedef enum RTZIPPKZIP_COMP_METHOD
+{
+ /** No compression */
+ RTZIPPKZIP_COMP_METHOD_STORED = 0,
+ /** Shrunk */
+ RTZIPPKZIP_COMP_METHOD_SHRUNK = 1,
+ /** Reduced with compression factor 1 */
+ RTZIPPKZIP_COMP_METHOD_REDUCED1 = 2,
+ /** Reduced with compression factor 2 */
+ RTZIPPKZIP_COMP_METHOD_REDUCED2 = 3,
+ /** Reduced with compression factor 3 */
+ RTZIPPKZIP_COMP_METHOD_REDUCED3 = 4,
+ /** Reduced with compression factor 4 */
+ RTZIPPKZIP_COMP_METHOD_REDUCED4 = 5,
+ /** Imploded */
+ RTZIPPKZIP_COMP_METHOD_IMPLODED = 6,
+ /** Deflated */
+ RTZIPPKZIP_COMP_METHOD_DEFLATED = 8,
+ /** Deflated64 */
+ RTZIPPKZIP_COMP_METHOD_DEFLATED64 = 9,
+ /* Compressed using bzip2 */
+ RTZIPPKZIP_COMP_METHOD_BZIP2 = 12,
+ /** Compressed using LZMA */
+ RTZIPPKZIP_COMP_METHOD_LZMA = 14
+} RTZIPPKZIP_COMP_METHOD;
+
+/**
+ * PKZip Central Directory Header.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIPCENTRDIRHDR
+{
+ /** The magic value. See RTZIPPKZIPCENTRDIRHDR_MAGIC. */
+ uint32_t u32Magic;
+ /** The version used for creating the item. */
+ uint16_t u16VerMade;
+ /** The minimum version required for extracting the item. */
+ uint16_t u16VerRequired;
+ /** General purpose flags. */
+ uint16_t fFlags;
+ /** Compresstion method. See RTZIPPKZIP_COMP_METHOD_XXX */
+ uint16_t u16ComprMethod;
+ /** Last modified time, MS-DOS format: HHHHHMMM MMMSSSSS, multiply seconds by 2 */
+ uint16_t u16LastModifiedTime;
+ /** Last modified date, MS-DOS format: YYYYYYYM MMMDDDDD, year starts at 1980 */
+ uint16_t u16LastModifiedDate;
+ /** Checksum. */
+ uint32_t u32Crc;
+ /** Compressed size. */
+ uint32_t cbCompressed;
+ /** Uncompressed size. */
+ uint32_t cbUncompressed;
+ /** Length of the object file name. */
+ uint16_t cbFilename;
+ /** Length of the extra field. */
+ uint16_t cbExtra;
+ /** Length of the object comment. */
+ uint16_t cbComment;
+ /** The number of the disk on which this file begins. */
+ uint16_t iDiskStart;
+ /** Internal attributes. */
+ uint16_t u16IntAttrib;
+ /** External attributes. */
+ uint32_t u32ExtAttrib;
+ /** Offset from the start of the first disk on which this file appears to
+ * where the local file header should be found. */
+ uint32_t offLocalFileHeader;
+ /** Start of the file name. */
+ uint8_t u8Filename;
+} RTZIPPKZIPCENTRDIRHDR;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIPCENTRDIRHDR, 46+1);
+/** Pointer to the PKZip Central Directory Header. */
+typedef RTZIPPKZIPCENTRDIRHDR *PRTZIPPKZIPCENTRDIRHDR;
+
+#define RTZIPPKZIPCENTRDIRHDR_MAGIC RT_MAKE_U32_FROM_U8('P','K','\001','\002')
+
+/**
+ * PKZip End of Central Directory Record.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIPENDOFCENTRDIRREC
+{
+ /** The magic value. See RTZIPPKZIPENDOFCENTRDIRREC_MAGIC. */
+ uint32_t u32Magic;
+ /** Number of this disk. */
+ uint16_t iThisDisk;
+ /** Number of the disk with the start of the Central Directory. */
+ uint16_t iDiskStartCentrDirectory;
+ /** Number of Central Directory entries on this disk. */
+ uint16_t cCentrDirRecordsThisDisk;
+ /** Number of Central Directory records. */
+ uint16_t cCentrDirRecords;
+ /** Size of the Central Directory in bytes. */
+ uint32_t cbCentrDir;
+ /** Offset of the Central Directory. */
+ uint32_t offCentrDir;
+ /** Size of the comment in bytes. */
+ uint16_t cbComment;
+ /** Start of the comment. */
+ uint8_t u8Comment;
+} RTZIPPKZIPENDOFCENTRDIRREC;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIPENDOFCENTRDIRREC, 22+1);
+/** Pointer to the PKZip End of Central Directory Record. */
+typedef RTZIPPKZIPENDOFCENTRDIRREC const *PCRTZIPPKZIPENDOFCENTRDIRREC;
+
+#define RTZIPPKZIPENDOFCENTRDIRREC_MAGIC RT_MAKE_U32_FROM_U8('P','K','\005','\006')
+
+/**
+ * PKZip ZIP64 End of Central Directory Record.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIP64ENDOFCENTRDIRREC
+{
+ /** The magic value. See RTZIPPKZIP64ENDOFCENTRDIRREC_MAGIC. */
+ uint32_t u32Magic;
+ /** Size of Zip64 end of Central Directory Record. */
+ uint64_t cbSizeEocdr;
+ /** The version used for creating the item. */
+ uint16_t u16VerMade;
+ /** The minimum version required for extracting the item. */
+ uint16_t u16VerRequired;
+ /** Number of this disk. */
+ uint32_t iThisDisk;
+ /** Number of the disk with the start of the Central Directory. */
+ uint32_t iDiskStartCentrDirectory;
+ /** Number of Central Directory entries on this disk. */
+ uint64_t cCentrDirRecordsThisDisk;
+ /** Number of Central Directory records. */
+ uint64_t cCentrDirRecords;
+ /** Size of the Central Directory in bytes. */
+ uint64_t cbCentrDir;
+ /** Offset of the Central Directory. */
+ uint64_t offCentrDir;
+} RTZIPPKZIP64ENDOFCENTRDIRREC;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIP64ENDOFCENTRDIRREC, 56);
+/** Pointer to the 64-bit PKZip End of Central Directory Record. */
+typedef RTZIPPKZIP64ENDOFCENTRDIRREC *PRTZIPPKZIP64ENDOFCENTRDIRREC;
+
+#define RTZIPPKZIP64ENDOFCENTRDIRREC_MAGIC RT_MAKE_U32_FROM_U8('P','K','\006','\006')
+
+/**
+ * PKZip ZIP64 End of Central Directory Locator.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIP64ENDOFCENTRDIRLOC
+{
+ /** The magic value. See RTZIPPKZIP64ENDOFCENTRDIRLOC_MAGIC. */
+ uint32_t u32Magic;
+ /** Number of the disk with the start of the ZIP64 End of Central Directory. */
+ uint32_t iDiskStartCentrDir;
+ /** Relative offset of the ZIP64 End of Central Directory Record. */
+ uint64_t offEndOfCentrDirRec;
+ /** Total number of disks. */
+ uint32_t cDisks;
+} RTZIPPKZIP64ENDOFCENTRDIRLOC;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIP64ENDOFCENTRDIRLOC, 20);
+
+#define RTZIPPKZIP64ENDOFCENTRDIRLOC_MAGIC RT_MAKE_U32_FROM_U8('P','K','\006','\007')
+
+/**
+ * PKZip ZIP64 Extended Information Extra Field.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIP64EXTRAFIELD
+{
+ /** Uncompressed size. */
+ uint64_t cbUncompressed;
+ /** Compressed size. */
+ uint64_t cbCompressed;
+ /** Offset from the start of the first disk on which this file appears to
+ * where the local file header should be found. */
+ uint64_t offLocalFileHeader;
+ /** The number of the disk on which this file begins. */
+ uint32_t iDiskStart;
+} RTZIPPKZIP64EXTRAFIELD;
+#pragma pack()
+/** Pointer to the ZIP64 Extended Information Extra Field. */
+typedef RTZIPPKZIP64EXTRAFIELD *PRTZIPPKZIP64EXTRAFIELD;
+AssertCompileSize(RTZIPPKZIP64EXTRAFIELD, 28);
+
+/**
+ * PKZip reader instance data.
+ */
+typedef struct RTZIPPKZIPREADER
+{
+ /** Set if we have the End of Central Directory record. */
+ bool fHaveEocd;
+ /** The Central Directory header. */
+ RTZIPPKZIPCENTRDIRHDR cdh;
+ /** ZIP64 extended information. */
+ RTZIPPKZIP64EXTRAFIELD cd64ex;
+ /** Set if ZIP64 End of Central Directory Locator is present (archive setting). */
+ bool fZip64Eocd;
+ /** Set if cd64ex is valid for the current file header (object setting). */
+ bool fZip64Ex;
+ /* The name of the current object. */
+ char szName[RTPATH_MAX];
+} RTZIPPKZIPREADER;
+/** Pointer to the PKZip reader instance data. */
+typedef RTZIPPKZIPREADER *PRTZIPPKZIPREADER;
+
+/**
+ * Pkzip object (directory).
+ */
+typedef struct RTZIPPKZIPBASEOBJ
+{
+ /** Pointer to the reader instance data (resides in the filesystem
+ * stream). */
+ PRTZIPPKZIPREADER pPkzipReader;
+ /** The object info with unix attributes. */
+ RTFSOBJINFO ObjInfo;
+} RTZIPPKZIPBASEOBJ;
+/** Pointer to a PKZIP filesystem stream base object. */
+typedef RTZIPPKZIPBASEOBJ *PRTZIPPKZIPBASEOBJ;
+
+/**
+ * Pkzip object (file) represented as a VFS I/O stream.
+ */
+typedef struct RTZIPPKZIPIOSTREAM
+{
+ /** The basic PKZIP object data. */
+ RTZIPPKZIPBASEOBJ BaseObj;
+ /** The number of (uncompressed) bytes in the file. */
+ uint64_t cbFile;
+ /** The current file position at uncompressed file data. */
+ uint64_t offFile;
+ /** The start position of the compressed data in the hVfsIos. */
+ uint64_t offCompStart;
+ /** The current position for decompressing bytes in the hVfsIos. */
+ uint64_t offComp;
+ /** The number of compressed bytes starting at offCompStart. */
+ uint64_t cbComp;
+ /** Set if we have to pass the type function the next time the input
+ * function is called. */
+ bool fPassZipType;
+ /** Set if we've reached the end of the file. */
+ bool fEndOfStream;
+ /** Pkzip compression method for this object. */
+ RTZIPPKZIP_COMP_METHOD enmCompMethod;
+ /** Zip compression method. */
+ RTZIPTYPE enmZipType;
+ /** The decompressor instance. */
+ PRTZIPDECOMP pZip;
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+} RTZIPPKZIPIOSTREAM;
+/** Pointer to a the private data of a PKZIP file I/O stream. */
+typedef RTZIPPKZIPIOSTREAM *PRTZIPPKZIPIOSTREAM;
+
+
+/**
+ * PKZip filesystem stream private data. The stream must be seekable!
+ */
+typedef struct RTZIPPKZIPFSSTREAM
+{
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+
+ /** The current object (referenced). */
+ RTVFSOBJ hVfsCurObj;
+ /** Pointer to the private data if hVfsCurObj is representing a file. */
+ PRTZIPPKZIPIOSTREAM pCurIosData;
+
+ /** The offset of the first Central Directory header. */
+ uint64_t offFirstCdh;
+ /** The offset of the next Central Directory header. */
+ uint64_t offNextCdh;
+
+ /** Size of the central directory. */
+ uint64_t cbCentrDir;
+ /** Current central directory entry. */
+ uint64_t iCentrDirEntry;
+ /** Number of central directory entries. */
+ uint64_t cCentrDirEntries;
+
+ /** Set if we have the End of Central Directory Record. */
+ bool fHaveEocd;
+ /** Set if we've reached the end of the stream. */
+ bool fEndOfStream;
+ /** Set if we've encountered a fatal error. */
+ int rcFatal;
+
+ /** The PKZIP reader instance data. */
+ RTZIPPKZIPREADER PkzipReader;
+} RTZIPPKZIPFSSTREAM;
+/** Pointer to a the private data of a PKZIP filesystem stream. */
+typedef RTZIPPKZIPFSSTREAM *PRTZIPPKZIPFSSTREAM;
+
+
+
+/**
+ * Decode date/time from DOS format as used in PKZip.
+ */
+static int rtZipPkzipReaderDecodeDosTime(PRTTIMESPEC pTimeSpec, uint16_t u16Time, uint16_t u16Date)
+{
+ RTTIME time;
+ RT_ZERO(time);
+ time.i32Year = ((u16Date & 0xfe00) >> 9) + 1980;
+ time.u8Month = (u16Date & 0x01e0) >> 5;
+ time.u8MonthDay = u16Date & 0x001f;
+ time.u8Hour = (u16Time & 0xf800) >> 11;
+ time.u8Minute = (u16Time & 0x07e0) >> 5;
+ time.u8Second = u16Time & 0x001f;
+ RTTimeNormalize(&time);
+ RTTimeImplode(pTimeSpec, &time);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parse the Local File Header.
+ * Just skip the data as we trust the Central Directory.
+ */
+static int rtZipPkzipParseLocalFileHeader(PRTZIPPKZIPREADER pThis, PRTZIPPKZIPLOCALFILEHDR pLfh, size_t *pcbExtra)
+{
+ RT_NOREF_PV(pThis);
+
+ if (pLfh->cbFilename >= sizeof(pThis->szName))
+ return VERR_PKZIP_NAME_TOO_LONG;
+
+ *pcbExtra = pLfh->cbFilename + pLfh->cbExtra;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parse the Central Directory Header.
+ */
+static int rtZipPkzipParseCentrDirHeader(PRTZIPPKZIPREADER pThis, PRTZIPPKZIPCENTRDIRHDR pCdh, size_t *pcbExtra)
+{
+ if (pCdh->u32Magic != RTZIPPKZIPCENTRDIRHDR_MAGIC)
+ return VERR_PKZIP_BAD_CDF_HEADER;
+
+ if (pCdh->cbFilename >= sizeof(pThis->szName))
+ return VERR_PKZIP_NAME_TOO_LONG;
+
+ *pcbExtra = pCdh->cbFilename + pCdh->cbExtra + pCdh->cbComment;
+
+ pThis->cdh = *pCdh;
+ pThis->fZip64Ex = false;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Return the offset of the Local File Header.
+ */
+static uint64_t rtZipPkzipReaderOffLocalHeader(PRTZIPPKZIPREADER pThis)
+{
+ if (pThis->fZip64Ex && pThis->cdh.offLocalFileHeader == (uint32_t)-1)
+ return pThis->cd64ex.offLocalFileHeader;
+
+ return pThis->cdh.offLocalFileHeader;
+}
+
+
+/**
+ * Return the uncompressed object size.
+ */
+static uint64_t rtZipPkzipReaderUncompressed(PRTZIPPKZIPREADER pThis)
+{
+ if (pThis->fZip64Ex && pThis->cdh.cbUncompressed == (uint32_t)-1)
+ return pThis->cd64ex.cbUncompressed;
+
+ return pThis->cdh.cbUncompressed;
+}
+
+
+/**
+ * Return the compressed object size.
+ */
+static uint64_t rtZipPkzipReaderCompressed(PRTZIPPKZIPREADER pThis)
+{
+ if (pThis->fZip64Ex && pThis->cdh.cbCompressed == (uint32_t)-1)
+ return pThis->cd64ex.cbCompressed;
+
+ return pThis->cdh.cbCompressed;
+}
+
+
+/**
+ * Parse the extra part of the Central Directory Header.
+ */
+static int rtZipPkzipParseCentrDirHeaderExtra(PRTZIPPKZIPREADER pThis, uint8_t *pu8Buf, size_t cb,
+ RTZIPPKZIP_COMP_METHOD *penmCompMethod, uint64_t *pcbCompressed)
+{
+ int rc = RTStrCopyEx(pThis->szName, sizeof(pThis->szName), (const char*)pu8Buf, pThis->cdh.cbFilename);
+ if (RT_SUCCESS(rc))
+ {
+ pu8Buf += pThis->cdh.cbFilename;
+ cb = pThis->cdh.cbExtra;
+ while (cb >= 4)
+ {
+ uint16_t idExtra = *(uint16_t*)pu8Buf;
+ pu8Buf += 2;
+ uint16_t cbExtra = *(uint16_t*)pu8Buf;
+ pu8Buf += 2;
+ cb -= 4;
+
+ if (cb >= cbExtra)
+ {
+ switch (idExtra)
+ {
+ case 0x0001:
+ /*
+ * ZIP64 Extended Information Extra Field.
+ */
+ if (!pThis->fZip64Eocd)
+ return VERR_PKZIP_ZIP64EX_IN_ZIP32;
+ /* Not all fields are really used. */
+ RT_ZERO(pThis->cd64ex);
+ memcpy(&pThis->cd64ex, pu8Buf, cbExtra);
+ pThis->fZip64Ex = true;
+ break;
+
+ default:
+ /* unknown, just skip */
+ break;
+ }
+ pu8Buf += cbExtra;
+ cb -= cbExtra;
+ }
+ else
+ {
+ rc = VERR_PKZIP_BAD_CDF_HEADER;
+ break;
+ }
+ }
+
+ *penmCompMethod = (RTZIPPKZIP_COMP_METHOD)pThis->cdh.u16ComprMethod;
+ *pcbCompressed = rtZipPkzipReaderCompressed(pThis);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Translate a PKZip header to an IPRT object info structure.
+ */
+static int rtZipPkzipReaderGetFsObjInfo(PRTZIPPKZIPREADER pThis, PRTFSOBJINFO pObjInfo)
+{
+ /*
+ * Zap the whole structure, this takes care of unused space in the union.
+ */
+ RT_ZERO(*pObjInfo);
+ pObjInfo->cbObject = rtZipPkzipReaderUncompressed(pThis);
+ pObjInfo->cbAllocated = rtZipPkzipReaderUncompressed(pThis); /* XXX */
+ RTTIMESPEC ts;
+ rtZipPkzipReaderDecodeDosTime(&ts, pThis->cdh.u16LastModifiedTime, pThis->cdh.u16LastModifiedDate);
+ pObjInfo->ChangeTime = ts;
+ pObjInfo->ModificationTime = ts;
+ pObjInfo->AccessTime = ts;
+ pObjInfo->BirthTime = ts;
+ const char *pszEnd = strchr(pThis->szName, '\0');
+ if (pszEnd == &pThis->szName[0] || pszEnd[-1] != '/')
+ pObjInfo->Attr.fMode = RTFS_TYPE_FILE \
+ | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR \
+ | RTFS_UNIX_IRGRP \
+ | RTFS_UNIX_IROTH;
+ else
+ pObjInfo->Attr.fMode = RTFS_TYPE_DIRECTORY \
+ | RTFS_UNIX_IRWXU \
+ | RTFS_UNIX_IRGRP | RTFS_UNIX_IXGRP \
+ | RTFS_UNIX_IROTH | RTFS_UNIX_IXOTH;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Search the magic value of the End Of Central Directory Record.
+ *
+ * @returns true if found, false otherwise.
+ * @param pu8Buf buffer.
+ * @param cb size of buffer.
+ * @param piPos where to store the position of the magic value.
+ */
+static bool rtZipPkzipReaderScanEocd(const uint8_t *pu8Buf, size_t cb, int *piPos)
+{
+ if (cb < 4)
+ return false;
+ ssize_t i;
+ for (i = (ssize_t)cb - 4; i >= 0; --i)
+ if (*(uint32_t*)(pu8Buf + i) == RTZIPPKZIPENDOFCENTRDIRREC_MAGIC)
+ {
+ *piPos = i;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Read the Local File Header. We ignore the content -- we trust the Central
+ * Directory.
+ */
+static int rtZipPkzipFssIosReadLfh(PRTZIPPKZIPFSSTREAM pThis, uint64_t *poffStartData)
+{
+ RTZIPPKZIPLOCALFILEHDR lfh;
+ uint64_t offLocalFileHeader = rtZipPkzipReaderOffLocalHeader(&pThis->PkzipReader);
+ int rc = RTVfsIoStrmReadAt(pThis->hVfsIos, offLocalFileHeader,
+ &lfh, sizeof(lfh) - 1, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (lfh.u32Magic == RTZIPPKZIPLOCALFILEHDR_MAGIC)
+ {
+ size_t cbExtra = 0;
+ rc = rtZipPkzipParseLocalFileHeader(&pThis->PkzipReader, &lfh, &cbExtra);
+ if (RT_SUCCESS(rc))
+ {
+ /* Just skip the file name and and extra field. We use the data
+ * from the Central Directory Header. */
+ rc = RTVfsIoStrmSkip(pThis->hVfsIos, cbExtra);
+ if (RT_SUCCESS(rc))
+ *poffStartData = offLocalFileHeader + sizeof(lfh) - 1 + cbExtra;
+ }
+ }
+ else
+ rc = VERR_PKZIP_BAD_LF_HEADER;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Scan the current Central Directory Header.
+ */
+static int rtZipPkzipFssIosReadCdh(PRTZIPPKZIPFSSTREAM pThis, uint64_t *poffStartData,
+ RTZIPPKZIP_COMP_METHOD *penmCompMethod, uint64_t *pcbCompressed)
+{
+ int rc;
+
+ uint64_t offCd = pThis->offNextCdh - pThis->offFirstCdh;
+ if ( pThis->iCentrDirEntry < pThis->cCentrDirEntries
+ || offCd + sizeof(RTZIPPKZIPCENTRDIRHDR) - 1 <= pThis->cbCentrDir)
+ {
+ RTZIPPKZIPCENTRDIRHDR cdh;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, pThis->offNextCdh,
+ &cdh, sizeof(cdh) - 1, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offNextCdh += sizeof(cdh) - 1;
+ pThis->iCentrDirEntry++;
+ size_t cbExtra = 0;
+ rc = rtZipPkzipParseCentrDirHeader(&pThis->PkzipReader, &cdh, &cbExtra);
+ if (RT_SUCCESS(rc))
+ {
+ if (offCd + sizeof(RTZIPPKZIPCENTRDIRHDR) - 1 + cbExtra <= pThis->cbCentrDir)
+ {
+ /* extra data up to 64k */
+ uint8_t *pu8Buf = (uint8_t*)RTMemTmpAlloc(cbExtra);
+ if (pu8Buf)
+ {
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, pu8Buf, cbExtra, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtZipPkzipParseCentrDirHeaderExtra(&pThis->PkzipReader, pu8Buf, cbExtra,
+ penmCompMethod, pcbCompressed);
+ if (RT_SUCCESS(rc))
+ rc = rtZipPkzipFssIosReadLfh(pThis, poffStartData);
+ }
+ pThis->offNextCdh += cbExtra;
+ RTMemTmpFree(pu8Buf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_EOF;
+ }
+ }
+ }
+ else
+ rc = VERR_EOF;
+
+ return rc;
+}
+
+
+/**
+ * Scan for the End of Central Directory Record. Of course this works not if
+ * the stream is non-seekable (i.e. a pipe).
+ */
+static int rtZipPkzipFssIosReadEocb(PRTZIPPKZIPFSSTREAM pThis)
+{
+ RTFSOBJINFO Info;
+ int rc = RTVfsIoStrmQueryInfo(pThis->hVfsIos, &Info, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint64_t cbFile = Info.cbObject;
+ if (cbFile < sizeof(RTZIPPKZIPENDOFCENTRDIRREC)-1)
+ return VERR_PKZIP_NO_EOCB;
+
+ /* search for start of the 'end of Central Directory Record' */
+ size_t cbBuf = RT_MIN(_1K, cbFile);
+ uint8_t *pu8Buf = (uint8_t*)RTMemTmpAlloc(cbBuf);
+ if (!pu8Buf)
+ return VERR_NO_MEMORY;
+
+ /* maximum size of EOCD comment 2^16-1 */
+ const size_t cbHdrMax = 0xffff + sizeof(RTZIPPKZIPENDOFCENTRDIRREC) - 1;
+ uint64_t offMin = cbFile >= cbHdrMax ? cbFile - cbHdrMax : 0;
+
+ uint64_t off = cbFile - cbBuf;
+ while (off >= offMin)
+ {
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, off, pu8Buf, cbBuf, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ int offMagic;
+ if (rtZipPkzipReaderScanEocd(pu8Buf, cbBuf, &offMagic))
+ {
+ off += offMagic;
+ RTZIPPKZIPENDOFCENTRDIRREC eocd;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, off, &eocd, sizeof(eocd) - 1,
+ true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* well, this shouldn't fail if the content didn't change */
+ if (eocd.u32Magic == RTZIPPKZIPENDOFCENTRDIRREC_MAGIC)
+ {
+ /* sanity check */
+ if (off + RT_UOFFSETOF(RTZIPPKZIPENDOFCENTRDIRREC, u8Comment) + eocd.cbComment == cbFile)
+ {
+ pThis->offFirstCdh = eocd.offCentrDir;
+ pThis->offNextCdh = eocd.offCentrDir;
+ pThis->iCentrDirEntry = 0;
+ pThis->cCentrDirEntries = eocd.cCentrDirRecords;
+ pThis->cbCentrDir = eocd.cbCentrDir;
+ pThis->PkzipReader.fHaveEocd = true;
+ }
+ else
+ rc = VERR_PKZIP_NO_EOCB;
+ }
+ else
+ rc = VERR_PKZIP_NO_EOCB;
+ }
+ if (rc != VERR_PKZIP_NO_EOCB)
+ break;
+ }
+ else
+ rc = VERR_PKZIP_NO_EOCB;
+ /* overlap the following read */
+ off -= cbBuf - 4;
+ }
+
+ RTMemTmpFree(pu8Buf);
+
+ /*
+ * Now check for the presence of the Zip64 End of Central Directory Locator.
+ */
+ if ( RT_SUCCESS(rc)
+ && off > (unsigned)sizeof(RTZIPPKZIP64ENDOFCENTRDIRLOC))
+ {
+ off -= sizeof(RTZIPPKZIP64ENDOFCENTRDIRLOC);
+
+ RTZIPPKZIP64ENDOFCENTRDIRLOC eocd64loc;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, off, &eocd64loc, sizeof(eocd64loc), true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (eocd64loc.u32Magic == RTZIPPKZIP64ENDOFCENTRDIRLOC_MAGIC)
+ pThis->PkzipReader.fZip64Eocd = true;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssBaseObj_Close(void *pvThis)
+{
+ NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssBaseObj_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPPKZIPBASEOBJ pThis = (PRTZIPPKZIPBASEOBJ)pvThis;
+
+ /*
+ * Copy the desired data.
+ */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * PKZip filesystem base object operations (directory objects).
+ */
+static const RTVFSOBJOPS g_rtZipPkzipFssBaseObjOps =
+{
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_BASE,
+ "PkzipFsStream::Obj",
+ rtZipPkzipFssBaseObj_Close,
+ rtZipPkzipFssBaseObj_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIos_Close(void *pvThis)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ if (pThis->pZip)
+ {
+ RTZipDecompDestroy(pThis->pZip);
+ pThis->pZip = NULL;
+ }
+
+ return rtZipPkzipFssBaseObj_Close(&pThis->BaseObj);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+ return rtZipPkzipFssBaseObj_QueryInfo(&pThis->BaseObj, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * Callback function for rtZipPkzipFssIos_Read. For feeding compressed data
+ * into the decompressor function.
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIosReadHelper(void *pvThis, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+ int rc = VINF_SUCCESS;
+ if (!cbToRead)
+ return rc;
+ if ( pThis->fPassZipType
+ && cbToRead > 0)
+ {
+ uint8_t *pu8Buf = (uint8_t*)pvBuf;
+ pu8Buf[0] = pThis->enmZipType;
+ pvBuf = &pu8Buf[1];
+ cbToRead--;
+ pThis->fPassZipType = false;
+ }
+ if (cbToRead > 0)
+ {
+ size_t cbRead = 0;
+ const size_t cbAvail = pThis->cbComp;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, pThis->offComp, pvBuf,
+ RT_MIN(cbToRead, cbAvail), true /*fBlocking*/, &cbRead);
+ if ( RT_SUCCESS(rc)
+ && cbToRead > cbAvail)
+ rc = pcbRead ? VINF_EOF : VERR_EOF;
+ if ( rc == VINF_EOF
+ && !pcbRead)
+ rc = VERR_EOF;
+ pThis->offComp += cbRead;
+ if (pcbRead)
+ *pcbRead = cbRead;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+ Assert(pSgBuf->cSegs == 1);
+ RT_NOREF_PV(fBlocking);
+
+ if (off < 0)
+ off = pThis->offFile;
+ if (off >= (RTFOFF)pThis->cbFile)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+ Assert(pThis->cbFile >= pThis->offFile);
+ uint64_t cbLeft = (uint64_t)(pThis->cbFile - pThis->offFile);
+ size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
+ if (cbToRead > cbLeft)
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ cbToRead = (size_t)cbLeft;
+ }
+
+ /*
+ * Restart decompression at start of stream or on backward seeking.
+ */
+ if ( !pThis->pZip
+ || !off
+ || off < (RTFOFF)pThis->offFile)
+ {
+ switch (pThis->enmCompMethod)
+ {
+ case RTZIPPKZIP_COMP_METHOD_STORED:
+ pThis->enmZipType = RTZIPTYPE_STORE;
+ break;
+
+ case RTZIPPKZIP_COMP_METHOD_DEFLATED:
+ pThis->enmZipType = RTZIPTYPE_ZLIB_NO_HEADER;
+ break;
+
+ default:
+ pThis->enmZipType = RTZIPTYPE_INVALID;
+ break;
+ }
+
+ if (pThis->pZip)
+ {
+ RTZipDecompDestroy(pThis->pZip);
+ pThis->pZip = NULL;
+ }
+ int rc = RTZipDecompCreate(&pThis->pZip, (void*)pThis, rtZipPkzipFssIosReadHelper);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Skip bytes if necessary.
+ */
+ if (off > (RTFOFF)pThis->offFile)
+ {
+ uint8_t u8Buf[_1K];
+ while (off > (RTFOFF)pThis->offFile)
+ {
+ size_t cbSkip = off - pThis->offFile;
+ if (cbSkip > sizeof(u8Buf))
+ cbSkip = sizeof(u8Buf);
+ int rc = RTZipDecompress(pThis->pZip, u8Buf, cbSkip, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->offFile += cbSkip;
+ }
+ }
+
+ /*
+ * Do the actual reading.
+ */
+ size_t cbReadStack = 0;
+ if (!pcbRead)
+ pcbRead = &cbReadStack;
+ int rc = RTZipDecompress(pThis->pZip, pSgBuf->paSegs[0].pvSeg, cbToRead, pcbRead);
+ pThis->offFile = off + *pcbRead;
+ if (pThis->offFile >= pThis->cbFile)
+ {
+ Assert(pThis->offFile == pThis->cbFile);
+ pThis->fEndOfStream = true;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) rtZipPkzipFssIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ RT_NOREF_PV(pvThis); RT_NOREF_PV(off); RT_NOREF_PV(pSgBuf); RT_NOREF_PV(fBlocking); RT_NOREF_PV(pcbWritten);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static DECLCALLBACK(int) rtZipPkzipFssIos_Flush(void *pvThis)
+{
+ RT_NOREF_PV(pvThis);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies,
+ bool fIntr, uint32_t *pfRetEvents)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)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) rtZipPkzipFssIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Pkzip I/O object stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_rtZipPkzipFssIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "PkzipFsStream::IoStream",
+ rtZipPkzipFssIos_Close,
+ rtZipPkzipFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipPkzipFssIos_Read,
+ rtZipPkzipFssIos_Write,
+ rtZipPkzipFssIos_Flush,
+ rtZipPkzipFssIos_PollOne,
+ rtZipPkzipFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipPkzipFss_Close(void *pvThis)
+{
+ PRTZIPPKZIPFSSTREAM pThis = (PRTZIPPKZIPFSSTREAM)pvThis;
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipPkzipFss_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPPKZIPFSSTREAM pThis = (PRTZIPPKZIPFSSTREAM)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) rtZipPkzipFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ PRTZIPPKZIPFSSTREAM pThis = (PRTZIPPKZIPFSSTREAM)pvThis;
+
+ /*
+ * Dispense with the current object.
+ */
+ if (pThis->hVfsCurObj != NIL_RTVFSOBJ)
+ {
+ if (pThis->pCurIosData)
+ {
+ pThis->pCurIosData->fEndOfStream = true;
+ pThis->pCurIosData->offFile = pThis->pCurIosData->cbFile;
+ pThis->pCurIosData = NULL;
+ }
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ }
+
+ /*
+ * 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;
+
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Read the end of Central Directory Record once.
+ */
+ if (!pThis->PkzipReader.fHaveEocd)
+ rc = rtZipPkzipFssIosReadEocb(pThis);
+ uint64_t offData = 0;
+
+ /*
+ * Parse the current Central Directory Header.
+ */
+ RTZIPPKZIP_COMP_METHOD enmCompMethod = RTZIPPKZIP_COMP_METHOD_STORED;
+ uint64_t cbCompressed = 0;
+ if (RT_SUCCESS(rc))
+ rc = rtZipPkzipFssIosReadCdh(pThis, &offData, &enmCompMethod, &cbCompressed);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ /*
+ * Fill an object info structure from the current Pkzip state.
+ */
+ RTFSOBJINFO Info;
+ rc = rtZipPkzipReaderGetFsObjInfo(&pThis->PkzipReader, &Info);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ /*
+ * Create an object of the appropriate type.
+ */
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ RTFMODE fType = Info.Attr.fMode & RTFS_TYPE_MASK;
+ switch (fType)
+ {
+ case RTFS_TYPE_FILE:
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPPKZIPIOSTREAM pIosData;
+ rc = RTVfsNewIoStream(&g_rtZipPkzipFssIosOps,
+ sizeof(*pIosData),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIos,
+ (void **)&pIosData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pIosData->BaseObj.pPkzipReader = &pThis->PkzipReader;
+ pIosData->BaseObj.ObjInfo = Info;
+ pIosData->cbFile = Info.cbObject;
+ pIosData->offFile = 0;
+ pIosData->offComp = offData;
+ pIosData->offCompStart = offData;
+ pIosData->cbComp = cbCompressed;
+ pIosData->enmCompMethod = enmCompMethod;
+ pIosData->fPassZipType = true;
+ pIosData->hVfsIos = pThis->hVfsIos;
+ RTVfsIoStrmRetain(pThis->hVfsIos);
+ pThis->pCurIosData = pIosData;
+ enmType = RTVFSOBJTYPE_IO_STREAM;
+ hVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ break;
+
+ case RTFS_TYPE_DIRECTORY:
+ PRTZIPPKZIPBASEOBJ pBaseObjData;
+ rc = RTVfsNewBaseObj(&g_rtZipPkzipFssBaseObjOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsObj,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->pPkzipReader = &pThis->PkzipReader;
+ pBaseObjData->ObjInfo = Info;
+ enmType = RTVFSOBJTYPE_BASE;
+ break;
+
+ default:
+ return pThis->rcFatal = VERR_PKZIP_UNKNOWN_TYPE_FLAG;
+ }
+ pThis->hVfsCurObj = hVfsObj;
+
+ if (ppszName)
+ {
+ rc = RTStrDupEx(ppszName, pThis->PkzipReader.szName);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ }
+
+ if (phVfsObj)
+ {
+ RTVfsObjRetain(hVfsObj);
+ *phVfsObj = hVfsObj;
+ }
+
+ if (penmType)
+ *penmType = enmType;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * PKZip filesystem stream operations.
+ */
+static const RTVFSFSSTREAMOPS rtZipPkzipFssOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "PkzipFsStream",
+ rtZipPkzipFss_Close,
+ rtZipPkzipFss_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipPkzipFss_Next,
+ NULL,
+ NULL,
+ NULL,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+RTDECL(int) RTZipPkzipFsStreamFromIoStream(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);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPPKZIPFSSTREAM pThis;
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&rtZipPkzipFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_READ,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIosIn;
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+ pThis->fEndOfStream = false;
+ pThis->rcFatal = VINF_SUCCESS;
+ pThis->fHaveEocd = false;
+
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosIn);
+ return rc;
+}
diff --git a/src/VBox/Runtime/common/zip/tar.h b/src/VBox/Runtime/common/zip/tar.h
new file mode 100644
index 00000000..b247b777
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tar.h
@@ -0,0 +1,146 @@
+/* $Id: tar.h $ */
+/** @file
+ * IPRT - TAR Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_zip_tar_h
+#define IPRT_INCLUDED_SRC_common_zip_tar_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/formats/tar.h>
+
+/**
+ * Tar header union.
+ */
+typedef union RTZIPTARHDR
+{
+ /** Byte view. */
+ char ab[512];
+ /** The standard header. */
+ RTZIPTARHDRANCIENT Ancient;
+ /** The standard header. */
+ RTZIPTARHDRPOSIX Posix;
+ /** The GNU header. */
+ RTZIPTARHDRGNU Gnu;
+ /** The bits common to both GNU and the standard header. */
+ RTZIPTARHDRCOMMON Common;
+ /** GNU sparse header. */
+ RTZIPTARHDRGNUSPARSE GnuSparse;
+} RTZIPTARHDR;
+AssertCompileSize(RTZIPTARHDR, 512);
+/** Pointer to a tar file header. */
+typedef RTZIPTARHDR *PRTZIPTARHDR;
+/** Pointer to a const tar file header. */
+typedef RTZIPTARHDR const *PCRTZIPTARHDR;
+
+
+/**
+ * Tar header type.
+ */
+typedef enum RTZIPTARTYPE
+{
+ /** Invalid type value. */
+ RTZIPTARTYPE_INVALID = 0,
+ /** Posix header. */
+ RTZIPTARTYPE_POSIX,
+ /** The old GNU header, has layout conflicting with posix. */
+ RTZIPTARTYPE_GNU,
+ /** Ancient tar header which does not use anything beyond the magic. */
+ RTZIPTARTYPE_ANCIENT,
+ /** End of the valid type values (this is not valid). */
+ RTZIPTARTYPE_END,
+ /** The usual type blow up. */
+ RTZIPTARTYPE_32BIT_HACK = 0x7fffffff
+} RTZIPTARTYPE;
+typedef RTZIPTARTYPE *PRTZIPTARTYPE;
+
+
+/**
+ * Calculates the TAR header checksums and detects if it's all zeros.
+ *
+ * @returns true if all zeros, false if not.
+ * @param pHdr The header to checksum.
+ * @param pi32Unsigned Where to store the checksum calculated using
+ * unsigned chars. This is the one POSIX specifies.
+ * @param pi32Signed Where to store the checksum calculated using
+ * signed chars.
+ *
+ * @remarks The reason why we calculate the checksum as both signed and unsigned
+ * has to do with various the char C type being signed on some hosts
+ * and unsigned on others.
+ */
+DECLINLINE(bool) rtZipTarCalcChkSum(PCRTZIPTARHDR pHdr, int32_t *pi32Unsigned, int32_t *pi32Signed)
+{
+ int32_t i32Unsigned = 0;
+ int32_t i32Signed = 0;
+
+ /*
+ * Sum up the entire header.
+ */
+ const char *pch = (const char *)pHdr;
+ const char *pchEnd = pch + sizeof(*pHdr);
+ do
+ {
+ i32Unsigned += *(unsigned char *)pch;
+ i32Signed += *(signed char *)pch;
+ } while (++pch != pchEnd);
+
+ /*
+ * Check if it's all zeros and replace the chksum field with spaces.
+ */
+ bool const fZeroHdr = i32Unsigned == 0;
+
+ pch = pHdr->Common.chksum;
+ pchEnd = pch + sizeof(pHdr->Common.chksum);
+ do
+ {
+ i32Unsigned -= *(unsigned char *)pch;
+ i32Signed -= *(signed char *)pch;
+ } while (++pch != pchEnd);
+
+ i32Unsigned += (unsigned char)' ' * sizeof(pHdr->Common.chksum);
+ i32Signed += (signed char)' ' * sizeof(pHdr->Common.chksum);
+
+ *pi32Unsigned = i32Unsigned;
+ if (pi32Signed)
+ *pi32Signed = i32Signed;
+ return fZeroHdr;
+}
+
+
+#endif /* !IPRT_INCLUDED_SRC_common_zip_tar_h */
+
diff --git a/src/VBox/Runtime/common/zip/tarcmd.cpp b/src/VBox/Runtime/common/zip/tarcmd.cpp
new file mode 100644
index 00000000..5c4f62bb
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tarcmd.cpp
@@ -0,0 +1,1960 @@
+/* $Id: tarcmd.cpp $ */
+/** @file
+ * IPRT - A mini TAR Command.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/symlink.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTZIPTARCMD_OPT_DELETE 1000
+#define RTZIPTARCMD_OPT_OWNER 1001
+#define RTZIPTARCMD_OPT_GROUP 1002
+#define RTZIPTARCMD_OPT_UTC 1003
+#define RTZIPTARCMD_OPT_PREFIX 1004
+#define RTZIPTARCMD_OPT_FILE_MODE_AND_MASK 1005
+#define RTZIPTARCMD_OPT_FILE_MODE_OR_MASK 1006
+#define RTZIPTARCMD_OPT_DIR_MODE_AND_MASK 1007
+#define RTZIPTARCMD_OPT_DIR_MODE_OR_MASK 1008
+#define RTZIPTARCMD_OPT_FORMAT 1009
+#define RTZIPTARCMD_OPT_READ_AHEAD 1010
+#define RTZIPTARCMD_OPT_USE_PUSH_FILE 1011
+#define RTZIPTARCMD_OPT_NO_RECURSION 1012
+
+/** File format. */
+typedef enum RTZIPTARCMDFORMAT
+{
+ RTZIPTARCMDFORMAT_INVALID = 0,
+ /** Autodetect if possible, defaulting to TAR. */
+ RTZIPTARCMDFORMAT_AUTO_DEFAULT,
+ /** TAR. */
+ RTZIPTARCMDFORMAT_TAR,
+ /** XAR. */
+ RTZIPTARCMDFORMAT_XAR,
+ /** CPIO. */
+ RTZIPTARCMDFORMAT_CPIO
+} RTZIPTARCMDFORMAT;
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * IPRT TAR option structure.
+ */
+typedef struct RTZIPTARCMDOPS
+{
+ /** The file format. */
+ RTZIPTARCMDFORMAT enmFormat;
+
+ /** The operation (Acdrtux or RTZIPTARCMD_OPT_DELETE). */
+ int iOperation;
+ /** The long operation option name. */
+ const char *pszOperation;
+
+ /** The directory to change into when packing and unpacking. */
+ const char *pszDirectory;
+ /** The tar file name. */
+ const char *pszFile;
+ /** Whether we're verbose or quiet. */
+ bool fVerbose;
+ /** Whether to preserve the original file owner when restoring. */
+ bool fPreserveOwner;
+ /** Whether to preserve the original file group when restoring. */
+ bool fPreserveGroup;
+ /** Whether to skip restoring the modification time (only time stored by the
+ * traditional TAR format). */
+ bool fNoModTime;
+ /** Whether to add a read ahead thread. */
+ bool fReadAhead;
+ /** Use RTVfsFsStrmPushFile instead of RTVfsFsStrmAdd for files. */
+ bool fUsePushFile;
+ /** Whether to handle directories recursively or not. Defaults to \c true. */
+ bool fRecursive;
+ /** The compressor/decompressor method to employ (0, z or j). */
+ char chZipper;
+
+ /** The owner to set. NULL if not applicable.
+ * Always resolved into uidOwner for extraction. */
+ const char *pszOwner;
+ /** The owner ID to set. NIL_RTUID if not applicable. */
+ RTUID uidOwner;
+ /** The group to set. NULL if not applicable.
+ * Always resolved into gidGroup for extraction. */
+ const char *pszGroup;
+ /** The group ID to set. NIL_RTGUID if not applicable. */
+ RTGID gidGroup;
+ /** Display the modification times in UTC instead of local time. */
+ bool fDisplayUtc;
+ /** File mode AND mask. */
+ RTFMODE fFileModeAndMask;
+ /** File mode OR mask. */
+ RTFMODE fFileModeOrMask;
+ /** Directory mode AND mask. */
+ RTFMODE fDirModeAndMask;
+ /** Directory mode OR mask. */
+ RTFMODE fDirModeOrMask;
+
+ /** What to prefix all names with when creating, adding, whatever. */
+ const char *pszPrefix;
+
+ /** The number of files(, directories or whatever) specified. */
+ uint32_t cFiles;
+ /** Array of files(, directories or whatever).
+ * Terminated by a NULL entry. */
+ const char * const *papszFiles;
+
+ /** The TAR format to create. */
+ RTZIPTARFORMAT enmTarFormat;
+ /** TAR creation flags. */
+ uint32_t fTarCreate;
+
+} RTZIPTARCMDOPS;
+/** Pointer to the IPRT tar options. */
+typedef RTZIPTARCMDOPS *PRTZIPTARCMDOPS;
+
+/** The size of the directory entry buffer we're using. */
+#define RTZIPTARCMD_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX)
+
+/**
+ * Callback used by rtZipTarDoWithMembers
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param hVfsObj The tar object to display
+ * @param pszName The name.
+ * @param rcExit The current exit code.
+ */
+typedef RTEXITCODE (*PFNDOWITHMEMBER)(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit);
+
+
+/**
+ * Checks if @a pszName is a member of @a papszNames, optionally returning the
+ * index.
+ *
+ * @returns true if the name is in the list, otherwise false.
+ * @param pszName The name to find.
+ * @param papszNames The array of names.
+ * @param piName Where to optionally return the array index.
+ */
+static bool rtZipTarCmdIsNameInArray(const char *pszName, const char * const *papszNames, uint32_t *piName)
+{
+ for (uint32_t iName = 0; papszNames[iName]; iName++)
+ if (!strcmp(papszNames[iName], pszName))
+ {
+ if (piName)
+ *piName = iName;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Queries information about a VFS object.
+ *
+ * @returns VBox status code.
+ * @param pszSpec VFS object spec to use.
+ * @param paObjInfo Where to store the queried object information.
+ * Must at least provide 3 structs, namely for UNIX, UNIX_OWNER and UNIX_GROUP attributes.
+ * @param cObjInfo Number of objection information structs handed in.
+ */
+static int rtZipTarCmdQueryObjInfo(const char *pszSpec, PRTFSOBJINFO paObjInfo, unsigned cObjInfo)
+{
+ AssertPtrReturn(paObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(cObjInfo >= 3, VERR_INVALID_PARAMETER);
+
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ int rc = RTVfsChainQueryInfo(pszSpec, &paObjInfo[0], RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsChainQueryInfo(pszSpec, &paObjInfo[1], RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsChainQueryInfo(pszSpec, &paObjInfo[2], RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ RT_BZERO(&paObjInfo[2], sizeof(RTFSOBJINFO));
+ }
+ else
+ {
+ RT_BZERO(&paObjInfo[1], sizeof(RTFSOBJINFO));
+ RT_BZERO(&paObjInfo[2], sizeof(RTFSOBJINFO));
+ }
+
+ rc = VINF_SUCCESS; /* aObjInfo[1] + aObjInfo[2] are optional. */
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainQueryInfo", pszSpec, rc, offError, &ErrInfo.Core);
+
+ return rc;
+}
+
+
+/**
+ * Archives a file.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param hVfsFss The TAR filesystem stream handle.
+ * @param pszSrc The file path or VFS spec.
+ * @param paObjInfo[3] Array of three FS object info structures. The first
+ * one is always filled with RTFSOBJATTRADD_UNIX info.
+ * The next two may contain owner and group names if
+ * available. Buffers can be modified.
+ * @param pszDst The name to archive the file under.
+ * @param pErrInfo Error info buffer (saves stack space).
+ */
+static RTEXITCODE rtZipTarCmdArchiveFile(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss, const char *pszSrc,
+ RTFSOBJINFO paObjInfo[3], const char *pszDst, PRTERRINFOSTATIC pErrInfo)
+{
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDst);
+
+ /* Open the file. */
+ uint32_t offError;
+ RTVFSIOSTREAM hVfsIosSrc;
+ int rc = RTVfsChainOpenIoStream(pszSrc, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ &hVfsIosSrc, &offError, RTErrInfoInitStatic(pErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszSrc, rc, offError, &pErrInfo->Core);
+
+ /* I/O stream to base object. */
+ RTVFSOBJ hVfsObjSrc = RTVfsObjFromIoStream(hVfsIosSrc);
+ if (hVfsObjSrc != NIL_RTVFSOBJ)
+ {
+ /*
+ * Add it to the stream. Got to variants here so we can test the
+ * RTVfsFsStrmPushFile API too.
+ */
+ if (!pOpts->fUsePushFile)
+ rc = RTVfsFsStrmAdd(hVfsFss, pszDst, hVfsObjSrc, 0 /*fFlags*/);
+ else
+ {
+ uint32_t cObjInfo = 1 + (paObjInfo[1].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_OWNER)
+ + (paObjInfo[2].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_GROUP);
+ RTVFSIOSTREAM hVfsIosDst;
+ rc = RTVfsFsStrmPushFile(hVfsFss, pszDst, paObjInfo[0].cbObject, paObjInfo, cObjInfo, 0 /*fFlags*/, &hVfsIosDst);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ }
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsObjRelease(hVfsObjSrc);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (rc != VINF_SUCCESS)
+ RTMsgWarning("%Rrc adding '%s'", rc, pszDst);
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTMsgErrorExitFailure("%Rrc adding '%s'", rc, pszDst);
+ }
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ return RTMsgErrorExitFailure("RTVfsObjFromIoStream failed unexpectedly!");
+}
+
+
+/**
+ * Sub-directory helper for creating archives.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param hVfsFss The TAR filesystem stream handle.
+ * @param pszSrc The directory path or VFS spec. We append to the
+ * buffer as we decend.
+ * @param cchSrc The length of the input.
+ * @param paObjInfo[3] Array of three FS object info structures. The first
+ * one is always filled with RTFSOBJATTRADD_UNIX info.
+ * The next two may contain owner and group names if
+ * available. The three buffers can be reused.
+ * @param pszDst The name to archive it the under. We append to the
+ * buffer as we decend.
+ * @param cchDst The length of the input.
+ * @param pDirEntry Directory entry to use for the directory to handle.
+ * @param pErrInfo Error info buffer (saves stack space).
+ */
+static RTEXITCODE rtZipTarCmdArchiveDirSub(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss,
+ char *pszSrc, size_t cchSrc, RTFSOBJINFO paObjInfo[3],
+ char pszDst[RTPATH_MAX], size_t cchDst, PRTDIRENTRYEX pDirEntry,
+ PRTERRINFOSTATIC pErrInfo)
+{
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDst);
+
+ uint32_t offError;
+ RTVFSDIR hVfsIoDir;
+ int rc = RTVfsChainOpenDir(pszSrc, 0 /*fFlags*/,
+ &hVfsIoDir, &offError, RTErrInfoInitStatic(pErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenDir", pszSrc, rc, offError, &pErrInfo->Core);
+
+ /* Make sure we've got some room in the path, to save us extra work further down. */
+ if (cchSrc + 3 >= RTPATH_MAX)
+ return RTMsgErrorExitFailure("Source path too long: '%s'\n", pszSrc);
+
+ /* Ensure we've got a trailing slash (there is space for it see above). */
+ if (!RTPATH_IS_SEP(pszSrc[cchSrc - 1]))
+ {
+ pszSrc[cchSrc++] = RTPATH_SLASH;
+ pszSrc[cchSrc] = '\0';
+ }
+
+ /* Ditto for destination. */
+ if (cchDst + 3 >= RTPATH_MAX)
+ return RTMsgErrorExitFailure("Destination path too long: '%s'\n", pszDst);
+
+ if (!RTPATH_IS_SEP(pszDst[cchDst - 1]))
+ {
+ pszDst[cchDst++] = RTPATH_SLASH;
+ pszDst[cchDst] = '\0';
+ }
+
+ /*
+ * Process the files and subdirs.
+ */
+ for (;;)
+ {
+ size_t cbDirEntry = RTZIPTARCMD_DIRENTRY_BUF_SIZE;
+ rc = RTVfsDirReadEx(hVfsIoDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Check length. */
+ if (pDirEntry->cbName + cchSrc + 3 >= RTPATH_MAX)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ if (!pOpts->fRecursive)
+ continue;
+
+ memcpy(&pszSrc[cchSrc], pDirEntry->szName, pDirEntry->cbName + 1);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pszDst[cchDst], pDirEntry->szName, pDirEntry->cbName + 1);
+ rc = rtZipTarCmdArchiveDirSub(pOpts, hVfsFss, pszSrc, cchSrc + pDirEntry->cbName, paObjInfo,
+ pszDst, cchDst + pDirEntry->cbName, pDirEntry, pErrInfo);
+ }
+
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ memcpy(&pszSrc[cchSrc], pDirEntry->szName, pDirEntry->cbName + 1);
+ rc = rtZipTarCmdQueryObjInfo(pszSrc, paObjInfo, 3 /* cObjInfo */);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pszDst[cchDst], pDirEntry->szName, pDirEntry->cbName + 1);
+ rc = rtZipTarCmdArchiveFile(pOpts, hVfsFss, pszSrc, paObjInfo, pszDst, pErrInfo);
+ }
+ break;
+ }
+
+ default:
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("Warning: File system type %#x for '%s' not implemented yet, sorry! Skipping ...\n",
+ pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK, pDirEntry->szName);
+ break;
+ }
+ }
+ }
+
+ RTVfsDirRelease(hVfsIoDir);
+
+ if (rc != VERR_NO_MORE_FILES)
+ return RTMsgErrorExitFailure("RTVfsDirReadEx failed unexpectedly!");
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Archives a directory recursively.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param hVfsFss The TAR filesystem stream handle.
+ * @param pszSrc The directory path or VFS spec. We append to the
+ * buffer as we decend.
+ * @param cchSrc The length of the input.
+ * @param paObjInfo[3] Array of three FS object info structures. The first
+ * one is always filled with RTFSOBJATTRADD_UNIX info.
+ * The next two may contain owner and group names if
+ * available. The three buffers can be reused.
+ * @param pszDst The name to archive it the under. We append to the
+ * buffer as we decend.
+ * @param cchDst The length of the input.
+ * @param pErrInfo Error info buffer (saves stack space).
+ */
+static RTEXITCODE rtZipTarCmdArchiveDir(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss, char pszSrc[RTPATH_MAX], size_t cchSrc,
+ RTFSOBJINFO paObjInfo[3], char pszDst[RTPATH_MAX], size_t cchDst,
+ PRTERRINFOSTATIC pErrInfo)
+{
+ RT_NOREF(cchSrc);
+
+ char szSrcAbs[RTPATH_MAX];
+ int rc = RTPathAbs(pszSrc, szSrcAbs, sizeof(szSrcAbs));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTPathAbs failed on '%s': %Rrc\n", pszSrc, rc);
+
+ union
+ {
+ uint8_t abPadding[RTZIPTARCMD_DIRENTRY_BUF_SIZE];
+ RTDIRENTRYEX DirEntry;
+ } uBuf;
+
+ return rtZipTarCmdArchiveDirSub(pOpts, hVfsFss, szSrcAbs, strlen(szSrcAbs), paObjInfo, pszDst, cchDst, &uBuf.DirEntry, pErrInfo);
+}
+
+
+/**
+ * Opens the output archive specified by the options.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param phVfsFss Where to return the TAR filesystem stream handle.
+ */
+static RTEXITCODE rtZipTarCmdOpenOutputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
+{
+ int rc;
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+
+ /*
+ * Open the output file.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ if ( pOpts->pszFile
+ && strcmp(pOpts->pszFile, "-") != 0)
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
+ }
+ else
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT,
+ RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ true /*fLeaveOpen*/,
+ &hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to prepare standard output for writing: %Rrc", rc);
+ }
+
+ /*
+ * Pass it thru a compressor?
+ */
+ RTVFSIOSTREAM hVfsIosComp = NIL_RTVFSIOSTREAM;
+ switch (pOpts->chZipper)
+ {
+ /* no */
+ case '\0':
+ rc = VINF_SUCCESS;
+ break;
+
+ /* gunzip */
+ case 'z':
+ rc = RTZipGzipCompressIoStream(hVfsIos, 0 /*fFlags*/, 6, &hVfsIosComp);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
+ break;
+
+ /* bunzip2 */
+ case 'j':
+ rc = VERR_NOT_SUPPORTED;
+ RTMsgError("bzip2 is not supported by this build");
+ break;
+
+ /* bug */
+ default:
+ rc = VERR_INTERNAL_ERROR_2;
+ RTMsgError("unknown decompression method '%c'", pOpts->chZipper);
+ break;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (hVfsIosComp != NIL_RTVFSIOSTREAM)
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ hVfsIos = hVfsIosComp;
+ hVfsIosComp = NIL_RTVFSIOSTREAM;
+ }
+
+ /*
+ * Open the filesystem stream creator.
+ */
+ if ( pOpts->enmFormat == RTZIPTARCMDFORMAT_TAR
+ || pOpts->enmFormat == RTZIPTARCMDFORMAT_AUTO_DEFAULT)
+ {
+ RTVFSFSSTREAM hVfsFss;
+ rc = RTZipTarFsStreamToIoStream(hVfsIos, pOpts->enmTarFormat, pOpts->fTarCreate, &hVfsFss);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Set transformation options.
+ */
+ rc = RTZipTarFsStreamSetFileMode(hVfsFss, pOpts->fFileModeAndMask, pOpts->fFileModeOrMask);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTZipTarFsStreamSetDirMode(hVfsFss, pOpts->fDirModeAndMask, pOpts->fDirModeOrMask);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTZipTarFsStreamSetDirMode(%o,%o) failed: %Rrc", pOpts->fDirModeAndMask, pOpts->fDirModeOrMask, rc);
+ }
+ else
+ RTMsgError("RTZipTarFsStreamSetFileMode(%o,%o) failed: %Rrc", pOpts->fFileModeAndMask, pOpts->fFileModeOrMask, rc);
+ if ((pOpts->pszOwner || pOpts->uidOwner != NIL_RTUID) && RT_SUCCESS(rc))
+ {
+ rc = RTZipTarFsStreamSetOwner(hVfsFss, pOpts->uidOwner, pOpts->pszOwner);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTZipTarFsStreamSetOwner(%d,%s) failed: %Rrc", pOpts->uidOwner, pOpts->pszOwner, rc);
+ }
+ if ((pOpts->pszGroup || pOpts->gidGroup != NIL_RTGID) && RT_SUCCESS(rc))
+ {
+ rc = RTZipTarFsStreamSetGroup(hVfsFss, pOpts->gidGroup, pOpts->pszGroup);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTZipTarFsStreamSetGroup(%d,%s) failed: %Rrc", pOpts->gidGroup, pOpts->pszGroup, rc);
+ }
+ if (RT_SUCCESS(rc))
+ *phVfsFss = hVfsFss;
+ else
+ {
+ RTVfsFsStrmRelease(hVfsFss);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ }
+ }
+ else
+ rc = RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Implements archive creation.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ */
+static RTEXITCODE rtZipTarCreate(PRTZIPTARCMDOPS pOpts)
+{
+ /*
+ * Refuse to create empty archive.
+ */
+ if (pOpts->cFiles == 0)
+ return RTMsgErrorExitFailure("Nothing to archive - refusing to create empty archive!");
+
+ /*
+ * First open the output file.
+ */
+ RTVFSFSSTREAM hVfsFss;
+ RTEXITCODE rcExit = rtZipTarCmdOpenOutputArchive(pOpts, &hVfsFss);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * Process the input files.
+ */
+ for (uint32_t iFile = 0; iFile < pOpts->cFiles; iFile++)
+ {
+ const char *pszFile = pOpts->papszFiles[iFile];
+
+ /*
+ * Construct/copy the source name.
+ */
+ int rc = VINF_SUCCESS;
+ char szSrc[RTPATH_MAX];
+ if ( RTPathStartsWithRoot(pszFile)
+ || RTVfsChainIsSpec(pszFile))
+ rc = RTStrCopy(szSrc, sizeof(szSrc), pszFile);
+ else
+ rc = RTPathJoin(szSrc, sizeof(szSrc), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pOpts->papszFiles[iFile]);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Construct the archived name. We must strip leading root specifier.
+ */
+ char *pszFinalPath = NULL;
+ char szDst[RTPATH_MAX];
+ const char *pszDst = pszFile;
+ if (RTVfsChainIsSpec(pszFile))
+ {
+ uint32_t offError;
+ rc = RTVfsChainQueryFinalPath(pszFile, &pszFinalPath, &offError);
+ if (RT_SUCCESS(rc))
+ pszDst = pszFinalPath;
+ else
+ rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainQueryFinalPath", pszFile, rc, offError, NULL);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pszDst = RTPathSkipRootSpec(pszDst);
+ if (*pszDst == '\0')
+ {
+ pszDst = pOpts->pszPrefix ? pOpts->pszPrefix : ".";
+ rc = RTStrCopy(szDst, sizeof(szDst), pszDst);
+ }
+ else
+ {
+ if (pOpts->pszPrefix)
+ rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszPrefix, pszDst);
+ else
+ rc = RTStrCopy(szDst, sizeof(szDst), pszDst);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * What kind of object is this and what affiliations does it have?
+ */
+ RTFSOBJINFO aObjInfo[3];
+ rc = rtZipTarCmdQueryObjInfo(szSrc, aObjInfo, RT_ELEMENTS(aObjInfo));
+ if (RT_SUCCESS(rc))
+ {
+ RTERRINFOSTATIC ErrInfo;
+
+ /*
+ * Process on an object type basis.
+ */
+ RTEXITCODE rcExit2;
+ if (RTFS_IS_DIRECTORY(aObjInfo[0].Attr.fMode))
+ rcExit2 = rtZipTarCmdArchiveDir(pOpts, hVfsFss, szSrc, strlen(szSrc), aObjInfo,
+ szDst, strlen(szDst), &ErrInfo);
+ else if (RTFS_IS_FILE(aObjInfo[0].Attr.fMode))
+ rcExit2 = rtZipTarCmdArchiveFile(pOpts, hVfsFss, szSrc, aObjInfo, szDst, &ErrInfo);
+ else if (RTFS_IS_SYMLINK(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTMsgErrorExitFailure("Symlink archiving is not implemented");
+ else if (RTFS_IS_FIFO(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTMsgErrorExitFailure("FIFO archiving is not implemented");
+ else if (RTFS_IS_SOCKET(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTMsgErrorExitFailure("Socket archiving is not implemented");
+ else if (RTFS_IS_DEV_CHAR(aObjInfo[0].Attr.fMode) || RTFS_IS_DEV_BLOCK(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTMsgErrorExitFailure("Device archiving is not implemented");
+ else if (RTFS_IS_WHITEOUT(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTEXITCODE_SUCCESS;
+ else
+ rcExit2 = RTMsgErrorExitFailure("Unknown file type: %#x\n", aObjInfo[0].Attr.fMode);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("querying object information for '%s' failed (%s)", szSrc, pszFile);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("archived file name is too long, skipping '%s' (%s)", pszDst, pszFile);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("input file name is too long, skipping '%s'", pszFile);
+ }
+
+ /*
+ * Finalize the archive.
+ */
+ int rc = RTVfsFsStrmEnd(hVfsFss);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("RTVfsFsStrmEnd failed: %Rrc", rc);
+
+ RTVfsFsStrmRelease(hVfsFss);
+ return rcExit;
+}
+
+
+/**
+ * Opens the input archive specified by the options.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param phVfsFss Where to return the TAR filesystem stream handle.
+ */
+static RTEXITCODE rtZipTarCmdOpenInputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
+{
+ int rc;
+
+ /*
+ * Open the input file.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ if ( pOpts->pszFile
+ && strcmp(pOpts->pszFile, "-") != 0)
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
+ }
+ else
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT,
+ RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ true /*fLeaveOpen*/,
+ &hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to prepare standard in for reading: %Rrc", rc);
+ }
+
+ /*
+ * Pass it thru a decompressor?
+ */
+ RTVFSIOSTREAM hVfsIosDecomp = NIL_RTVFSIOSTREAM;
+ switch (pOpts->chZipper)
+ {
+ /* no */
+ case '\0':
+ rc = VINF_SUCCESS;
+ break;
+
+ /* gunzip */
+ case 'z':
+ rc = RTZipGzipDecompressIoStream(hVfsIos, 0 /*fFlags*/, &hVfsIosDecomp);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
+ break;
+
+ /* bunzip2 */
+ case 'j':
+ rc = VERR_NOT_SUPPORTED;
+ RTMsgError("bzip2 is not supported by this build");
+ break;
+
+ /* bug */
+ default:
+ rc = VERR_INTERNAL_ERROR_2;
+ RTMsgError("unknown decompression method '%c'", pOpts->chZipper);
+ break;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (hVfsIosDecomp != NIL_RTVFSIOSTREAM)
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ hVfsIos = hVfsIosDecomp;
+ hVfsIosDecomp = NIL_RTVFSIOSTREAM;
+ }
+
+ /*
+ * Open the filesystem stream.
+ */
+ if (pOpts->enmFormat == RTZIPTARCMDFORMAT_TAR)
+ rc = RTZipTarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
+ else if (pOpts->enmFormat == RTZIPTARCMDFORMAT_XAR)
+#ifdef IPRT_WITH_XAR /* Requires C++ and XML, so only in some configruation of IPRT. */
+ rc = RTZipXarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
+#else
+ rc = VERR_NOT_SUPPORTED;
+#endif
+ else if (pOpts->enmFormat == RTZIPTARCMDFORMAT_CPIO)
+ rc = RTZipCpioFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
+ else /** @todo make RTZipTarFsStreamFromIoStream fail if not tar file! */
+ rc = RTZipTarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Worker for the --list and --extract commands.
+ *
+ * @returns The appropriate exit code.
+ * @param pOpts The tar options.
+ * @param pfnCallback The command specific callback.
+ */
+static RTEXITCODE rtZipTarDoWithMembers(PRTZIPTARCMDOPS pOpts, PFNDOWITHMEMBER pfnCallback)
+{
+ /*
+ * Allocate a bitmap to go with the file list. This will be used to
+ * indicate which files we've processed and which not.
+ */
+ uint32_t *pbmFound = NULL;
+ if (pOpts->cFiles)
+ {
+ pbmFound = (uint32_t *)RTMemAllocZ(((pOpts->cFiles + 31) / 32) * sizeof(uint32_t));
+ if (!pbmFound)
+ return RTMsgErrorExitFailure("Failed to allocate the found-file-bitmap");
+ }
+
+
+ /*
+ * Open the input archive.
+ */
+ RTVFSFSSTREAM hVfsFssIn;
+ RTEXITCODE rcExit = rtZipTarCmdOpenInputArchive(pOpts, &hVfsFssIn);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Process the stream.
+ */
+ for (;;)
+ {
+ /*
+ * Retrive the next object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ int rc = RTVfsFsStrmNext(hVfsFssIn, &pszName, NULL, &hVfsObj);
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_EOF)
+ rcExit = RTMsgErrorExitFailure("RTVfsFsStrmNext returned %Rrc", rc);
+ break;
+ }
+
+ /*
+ * Should we process this entry?
+ */
+ uint32_t iFile = UINT32_MAX;
+ if ( !pOpts->cFiles
+ || rtZipTarCmdIsNameInArray(pszName, pOpts->papszFiles, &iFile) )
+ {
+ if (pbmFound)
+ ASMBitSet(pbmFound, iFile);
+
+ rcExit = pfnCallback(pOpts, hVfsObj, pszName, rcExit);
+ }
+
+ /*
+ * Release the current object and string.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ }
+
+ /*
+ * Complain about any files we didn't find.
+ */
+ for (uint32_t iFile = 0; iFile < pOpts->cFiles; iFile++)
+ if (!ASMBitTest(pbmFound, iFile))
+ {
+ RTMsgError("%s: Was not found in the archive", pOpts->papszFiles[iFile]);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ RTVfsFsStrmRelease(hVfsFssIn);
+ }
+ RTMemFree(pbmFound);
+ return rcExit;
+}
+
+
+/**
+ * Checks if the name contains any escape sequences.
+ *
+ * An escape sequence would generally be one or more '..' references. On DOS
+ * like system, something that would make up a drive letter reference is also
+ * considered an escape sequence.
+ *
+ * @returns true / false.
+ * @param pszName The name to consider.
+ */
+static bool rtZipTarHasEscapeSequence(const char *pszName)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ if (pszName[0] == ':')
+ return true;
+#endif
+ while (*pszName)
+ {
+ while (RTPATH_IS_SEP(*pszName))
+ pszName++;
+ if ( pszName[0] == '.'
+ && pszName[1] == '.'
+ && (pszName[2] == '\0' || RTPATH_IS_SLASH(pszName[2])) )
+ return true;
+ while (*pszName && !RTPATH_IS_SEP(*pszName))
+ pszName++;
+ }
+
+ return false;
+}
+
+
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+
+/**
+ * Queries the user ID to use when extracting a member.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param pUser The user info.
+ * @param pszName The file name to use when complaining.
+ * @param rcExit The current exit code.
+ * @param pUid Where to return the user ID.
+ */
+static RTEXITCODE rtZipTarQueryExtractOwner(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pOwner, const char *pszName, RTEXITCODE rcExit,
+ PRTUID pUid)
+{
+ if (pOpts->uidOwner != NIL_RTUID)
+ *pUid = pOpts->uidOwner;
+ else if (pOpts->fPreserveGroup)
+ {
+ if (!pOwner->Attr.u.UnixGroup.szName[0])
+ *pUid = pOwner->Attr.u.UnixOwner.uid;
+ else
+ {
+ *pUid = NIL_RTUID;
+ return RTMsgErrorExitFailure("%s: User resolving is not implemented.", pszName);
+ }
+ }
+ else
+ *pUid = NIL_RTUID;
+ return rcExit;
+}
+
+
+/**
+ * Queries the group ID to use when extracting a member.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param pGroup The group info.
+ * @param pszName The file name to use when complaining.
+ * @param rcExit The current exit code.
+ * @param pGid Where to return the group ID.
+ */
+static RTEXITCODE rtZipTarQueryExtractGroup(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pGroup, const char *pszName, RTEXITCODE rcExit,
+ PRTGID pGid)
+{
+ if (pOpts->gidGroup != NIL_RTGID)
+ *pGid = pOpts->gidGroup;
+ else if (pOpts->fPreserveGroup)
+ {
+ if (!pGroup->Attr.u.UnixGroup.szName[0])
+ *pGid = pGroup->Attr.u.UnixGroup.gid;
+ else
+ {
+ *pGid = NIL_RTGID;
+ return RTMsgErrorExitFailure("%s: Group resolving is not implemented.", pszName);
+ }
+ }
+ else
+ *pGid = NIL_RTGID;
+ return rcExit;
+}
+
+#endif /* !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) */
+
+
+/**
+ * Corrects the file mode and other attributes.
+ *
+ * Common worker for rtZipTarCmdExtractFile and rtZipTarCmdExtractHardlink.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param rcExit The current exit code.
+ * @param hFile The handle to the destination file.
+ * @param pszDst The destination path (for error reporting).
+ * @param pUnixInfo The unix fs object info.
+ * @param pOwner The owner info.
+ * @param pGroup The group info.
+ */
+static RTEXITCODE rtZipTarCmdExtractSetAttribs(PRTZIPTARCMDOPS pOpts, RTEXITCODE rcExit, RTFILE hFile, const char *pszDst,
+ PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
+{
+ int rc;
+
+ if (!pOpts->fNoModTime)
+ {
+ rc = RTFileSetTimes(hFile, NULL, &pUnixInfo->ModificationTime, NULL, NULL);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error setting times: %Rrc", pszDst, rc);
+ }
+
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ if ( pOpts->uidOwner != NIL_RTUID
+ || pOpts->gidGroup != NIL_RTGID
+ || pOpts->fPreserveOwner
+ || pOpts->fPreserveGroup)
+ {
+ RTUID uidFile;
+ rcExit = rtZipTarQueryExtractOwner(pOpts, pOwner, pszDst, rcExit, &uidFile);
+
+ RTGID gidFile;
+ rcExit = rtZipTarQueryExtractGroup(pOpts, pGroup, pszDst, rcExit, &gidFile);
+ if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
+ {
+ rc = RTFileSetOwner(hFile, uidFile, gidFile);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error owner/group: %Rrc", pszDst, rc);
+ }
+ }
+#else
+ RT_NOREF_PV(pOwner); RT_NOREF_PV(pGroup);
+#endif
+
+ RTFMODE fMode = (pUnixInfo->Attr.fMode & pOpts->fFileModeAndMask) | pOpts->fFileModeOrMask;
+ rc = RTFileSetMode(hFile, fMode | RTFS_TYPE_FILE);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error changing mode: %Rrc", pszDst, rc);
+
+ return rcExit;
+}
+
+
+/**
+ * Extracts a hard linked file.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param rcExit The current exit code.
+ * @param pszDst The destination path.
+ * @param pszTarget The target relative path.
+ * @param pUnixInfo The unix fs object info.
+ * @param pOwner The owner info.
+ * @param pGroup The group info.
+ */
+static RTEXITCODE rtZipTarCmdExtractHardlink(PRTZIPTARCMDOPS pOpts, RTEXITCODE rcExit, const char *pszDst,
+ const char *pszTarget, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner,
+ PCRTFSOBJINFO pGroup)
+{
+ /*
+ * Construct full target path and check that it exists.
+ */
+ char szFullTarget[RTPATH_MAX];
+ int rc = RTPathJoin(szFullTarget, sizeof(szFullTarget), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszTarget);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Failed to construct full hardlink target path for %s: %Rrc",
+ pszDst, pszTarget, rc);
+
+ if (!RTFileExists(szFullTarget))
+ return RTMsgErrorExitFailure("%s: Hardlink target not found (or not a file): %s", pszDst, szFullTarget);
+
+ /*
+ * Try hardlink the file, falling back on copying.
+ */
+ /** @todo actual hardlinking */
+ if (true)
+ {
+ RTMsgWarning("%s: Hardlinking not available, copying '%s' instead.", pszDst, szFullTarget);
+
+ RTFILE hSrcFile;
+ rc = RTFileOpen(&hSrcFile, szFullTarget, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
+ | ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << RTFILE_O_CREATE_MODE_SHIFT);
+ RTFILE hDstFile;
+ rc = RTFileOpen(&hDstFile, pszDst, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileCopyByHandles(hSrcFile, hDstFile);
+ if (RT_SUCCESS(rc))
+ {
+ rcExit = rtZipTarCmdExtractSetAttribs(pOpts, rcExit, hDstFile, pszDst, pUnixInfo, pOwner, pGroup);
+ rc = RTFileClose(hDstFile);
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTMsgErrorExitFailure("%s: Error closing hardlinked file copy: %Rrc", pszDst, rc);
+ RTFileDelete(pszDst);
+ }
+ }
+ else
+ {
+ rcExit = RTMsgErrorExitFailure("%s: Failed copying hardlinked file '%s': %Rrc", pszDst, szFullTarget, rc);
+ rc = RTFileClose(hDstFile);
+ RTFileDelete(pszDst);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
+ RTFileClose(hSrcFile);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("%s: Error opening file '%s' for reading (hardlink target): %Rrc",
+ pszDst, szFullTarget, rc);
+ }
+
+ return rcExit;
+}
+
+
+
+/**
+ * Extracts a file.
+ *
+ * Since we can restore permissions and attributes more efficiently by working
+ * directly on the file handle, we have special code path for files.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param hVfsObj The tar object to display
+ * @param rcExit The current exit code.
+ * @param pszDst The destination path.
+ * @param pUnixInfo The unix fs object info.
+ * @param pOwner The owner info.
+ * @param pGroup The group info.
+ */
+static RTEXITCODE rtZipTarCmdExtractFile(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit,
+ const char *pszDst, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
+{
+ /*
+ * Open the destination file and create a stream object for it.
+ */
+ uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
+ | ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << RTFILE_O_CREATE_MODE_SHIFT);
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszDst, fOpen);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
+
+ RTVFSIOSTREAM hVfsIosDst;
+ rc = RTVfsIoStrmFromRTFile(hFile, fOpen, true /*fLeaveOpen*/, &hVfsIosDst);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert source to a stream and optionally add a read ahead stage.
+ */
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj);
+ if (pOpts->fReadAhead)
+ {
+ RTVFSIOSTREAM hVfsReadAhead;
+ rc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlag*/, 16 /*cBuffers*/, _256K /*cbBuffer*/, &hVfsReadAhead);
+ if (RT_SUCCESS(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ hVfsIosSrc = hVfsReadAhead;
+ }
+ else
+ AssertRC(rc); /* can be ignored in release builds. */
+ }
+
+ /*
+ * Pump the data thru and correct the file attributes.
+ */
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(pUnixInfo->cbObject, _1M));
+ if (RT_SUCCESS(rc))
+ rcExit = rtZipTarCmdExtractSetAttribs(pOpts, rcExit, hFile, pszDst, pUnixInfo, pOwner, pGroup);
+ else
+ rcExit = RTMsgErrorExitFailure("%s: Error writing out file: %Rrc", pszDst, rc);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("%s: Error creating I/O stream for file: %Rrc", pszDst, rc);
+ RTFileClose(hFile);
+ return rcExit;
+}
+
+
+/**
+ * @callback_method_impl{PFNDOWITHMEMBER, Implements --extract.}
+ */
+static RTEXITCODE rtZipTarCmdExtractCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
+{
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszName);
+
+ /*
+ * Query all the information.
+ */
+ RTFSOBJINFO UnixInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
+
+ RTFSOBJINFO Owner;
+ rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
+ rc, pszName);
+
+ RTFSOBJINFO Group;
+ rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
+ rc, pszName);
+
+ bool fIsHardLink = false;
+ char szTarget[RTPATH_MAX];
+ szTarget[0] = '\0';
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ if (hVfsSymlink != NIL_RTVFSSYMLINK)
+ {
+ rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
+ RTVfsSymlinkRelease(hVfsSymlink);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: RTVfsSymlinkRead failed: %Rrc", pszName, rc);
+ if (!szTarget[0])
+ return RTMsgErrorExitFailure("%s: Link target is empty.", pszName);
+ if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
+ {
+ fIsHardLink = true;
+ if (!RTFS_IS_FILE(UnixInfo.Attr.fMode))
+ return RTMsgErrorExitFailure("%s: Hardlinks are only supported for regular files (target=%s).",
+ pszName, szTarget);
+ if (rtZipTarHasEscapeSequence(pszName))
+ return RTMsgErrorExitFailure("%s: Hardlink target '%s' contains an escape sequence.",
+ pszName, szTarget);
+ }
+ }
+ else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
+ return RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
+
+ if (rtZipTarHasEscapeSequence(pszName))
+ return RTMsgErrorExitFailure("Name '%s' contains an escape sequence.", pszName);
+
+ /*
+ * Construct the path to the extracted member.
+ */
+ char szDst[RTPATH_MAX];
+ rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Failed to construct destination path for: %Rrc", pszName, rc);
+
+ /*
+ * Extract according to the type.
+ */
+ if (!fIsHardLink)
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ return rtZipTarCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo, &Owner, &Group);
+
+ case RTFS_TYPE_DIRECTORY:
+ rc = RTDirCreateFullPath(szDst, UnixInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Error creating directory: %Rrc", szDst, rc);
+ break;
+
+ case RTFS_TYPE_SYMLINK:
+ rc = RTSymlinkCreate(szDst, szTarget, RTSYMLINKTYPE_UNKNOWN, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Error creating symbolic link: %Rrc", szDst, rc);
+ break;
+
+ case RTFS_TYPE_FIFO:
+ return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
+ case RTFS_TYPE_DEV_CHAR:
+ return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
+ case RTFS_TYPE_DEV_BLOCK:
+ return RTMsgErrorExitFailure("%s: Block devices are not supported.", pszName);
+ case RTFS_TYPE_SOCKET:
+ return RTMsgErrorExitFailure("%s: Sockets are not supported.", pszName);
+ case RTFS_TYPE_WHITEOUT:
+ return RTMsgErrorExitFailure("%s: Whiteouts are not support.", pszName);
+ default:
+ return RTMsgErrorExitFailure("%s: Unknown file type.", pszName);
+ }
+ else
+ return rtZipTarCmdExtractHardlink(pOpts, rcExit, szDst, szTarget, &UnixInfo, &Owner, &Group);
+
+ /*
+ * Set other attributes as requested.
+ *
+ * Note! File extraction does get here.
+ */
+ if (!pOpts->fNoModTime)
+ {
+ rc = RTPathSetTimesEx(szDst, NULL, &UnixInfo.ModificationTime, NULL, NULL, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED && rc != VERR_NS_SYMLINK_SET_TIME)
+ rcExit = RTMsgErrorExitFailure("%s: Error changing modification time: %Rrc.", pszName, rc);
+ }
+
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ if ( pOpts->uidOwner != NIL_RTUID
+ || pOpts->gidGroup != NIL_RTGID
+ || pOpts->fPreserveOwner
+ || pOpts->fPreserveGroup)
+ {
+ RTUID uidFile;
+ rcExit = rtZipTarQueryExtractOwner(pOpts, &Owner, szDst, rcExit, &uidFile);
+
+ RTGID gidFile;
+ rcExit = rtZipTarQueryExtractGroup(pOpts, &Group, szDst, rcExit, &gidFile);
+ if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
+ {
+ rc = RTPathSetOwnerEx(szDst, uidFile, gidFile, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error owner/group: %Rrc", szDst, rc);
+ }
+ }
+#endif
+
+#if !defined(RT_OS_WINDOWS) /** @todo implement RTPathSetMode on windows... */
+ if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode)) /* RTPathSetMode follows symbolic links atm. */
+ {
+ RTFMODE fMode;
+ if (RTFS_IS_DIRECTORY(UnixInfo.Attr.fMode))
+ fMode = (UnixInfo.Attr.fMode & (pOpts->fDirModeAndMask | RTFS_TYPE_MASK)) | pOpts->fDirModeOrMask;
+ else
+ fMode = (UnixInfo.Attr.fMode & (pOpts->fFileModeAndMask | RTFS_TYPE_MASK)) | pOpts->fFileModeOrMask;
+ rc = RTPathSetMode(szDst, fMode);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error changing mode: %Rrc", szDst, rc);
+ }
+#endif
+
+ return rcExit;
+}
+
+
+/**
+ * @callback_method_impl{PFNDOWITHMEMBER, Implements --list.}
+ */
+static RTEXITCODE rtZipTarCmdListCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
+{
+ /*
+ * This is very simple in non-verbose mode.
+ */
+ if (!pOpts->fVerbose)
+ {
+ RTPrintf("%s\n", pszName);
+ return rcExit;
+ }
+
+ /*
+ * Query all the information.
+ */
+ RTFSOBJINFO UnixInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
+ RT_ZERO(UnixInfo);
+ }
+
+ RTFSOBJINFO Owner;
+ rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
+ rc, pszName);
+ RT_ZERO(Owner);
+ }
+
+ RTFSOBJINFO Group;
+ rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
+ rc, pszName);
+ RT_ZERO(Group);
+ }
+
+ const char *pszLinkType = NULL;
+ char szTarget[RTPATH_MAX];
+ szTarget[0] = '\0';
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ if (hVfsSymlink != NIL_RTVFSSYMLINK)
+ {
+ rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("RTVfsSymlinkRead returned %Rrc on '%s'", rc, pszName);
+ RTVfsSymlinkRelease(hVfsSymlink);
+ pszLinkType = RTFS_IS_SYMLINK(UnixInfo.Attr.fMode) ? "->" : "link to";
+ }
+ else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
+ rcExit = RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
+
+ /*
+ * Translate the mode mask.
+ */
+ char szMode[16];
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FIFO: szMode[0] = 'f'; break;
+ case RTFS_TYPE_DEV_CHAR: szMode[0] = 'c'; break;
+ case RTFS_TYPE_DIRECTORY: szMode[0] = 'd'; break;
+ case RTFS_TYPE_DEV_BLOCK: szMode[0] = 'b'; break;
+ case RTFS_TYPE_FILE: szMode[0] = '-'; break;
+ case RTFS_TYPE_SYMLINK: szMode[0] = 'l'; break;
+ case RTFS_TYPE_SOCKET: szMode[0] = 's'; break;
+ case RTFS_TYPE_WHITEOUT: szMode[0] = 'w'; break;
+ default: szMode[0] = '?'; break;
+ }
+ if (pszLinkType && szMode[0] != 's')
+ szMode[0] = 'h';
+
+ szMode[1] = UnixInfo.Attr.fMode & RTFS_UNIX_IRUSR ? 'r' : '-';
+ szMode[2] = UnixInfo.Attr.fMode & RTFS_UNIX_IWUSR ? 'w' : '-';
+ szMode[3] = UnixInfo.Attr.fMode & RTFS_UNIX_IXUSR ? 'x' : '-';
+
+ szMode[4] = UnixInfo.Attr.fMode & RTFS_UNIX_IRGRP ? 'r' : '-';
+ szMode[5] = UnixInfo.Attr.fMode & RTFS_UNIX_IWGRP ? 'w' : '-';
+ szMode[6] = UnixInfo.Attr.fMode & RTFS_UNIX_IXGRP ? 'x' : '-';
+
+ szMode[7] = UnixInfo.Attr.fMode & RTFS_UNIX_IROTH ? 'r' : '-';
+ szMode[8] = UnixInfo.Attr.fMode & RTFS_UNIX_IWOTH ? 'w' : '-';
+ szMode[9] = UnixInfo.Attr.fMode & RTFS_UNIX_IXOTH ? 'x' : '-';
+ szMode[10] = '\0';
+
+ /** @todo sticky and set-uid/gid bits. */
+
+ /*
+ * Make sure we've got valid owner and group strings.
+ */
+ if (!Owner.Attr.u.UnixGroup.szName[0])
+ RTStrPrintf(Owner.Attr.u.UnixOwner.szName, sizeof(Owner.Attr.u.UnixOwner.szName),
+ "%u", UnixInfo.Attr.u.Unix.uid);
+
+ if (!Group.Attr.u.UnixOwner.szName[0])
+ RTStrPrintf(Group.Attr.u.UnixGroup.szName, sizeof(Group.Attr.u.UnixGroup.szName),
+ "%u", UnixInfo.Attr.u.Unix.gid);
+
+ /*
+ * Format the modification time.
+ */
+ char szModTime[32];
+ RTTIME ModTime;
+ PRTTIME pTime;
+ if (!pOpts->fDisplayUtc)
+ pTime = RTTimeLocalExplode(&ModTime, &UnixInfo.ModificationTime);
+ else
+ pTime = RTTimeExplode(&ModTime, &UnixInfo.ModificationTime);
+ if (!pTime)
+ RT_ZERO(ModTime);
+ RTStrPrintf(szModTime, sizeof(szModTime), "%04d-%02u-%02u %02u:%02u",
+ ModTime.i32Year, ModTime.u8Month, ModTime.u8MonthDay, ModTime.u8Hour, ModTime.u8Minute);
+
+ /*
+ * Format the size and figure how much space is needed between the
+ * user/group and the size.
+ */
+ char szSize[64];
+ size_t cchSize;
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DEV_BLOCK:
+ cchSize = RTStrPrintf(szSize, sizeof(szSize), "%u,%u",
+ RTDEV_MAJOR(UnixInfo.Attr.u.Unix.Device), RTDEV_MINOR(UnixInfo.Attr.u.Unix.Device));
+ break;
+ default:
+ cchSize = RTStrPrintf(szSize, sizeof(szSize), "%RU64", UnixInfo.cbObject);
+ break;
+ }
+
+ size_t cchUserGroup = strlen(Owner.Attr.u.UnixOwner.szName)
+ + 1
+ + strlen(Group.Attr.u.UnixGroup.szName);
+ ssize_t cchPad = cchUserGroup + cchSize + 1 < 19
+ ? 19 - (cchUserGroup + cchSize + 1)
+ : 0;
+
+ /*
+ * Go to press.
+ */
+ if (pszLinkType)
+ RTPrintf("%s %s/%s%*s %s %s %s %s %s\n",
+ szMode,
+ Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
+ cchPad, "",
+ szSize,
+ szModTime,
+ pszName,
+ pszLinkType,
+ szTarget);
+ else
+ RTPrintf("%s %s/%s%*s %s %s %s\n",
+ szMode,
+ Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
+ cchPad, "",
+ szSize,
+ szModTime,
+ pszName);
+
+ return rcExit;
+}
+
+
+/**
+ * Display usage.
+ *
+ * @param pszProgName The program name.
+ */
+static void rtZipTarUsage(const char *pszProgName)
+{
+ /*
+ * 0 1 2 3 4 5 6 7 8
+ * 012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ */
+ RTPrintf("Usage: %s [options]\n"
+ "\n",
+ pszProgName);
+ RTPrintf("Operations:\n"
+ " -A, --concatenate, --catenate\n"
+ " Append the content of one tar archive to another. (not impl)\n"
+ " -c, --create\n"
+ " Create a new tar archive. (not impl)\n"
+ " -d, --diff, --compare\n"
+ " Compare atar archive with the file system. (not impl)\n"
+ " -r, --append\n"
+ " Append more files to the tar archive. (not impl)\n"
+ " -t, --list\n"
+ " List the contents of the tar archive.\n"
+ " -u, --update\n"
+ " Update the archive, adding files that are newer than the\n"
+ " ones in the archive. (not impl)\n"
+ " -x, --extract, --get\n"
+ " Extract the files from the tar archive.\n"
+ " --delete\n"
+ " Delete files from the tar archive.\n"
+ "\n"
+ );
+ RTPrintf("Basic Options:\n"
+ " -C <dir>, --directory <dir> (-A, -c, -d, -r, -u, -x)\n"
+ " Sets the base directory for input and output file members.\n"
+ " This does not apply to --file, even if it preceeds it.\n"
+ " -f <archive>, --file <archive> (all)\n"
+ " The tar file to create or process. '-' indicates stdout/stdin,\n"
+ " which is is the default.\n"
+ " -v, --verbose (all)\n"
+ " Verbose operation.\n"
+ " -p, --preserve-permissions (-x)\n"
+ " Preserve all permissions when extracting. Must be used\n"
+ " before the mode mask options as it will change some of these.\n"
+ " -j, --bzip2 (all)\n"
+ " Compress/decompress the archive with bzip2.\n"
+ " -z, --gzip, --gunzip, --ungzip (all)\n"
+ " Compress/decompress the archive with gzip.\n"
+ "\n");
+ RTPrintf("Misc Options:\n"
+ " --owner <uid/username> (-A, -c, -d, -r, -u, -x)\n"
+ " Set the owner of extracted and archived files to the user specified.\n"
+ " --group <uid/username> (-A, -c, -d, -r, -u, -x)\n"
+ " Set the group of extracted and archived files to the group specified.\n"
+ " --utc (-t)\n"
+ " Display timestamps as UTC instead of local time.\n"
+ " -S, --sparse (-A, -c, -u)\n"
+ " Detect sparse files and store them (gnu tar extension).\n"
+ " --format <format> (-A, -c, -u, but also -d, -r, -x)\n"
+ " The file format:\n"
+ " auto (gnu tar)\n"
+ " default (gnu tar)\n"
+ " tar (gnu tar)"
+ " gnu (tar v1.13+), "
+ " ustar (tar POSIX.1-1988), "
+ " pax (tar POSIX.1-2001),\n"
+ " xar\n"
+ " cpio\n"
+ " Note! Because XAR/TAR/CPIO detection isn't implemented yet, it\n"
+ " is necessary to specifcy --format=xar when reading a\n"
+ " XAR file or --format=cpio for a CPIO file.\n"
+ " Otherwise this option is only for creation.\n"
+ "\n");
+ RTPrintf("IPRT Options:\n"
+ " --prefix <dir-prefix> (-A, -c, -d, -r, -u)\n"
+ " Directory prefix to give the members added to the archive.\n"
+ " --file-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
+ " Restrict the access mode of regular and special files.\n"
+ " --file-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
+ " Include the given access mode for regular and special files.\n"
+ " --dir-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
+ " Restrict the access mode of directories.\n"
+ " --dir-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
+ " Include the given access mode for directories.\n"
+ " --read-ahead (-x)\n"
+ " Enabled read ahead thread when extracting files.\n"
+ " --push-file (-A, -c, -u)\n"
+ " Use RTVfsFsStrmPushFile instead of RTVfsFsStrmAdd.\n"
+ "\n");
+ RTPrintf("Standard Options:\n"
+ " -h, -?, --help\n"
+ " Display this help text.\n"
+ " -V, --version\n"
+ " Display version number.\n");
+}
+
+
+RTDECL(RTEXITCODE) RTZipTarCmd(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ *
+ * N.B. This is less flexible that your regular tar program in that it
+ * requires the operation to be specified as an option. On the other
+ * hand, you can specify it where ever you like in the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--concatenate", 'A', RTGETOPT_REQ_NOTHING },
+ { "--catenate", 'A', RTGETOPT_REQ_NOTHING },
+ { "--create", 'c', RTGETOPT_REQ_NOTHING },
+ { "--diff", 'd', RTGETOPT_REQ_NOTHING },
+ { "--compare", 'd', RTGETOPT_REQ_NOTHING },
+ { "--append", 'r', RTGETOPT_REQ_NOTHING },
+ { "--list", 't', RTGETOPT_REQ_NOTHING },
+ { "--update", 'u', RTGETOPT_REQ_NOTHING },
+ { "--extract", 'x', RTGETOPT_REQ_NOTHING },
+ { "--get", 'x', RTGETOPT_REQ_NOTHING },
+ { "--delete", RTZIPTARCMD_OPT_DELETE, RTGETOPT_REQ_NOTHING },
+
+ /* basic options */
+ { "--directory", 'C', RTGETOPT_REQ_STRING },
+ { "--file", 'f', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--preserve-permissions", 'p', RTGETOPT_REQ_NOTHING },
+ { "--bzip2", 'j', RTGETOPT_REQ_NOTHING },
+ { "--gzip", 'z', RTGETOPT_REQ_NOTHING },
+ { "--gunzip", 'z', RTGETOPT_REQ_NOTHING },
+ { "--ungzip", 'z', RTGETOPT_REQ_NOTHING },
+
+ /* other options. */
+ { "--owner", RTZIPTARCMD_OPT_OWNER, RTGETOPT_REQ_STRING },
+ { "--group", RTZIPTARCMD_OPT_GROUP, RTGETOPT_REQ_STRING },
+ { "--utc", RTZIPTARCMD_OPT_UTC, RTGETOPT_REQ_NOTHING },
+ { "--sparse", 'S', RTGETOPT_REQ_NOTHING },
+ { "--format", RTZIPTARCMD_OPT_FORMAT, RTGETOPT_REQ_STRING },
+ { "--no-recursion", RTZIPTARCMD_OPT_NO_RECURSION, RTGETOPT_REQ_NOTHING },
+
+ /* IPRT extensions */
+ { "--prefix", RTZIPTARCMD_OPT_PREFIX, RTGETOPT_REQ_STRING },
+ { "--file-mode-and-mask", RTZIPTARCMD_OPT_FILE_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--file-mode-or-mask", RTZIPTARCMD_OPT_FILE_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--dir-mode-and-mask", RTZIPTARCMD_OPT_DIR_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--dir-mode-or-mask", RTZIPTARCMD_OPT_DIR_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--read-ahead", RTZIPTARCMD_OPT_READ_AHEAD, RTGETOPT_REQ_NOTHING },
+ { "--use-push-file", RTZIPTARCMD_OPT_USE_PUSH_FILE, RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTGetOpt failed: %Rrc", rc);
+
+ RTZIPTARCMDOPS Opts;
+ RT_ZERO(Opts);
+ Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
+ Opts.uidOwner = NIL_RTUID;
+ Opts.gidGroup = NIL_RTUID;
+ Opts.fFileModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
+ Opts.fDirModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
+#if 0
+ if (RTPermIsSuperUser())
+ {
+ Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
+ Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
+ Opts.fPreserveOwner = true;
+ Opts.fPreserveGroup = true;
+ }
+#endif
+ Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
+ Opts.fRecursive = true; /* Recursion is implicit unless otherwise specified. */
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ /* operations */
+ case 'A':
+ case 'c':
+ case 'd':
+ case 'r':
+ case 't':
+ case 'u':
+ case 'x':
+ case RTZIPTARCMD_OPT_DELETE:
+ if (Opts.iOperation)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Conflicting tar operation (%s already set, now %s)",
+ Opts.pszOperation, ValueUnion.pDef->pszLong);
+ Opts.iOperation = rc;
+ Opts.pszOperation = ValueUnion.pDef->pszLong;
+ break;
+
+ /* basic options */
+ case 'C':
+ if (Opts.pszDirectory)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -C/--directory once");
+ Opts.pszDirectory = ValueUnion.psz;
+ break;
+
+ case 'f':
+ if (Opts.pszFile)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -f/--file once");
+ Opts.pszFile = ValueUnion.psz;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case 'p':
+ Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
+ Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
+ Opts.fPreserveOwner = true;
+ Opts.fPreserveGroup = true;
+ break;
+
+ case 'j':
+ case 'z':
+ if (Opts.chZipper)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify one compressor / decompressor");
+ Opts.chZipper = rc;
+ break;
+
+ case RTZIPTARCMD_OPT_OWNER:
+ if (Opts.pszOwner)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --owner once");
+ Opts.pszOwner = ValueUnion.psz;
+
+ rc = RTStrToUInt32Full(Opts.pszOwner, 0, &ValueUnion.u32);
+ if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Error convering --owner '%s' into a number: %Rrc", Opts.pszOwner, rc);
+ if (RT_SUCCESS(rc))
+ {
+ Opts.uidOwner = ValueUnion.u32;
+ Opts.pszOwner = NULL;
+ }
+ break;
+
+ case RTZIPTARCMD_OPT_GROUP:
+ if (Opts.pszGroup)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --group once");
+ Opts.pszGroup = ValueUnion.psz;
+
+ rc = RTStrToUInt32Full(Opts.pszGroup, 0, &ValueUnion.u32);
+ if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Error convering --group '%s' into a number: %Rrc", Opts.pszGroup, rc);
+ if (RT_SUCCESS(rc))
+ {
+ Opts.gidGroup = ValueUnion.u32;
+ Opts.pszGroup = NULL;
+ }
+ break;
+
+ case RTZIPTARCMD_OPT_UTC:
+ Opts.fDisplayUtc = true;
+ break;
+
+ case RTZIPTARCMD_OPT_NO_RECURSION:
+ Opts.fRecursive = false;
+ break;
+
+ /* GNU */
+ case 'S':
+ Opts.fTarCreate |= RTZIPTAR_C_SPARSE;
+ break;
+
+ /* iprt extensions */
+ case RTZIPTARCMD_OPT_PREFIX:
+ if (Opts.pszPrefix)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --prefix once");
+ Opts.pszPrefix = ValueUnion.psz;
+ break;
+
+ case RTZIPTARCMD_OPT_FILE_MODE_AND_MASK:
+ Opts.fFileModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
+ break;
+
+ case RTZIPTARCMD_OPT_FILE_MODE_OR_MASK:
+ Opts.fFileModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
+ break;
+
+ case RTZIPTARCMD_OPT_DIR_MODE_AND_MASK:
+ Opts.fDirModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
+ break;
+
+ case RTZIPTARCMD_OPT_DIR_MODE_OR_MASK:
+ Opts.fDirModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
+ break;
+
+ case RTZIPTARCMD_OPT_FORMAT:
+ if (!strcmp(ValueUnion.psz, "auto") || !strcmp(ValueUnion.psz, "default"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
+ Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
+ }
+ else if (!strcmp(ValueUnion.psz, "tar"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
+ Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
+ }
+ else if (!strcmp(ValueUnion.psz, "gnu"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
+ Opts.enmTarFormat = RTZIPTARFORMAT_GNU;
+ }
+ else if (!strcmp(ValueUnion.psz, "ustar"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
+ Opts.enmTarFormat = RTZIPTARFORMAT_USTAR;
+ }
+ else if ( !strcmp(ValueUnion.psz, "posix")
+ || !strcmp(ValueUnion.psz, "pax"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
+ Opts.enmTarFormat = RTZIPTARFORMAT_PAX;
+ }
+ else if (!strcmp(ValueUnion.psz, "xar"))
+ Opts.enmFormat = RTZIPTARCMDFORMAT_XAR;
+ else if (!strcmp(ValueUnion.psz, "cpio"))
+ Opts.enmFormat = RTZIPTARCMDFORMAT_CPIO;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown archive format: '%s'", ValueUnion.psz);
+ break;
+
+ case RTZIPTARCMD_OPT_READ_AHEAD:
+ Opts.fReadAhead = true;
+ break;
+
+ case RTZIPTARCMD_OPT_USE_PUSH_FILE:
+ Opts.fUsePushFile = true;
+ break;
+
+ /* Standard bits. */
+ case 'h':
+ rtZipTarUsage(RTPathFilename(papszArgs[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ /* this is kind of ugly. */
+ Assert((unsigned)GetState.iNext - 1 <= cArgs);
+ Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext - 1];
+ Opts.cFiles = cArgs - GetState.iNext + 1;
+ }
+
+ if (!Opts.pszFile)
+ return RTMsgErrorExitFailure("No archive specified");
+
+ /*
+ * Post proceess the options.
+ */
+ if (Opts.iOperation == 0)
+ {
+ Opts.iOperation = 't';
+ Opts.pszOperation = "--list";
+ }
+
+ if ( Opts.iOperation == 'x'
+ && Opts.pszOwner)
+ return RTMsgErrorExitFailure("The use of --owner with %s has not implemented yet", Opts.pszOperation);
+
+ if ( Opts.iOperation == 'x'
+ && Opts.pszGroup)
+ return RTMsgErrorExitFailure("The use of --group with %s has not implemented yet", Opts.pszOperation);
+
+ /*
+ * Do the job.
+ */
+ switch (Opts.iOperation)
+ {
+ case 't':
+ return rtZipTarDoWithMembers(&Opts, rtZipTarCmdListCallback);
+
+ case 'x':
+ return rtZipTarDoWithMembers(&Opts, rtZipTarCmdExtractCallback);
+
+ case 'c':
+ return rtZipTarCreate(&Opts);
+
+ case 'A':
+ case 'd':
+ case 'r':
+ case 'u':
+ case RTZIPTARCMD_OPT_DELETE:
+ return RTMsgErrorExitFailure("The operation %s is not implemented yet", Opts.pszOperation);
+
+ default:
+ return RTMsgErrorExitFailure("Internal error");
+ }
+}
+
diff --git a/src/VBox/Runtime/common/zip/tarvfs.cpp b/src/VBox/Runtime/common/zip/tarvfs.cpp
new file mode 100644
index 00000000..72db1dc7
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tarvfs.cpp
@@ -0,0 +1,1477 @@
+/* $Id: tarvfs.cpp $ */
+/** @file
+ * IPRT - TAR Virtual Filesystem, Reader.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/poll.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include "tarvfsreader.h"
+
+
+/**
+ * Converts a numeric header field to the C native type.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pszField The TAR header field.
+ * @param cchField The length of the field.
+ * @param fOctalOnly Must be octal.
+ * @param pi64 Where to store the value.
+ */
+static int rtZipTarHdrFieldToNum(const char *pszField, size_t cchField, bool fOctalOnly, int64_t *pi64)
+{
+ unsigned char const *puchField = (unsigned char const *)pszField;
+ size_t const cchFieldOrg = cchField;
+ if ( fOctalOnly
+ || !(*puchField & 0x80))
+ {
+ /*
+ * Skip leading spaces. Include zeros to save a few slower loops below.
+ */
+ unsigned char ch;
+ while (cchField > 0 && ((ch = *puchField) == ' '|| ch == '0'))
+ cchField--, puchField++;
+
+ /*
+ * Convert octal digits.
+ */
+ int64_t i64 = 0;
+ while (cchField > 0)
+ {
+ unsigned char uDigit = *puchField - '0';
+ if (uDigit >= 8)
+ break;
+ i64 <<= 3;
+ i64 |= uDigit;
+
+ puchField++;
+ cchField--;
+ }
+ *pi64 = i64;
+
+ /*
+ * Was it terminated correctly?
+ */
+ while (cchField > 0)
+ {
+ ch = *puchField++;
+ if (ch != 0 && ch != ' ')
+ return cchField < cchFieldOrg
+ ? VERR_TAR_BAD_NUM_FIELD_TERM
+ : VERR_TAR_BAD_NUM_FIELD;
+ cchField--;
+ }
+ }
+ else
+ {
+ /*
+ * The first byte has the bit 7 set to indicate base-256, while bit 6
+ * is the signed bit. Bits 5:0 are the most significant value bits.
+ */
+ uint64_t u64;
+ if (!(0x40 & *puchField))
+ {
+ /* Positive or zero value. */
+ u64 = *puchField & 0x3f;
+ cchField--;
+ puchField++;
+
+ while (cchField-- > 0)
+ {
+ if (RT_LIKELY(u64 <= (uint64_t)INT64_MAX / 256))
+ u64 = (u64 << 8) | *puchField++;
+ else
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+ }
+ }
+ else
+ {
+ /* Negative value (could be used in timestamp). We do manual sign extending here. */
+ u64 = (UINT64_MAX << 6) | (*puchField & 0x3f);
+ cchField--;
+ puchField++;
+
+ while (cchField-- > 0)
+ {
+ if (RT_LIKELY(u64 >= (uint64_t)(INT64_MIN / 256)))
+ u64 = (u64 << 8) | *puchField++;
+ else
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+ }
+ }
+ *pi64 = (int64_t)u64;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates the TAR header.
+ *
+ * @returns VINF_SUCCESS if valid, VERR_TAR_ZERO_HEADER if all zeros, and
+ * the appropriate VERR_TAR_XXX otherwise.
+ * @param pTar The TAR header.
+ * @param penmType Where to return the type of header on success.
+ */
+static int rtZipTarHdrValidate(PCRTZIPTARHDR pTar, PRTZIPTARTYPE penmType)
+{
+ /*
+ * Calc the checksum first since this enables us to detect zero headers.
+ */
+ int32_t i32ChkSum;
+ int32_t i32ChkSumSignedAlt;
+ if (rtZipTarCalcChkSum(pTar, &i32ChkSum, &i32ChkSumSignedAlt))
+ return VERR_TAR_ZERO_HEADER;
+
+ /*
+ * Read the checksum field and match the checksums.
+ */
+ int64_t i64HdrChkSum;
+ int rc = rtZipTarHdrFieldToNum(pTar->Common.chksum, sizeof(pTar->Common.chksum), true /*fOctalOnly*/, &i64HdrChkSum);
+ if (RT_FAILURE(rc))
+ return VERR_TAR_BAD_CHKSUM_FIELD;
+ if ( i32ChkSum != i64HdrChkSum
+ && i32ChkSumSignedAlt != i64HdrChkSum) /** @todo test this */
+ return VERR_TAR_CHKSUM_MISMATCH;
+
+ /*
+ * Detect the TAR type.
+ */
+ RTZIPTARTYPE enmType;
+ if ( pTar->Common.magic[0] == 'u'
+ && pTar->Common.magic[1] == 's'
+ && pTar->Common.magic[2] == 't'
+ && pTar->Common.magic[3] == 'a'
+ && pTar->Common.magic[4] == 'r')
+ {
+/** @todo detect star headers */
+ if ( pTar->Common.magic[5] == '\0'
+ && pTar->Common.version[0] == '0'
+ && pTar->Common.version[1] == '0')
+ enmType = RTZIPTARTYPE_POSIX;
+ else if ( pTar->Common.magic[5] == ' '
+ && pTar->Common.version[0] == ' '
+ && pTar->Common.version[1] == '\0')
+ enmType = RTZIPTARTYPE_GNU;
+ else if ( pTar->Common.magic[5] == '\0' /* VMWare ambiguity - they probably mean posix but */
+ && pTar->Common.version[0] == ' ' /* got the version wrong. */
+ && pTar->Common.version[1] == '\0')
+ enmType = RTZIPTARTYPE_POSIX;
+ else
+ return VERR_TAR_NOT_USTAR_V00;
+ }
+ else
+ enmType = RTZIPTARTYPE_ANCIENT;
+ *penmType = enmType;
+
+ /*
+ * Perform some basic checks.
+ */
+ switch (enmType)
+ {
+ case RTZIPTARTYPE_POSIX:
+ if ( !RT_C_IS_ALNUM(pTar->Common.typeflag)
+ && pTar->Common.typeflag != '\0')
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ break;
+
+ case RTZIPTARTYPE_GNU:
+ switch (pTar->Common.typeflag)
+ {
+ case RTZIPTAR_TF_OLDNORMAL:
+ case RTZIPTAR_TF_NORMAL:
+ case RTZIPTAR_TF_CONTIG:
+ case RTZIPTAR_TF_DIR:
+ case RTZIPTAR_TF_CHR:
+ case RTZIPTAR_TF_BLK:
+ case RTZIPTAR_TF_LINK:
+ case RTZIPTAR_TF_SYMLINK:
+ case RTZIPTAR_TF_FIFO:
+ break;
+
+ case RTZIPTAR_TF_GNU_LONGLINK:
+ case RTZIPTAR_TF_GNU_LONGNAME:
+ break;
+
+ case RTZIPTAR_TF_GNU_DUMPDIR:
+ case RTZIPTAR_TF_GNU_MULTIVOL:
+ case RTZIPTAR_TF_GNU_SPARSE:
+ case RTZIPTAR_TF_GNU_VOLDHR:
+ /** @todo Implement full GNU TAR support. .*/
+ return VERR_TAR_UNSUPPORTED_GNU_HDR_TYPE;
+
+ default:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ }
+ break;
+
+ case RTZIPTARTYPE_ANCIENT:
+ switch (pTar->Common.typeflag)
+ {
+ case RTZIPTAR_TF_OLDNORMAL:
+ case RTZIPTAR_TF_NORMAL:
+ case RTZIPTAR_TF_CONTIG:
+ case RTZIPTAR_TF_DIR:
+ case RTZIPTAR_TF_LINK:
+ case RTZIPTAR_TF_SYMLINK:
+ case RTZIPTAR_TF_FIFO:
+ break;
+ default:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ }
+ break;
+ default: /* shut up gcc */
+ AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses and validates the first TAR header of a archive/file/dir/whatever.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR reader stat.
+ * @param pTar The TAR header that has been read.
+ * @param fFirst Set if this is the first header, otherwise
+ * clear.
+ */
+static int rtZipTarReaderParseNextHeader(PRTZIPTARREADER pThis, PCRTZIPTARHDR pHdr, bool fFirst)
+{
+ int rc;
+
+ /*
+ * Basic header validation and detection first.
+ */
+ RTZIPTARTYPE enmType;
+ rc = rtZipTarHdrValidate(pHdr, &enmType);
+ if (RT_FAILURE_NP(rc))
+ {
+ if (rc == VERR_TAR_ZERO_HEADER)
+ {
+ pThis->cZeroHdrs = 1;
+ pThis->enmState = RTZIPTARREADERSTATE_ZERO;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ if (fFirst)
+ {
+ pThis->enmType = enmType;
+ if (pThis->enmPrevType == RTZIPTARTYPE_INVALID)
+ pThis->enmPrevType = enmType;
+ }
+
+ /*
+ * Handle the header by type.
+ */
+ switch (pHdr->Common.typeflag)
+ {
+ case RTZIPTAR_TF_OLDNORMAL:
+ case RTZIPTAR_TF_NORMAL:
+ case RTZIPTAR_TF_CONTIG:
+ case RTZIPTAR_TF_LINK:
+ case RTZIPTAR_TF_SYMLINK:
+ case RTZIPTAR_TF_CHR:
+ case RTZIPTAR_TF_BLK:
+ case RTZIPTAR_TF_FIFO:
+ case RTZIPTAR_TF_DIR:
+ /*
+ * Extract the name first.
+ */
+ if (!pHdr->Common.name[0])
+ return VERR_TAR_EMPTY_NAME;
+ if (pThis->enmType == RTZIPTARTYPE_POSIX)
+ {
+ Assert(pThis->offGnuLongCur == 0); Assert(pThis->szName[0] == '\0');
+ pThis->szName[0] = '\0';
+ if (pHdr->Posix.prefix[0])
+ {
+ rc = RTStrCopyEx(pThis->szName, sizeof(pThis->szName), pHdr->Posix.prefix, sizeof(pHdr->Posix.prefix));
+ AssertRC(rc); /* shall not fail */
+ rc = RTStrCat(pThis->szName, sizeof(pThis->szName), "/");
+ AssertRC(rc); /* ditto */
+ }
+ rc = RTStrCatEx(pThis->szName, sizeof(pThis->szName), pHdr->Common.name, sizeof(pHdr->Common.name));
+ AssertRCReturn(rc, rc);
+ }
+ else if (pThis->enmType == RTZIPTARTYPE_GNU)
+ {
+ if (!pThis->szName[0])
+ {
+ rc = RTStrCopyEx(pThis->szName, sizeof(pThis->szName), pHdr->Common.name, sizeof(pHdr->Common.name));
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ {
+ /* Old TAR */
+ Assert(pThis->offGnuLongCur == 0); Assert(pThis->szName[0] == '\0');
+ rc = RTStrCopyEx(pThis->szName, sizeof(pThis->szName), pHdr->Common.name, sizeof(pHdr->Common.name));
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Extract the link target.
+ */
+ if ( pHdr->Common.typeflag == RTZIPTAR_TF_LINK
+ || pHdr->Common.typeflag == RTZIPTAR_TF_SYMLINK)
+ {
+ if ( pThis->enmType == RTZIPTARTYPE_POSIX
+ || pThis->enmType == RTZIPTARTYPE_ANCIENT
+ || (pThis->enmType == RTZIPTARTYPE_GNU && pThis->szTarget[0] == '\0')
+ )
+ {
+ Assert(pThis->szTarget[0] == '\0');
+ rc = RTStrCopyEx(pThis->szTarget, sizeof(pThis->szTarget),
+ pHdr->Common.linkname, sizeof(pHdr->Common.linkname));
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ pThis->szTarget[0] = '\0';
+
+ pThis->Hdr = *pHdr;
+ break;
+
+ case RTZIPTAR_TF_X_HDR:
+ case RTZIPTAR_TF_X_GLOBAL:
+ /** @todo implement PAX */
+ return VERR_TAR_UNSUPPORTED_PAX_TYPE;
+
+ case RTZIPTAR_TF_SOLARIS_XHDR:
+ /** @todo implement solaris / pax attribute lists. */
+ return VERR_TAR_UNSUPPORTED_SOLARIS_HDR_TYPE;
+
+
+ /*
+ * A GNU long name or long link is a dummy record followed by one or
+ * more 512 byte string blocks holding the long name/link. The name
+ * lenght is encoded in the size field, null terminator included. If
+ * it is a symlink or hard link the long name may be followed by a
+ * long link sequence.
+ */
+ case RTZIPTAR_TF_GNU_LONGNAME:
+ case RTZIPTAR_TF_GNU_LONGLINK:
+ {
+ if (strcmp(pHdr->Gnu.name, "././@LongLink"))
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+
+ int64_t cb64;
+ rc = rtZipTarHdrFieldToNum(pHdr->Gnu.size, sizeof(pHdr->Gnu.size), false /*fOctalOnly*/, &cb64);
+ if (RT_FAILURE(rc) || cb64 < 0 || cb64 > _1M)
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+ uint32_t cb = (uint32_t)cb64;
+ if (cb >= sizeof(pThis->szName))
+ return VERR_TAR_NAME_TOO_LONG;
+
+ pThis->cbGnuLongExpect = cb;
+ pThis->offGnuLongCur = 0;
+ pThis->enmState = pHdr->Common.typeflag == RTZIPTAR_TF_GNU_LONGNAME
+ ? RTZIPTARREADERSTATE_GNU_LONGNAME
+ : RTZIPTARREADERSTATE_GNU_LONGLINK;
+ break;
+ }
+
+ case RTZIPTAR_TF_GNU_DUMPDIR:
+ case RTZIPTAR_TF_GNU_MULTIVOL:
+ case RTZIPTAR_TF_GNU_SPARSE:
+ case RTZIPTAR_TF_GNU_VOLDHR:
+ /** @todo Implement or skip GNU headers */
+ return VERR_TAR_UNSUPPORTED_GNU_HDR_TYPE;
+
+ default:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses and validates a TAR header.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR reader stat.
+ * @param pTar The TAR header that has been read.
+ */
+static int rtZipTarReaderParseHeader(PRTZIPTARREADER pThis, PCRTZIPTARHDR pHdr)
+{
+ switch (pThis->enmState)
+ {
+ /*
+ * The first record for a file/directory/whatever.
+ */
+ case RTZIPTARREADERSTATE_FIRST:
+ pThis->Hdr.Common.typeflag = 0x7f;
+ pThis->enmPrevType = pThis->enmType;
+ pThis->enmType = RTZIPTARTYPE_INVALID;
+ pThis->offGnuLongCur = 0;
+ pThis->cbGnuLongExpect = 0;
+ pThis->szName[0] = '\0';
+ pThis->szTarget[0] = '\0';
+ return rtZipTarReaderParseNextHeader(pThis, pHdr, true /*fFirst*/);
+
+ /*
+ * There should only be so many zero headers at the end of the file as
+ * it is a function of the block size used when writing. Don't go on
+ * reading them forever in case someone points us to /dev/zero.
+ */
+ case RTZIPTARREADERSTATE_ZERO:
+ if (!ASMMemIsZero(pHdr, sizeof(*pHdr)))
+ return VERR_TAR_ZERO_HEADER;
+ pThis->cZeroHdrs++;
+ if (pThis->cZeroHdrs <= _64K / 512 + 2)
+ return VINF_SUCCESS;
+ return VERR_TAR_ZERO_HEADER;
+
+ case RTZIPTARREADERSTATE_GNU_LONGNAME:
+ case RTZIPTARREADERSTATE_GNU_LONGLINK:
+ {
+ size_t cbIncoming = RTStrNLen((const char *)pHdr->ab, sizeof(*pHdr));
+ if (cbIncoming < sizeof(*pHdr))
+ cbIncoming += 1;
+
+ if (cbIncoming + pThis->offGnuLongCur > pThis->cbGnuLongExpect)
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+ if ( cbIncoming < sizeof(*pHdr)
+ && cbIncoming + pThis->offGnuLongCur != pThis->cbGnuLongExpect)
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+
+ char *pszDst = pThis->enmState == RTZIPTARREADERSTATE_GNU_LONGNAME ? pThis->szName : pThis->szTarget;
+ pszDst += pThis->offGnuLongCur;
+ memcpy(pszDst, pHdr->ab, cbIncoming);
+
+ pThis->offGnuLongCur += (uint32_t)cbIncoming;
+ if (pThis->offGnuLongCur == pThis->cbGnuLongExpect)
+ pThis->enmState = RTZIPTARREADERSTATE_GNU_NEXT;
+ return VINF_SUCCESS;
+ }
+
+ case RTZIPTARREADERSTATE_GNU_NEXT:
+ pThis->enmState = RTZIPTARREADERSTATE_FIRST;
+ return rtZipTarReaderParseNextHeader(pThis, pHdr, false /*fFirst*/);
+
+ default:
+ return VERR_INTERNAL_ERROR_5;
+ }
+}
+
+
+/**
+ * Translate a TAR header to an IPRT object info structure with additional UNIX
+ * attributes.
+ *
+ * This completes the validation done by rtZipTarHdrValidate.
+ *
+ * @returns VINF_SUCCESS if valid, appropriate VERR_TAR_XXX if not.
+ * @param pThis The TAR reader instance.
+ * @param pObjInfo The object info structure (output).
+ */
+static int rtZipTarReaderGetFsObjInfo(PRTZIPTARREADER pThis, PRTFSOBJINFO pObjInfo)
+{
+ /*
+ * Zap the whole structure, this takes care of unused space in the union.
+ */
+ RT_ZERO(*pObjInfo);
+
+ /*
+ * Convert the TAR field in RTFSOBJINFO order.
+ */
+ int rc;
+ int64_t i64Tmp;
+#define GET_TAR_NUMERIC_FIELD_RET(a_Var, a_Field) \
+ do { \
+ rc = rtZipTarHdrFieldToNum(a_Field, sizeof(a_Field), false /*fOctalOnly*/, &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = i64Tmp; \
+ if ((a_Var) != i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+ GET_TAR_NUMERIC_FIELD_RET(pObjInfo->cbObject, pThis->Hdr.Common.size);
+ pObjInfo->cbAllocated = RT_ALIGN_64(pObjInfo->cbObject, 512);
+ int64_t c64SecModTime;
+ GET_TAR_NUMERIC_FIELD_RET(c64SecModTime, pThis->Hdr.Common.mtime);
+ RTTimeSpecSetSeconds(&pObjInfo->ChangeTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ModificationTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->AccessTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->BirthTime, c64SecModTime);
+ if (c64SecModTime != RTTimeSpecGetSeconds(&pObjInfo->ModificationTime))
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+ GET_TAR_NUMERIC_FIELD_RET(pObjInfo->Attr.fMode, pThis->Hdr.Common.mode);
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ GET_TAR_NUMERIC_FIELD_RET(pObjInfo->Attr.u.Unix.uid, pThis->Hdr.Common.uid);
+ GET_TAR_NUMERIC_FIELD_RET(pObjInfo->Attr.u.Unix.gid, pThis->Hdr.Common.gid);
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = 0;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ switch (pThis->enmType)
+ {
+ case RTZIPTARTYPE_POSIX:
+ case RTZIPTARTYPE_GNU:
+ if ( pThis->Hdr.Common.typeflag == RTZIPTAR_TF_CHR
+ || pThis->Hdr.Common.typeflag == RTZIPTAR_TF_BLK)
+ {
+ uint32_t uMajor, uMinor;
+ GET_TAR_NUMERIC_FIELD_RET(uMajor, pThis->Hdr.Common.devmajor);
+ GET_TAR_NUMERIC_FIELD_RET(uMinor, pThis->Hdr.Common.devminor);
+ pObjInfo->Attr.u.Unix.Device = RTDEV_MAKE(uMajor, uMinor);
+ if ( uMajor != RTDEV_MAJOR(pObjInfo->Attr.u.Unix.Device)
+ || uMinor != RTDEV_MINOR(pObjInfo->Attr.u.Unix.Device))
+ return VERR_TAR_DEV_VALUE_TOO_LARGE;
+ }
+ break;
+
+ default:
+ if ( pThis->Hdr.Common.typeflag == RTZIPTAR_TF_CHR
+ || pThis->Hdr.Common.typeflag == RTZIPTAR_TF_BLK)
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ }
+
+#undef GET_TAR_NUMERIC_FIELD_RET
+
+ /*
+ * Massage the result a little bit.
+ * Also validate some more now that we've got the numbers to work with.
+ */
+ if ( (pObjInfo->Attr.fMode & ~RTFS_UNIX_MASK)
+ && pThis->enmType == RTZIPTARTYPE_POSIX)
+ return VERR_TAR_BAD_MODE_FIELD;
+ pObjInfo->Attr.fMode &= RTFS_UNIX_MASK;
+
+ RTFMODE fModeType = 0;
+ switch (pThis->Hdr.Common.typeflag)
+ {
+ case RTZIPTAR_TF_OLDNORMAL:
+ case RTZIPTAR_TF_NORMAL:
+ case RTZIPTAR_TF_CONTIG:
+ {
+ const char *pszEnd = strchr(pThis->szName, '\0');
+ if (pszEnd == &pThis->szName[0] || pszEnd[-1] != '/')
+ fModeType |= RTFS_TYPE_FILE;
+ else
+ fModeType |= RTFS_TYPE_DIRECTORY;
+ break;
+ }
+
+ case RTZIPTAR_TF_LINK:
+ if (pObjInfo->cbObject != 0)
+#if 0 /* too strict */
+ return VERR_TAR_SIZE_NOT_ZERO;
+#else
+ pObjInfo->cbObject = pObjInfo->cbAllocated = 0;
+#endif
+ fModeType |= RTFS_TYPE_FILE; /* no better idea for now */
+ break;
+
+ case RTZIPTAR_TF_SYMLINK:
+ fModeType |= RTFS_TYPE_SYMLINK;
+ break;
+
+ case RTZIPTAR_TF_CHR:
+ fModeType |= RTFS_TYPE_DEV_CHAR;
+ break;
+
+ case RTZIPTAR_TF_BLK:
+ fModeType |= RTFS_TYPE_DEV_BLOCK;
+ break;
+
+ case RTZIPTAR_TF_DIR:
+ fModeType |= RTFS_TYPE_DIRECTORY;
+ break;
+
+ case RTZIPTAR_TF_FIFO:
+ fModeType |= RTFS_TYPE_FIFO;
+ break;
+
+ case RTZIPTAR_TF_GNU_LONGLINK:
+ case RTZIPTAR_TF_GNU_LONGNAME:
+ /* ASSUMES RTFS_TYPE_XXX uses the same values as GNU stored in the mode field. */
+ fModeType = pObjInfo->Attr.fMode & RTFS_TYPE_MASK;
+ switch (fModeType)
+ {
+ case RTFS_TYPE_FILE:
+ case RTFS_TYPE_DIRECTORY:
+ case RTFS_TYPE_SYMLINK:
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_FIFO:
+ break;
+
+ default:
+ case 0:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG; /** @todo new status code */
+ }
+ break;
+
+ default:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG; /* Should've been caught in validate. */
+ }
+ if ( (pObjInfo->Attr.fMode & RTFS_TYPE_MASK)
+ && (pObjInfo->Attr.fMode & RTFS_TYPE_MASK) != fModeType)
+ return VERR_TAR_MODE_WITH_TYPE;
+ pObjInfo->Attr.fMode &= ~RTFS_TYPE_MASK;
+ pObjInfo->Attr.fMode |= fModeType;
+
+ switch (pThis->Hdr.Common.typeflag)
+ {
+ case RTZIPTAR_TF_CHR:
+ case RTZIPTAR_TF_BLK:
+ case RTZIPTAR_TF_DIR:
+ case RTZIPTAR_TF_FIFO:
+ pObjInfo->cbObject = 0;
+ pObjInfo->cbAllocated = 0;
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Checks if the reader is expecting more headers.
+ *
+ * @returns true / false.
+ * @param pThis The TAR reader instance.
+ */
+static bool rtZipTarReaderExpectingMoreHeaders(PRTZIPTARREADER pThis)
+{
+ return pThis->enmState != RTZIPTARREADERSTATE_FIRST;
+}
+
+
+/**
+ * Checks if we're at the end of the TAR file.
+ *
+ * @returns true / false.
+ * @param pThis The TAR reader instance.
+ */
+static bool rtZipTarReaderIsAtEnd(PRTZIPTARREADER pThis)
+{
+ /*
+ * In theory there shall always be two zero headers at the end of the
+ * archive, but life isn't that simple. We've been creating archives
+ * without any zero headers at the end ourselves for a long long time
+ * (old tar.cpp).
+ *
+ * So, we're fine if the state is 'FIRST' or 'ZERO' here, but we'll barf
+ * if we're in the middle of a multi-header stream (long GNU names, sparse
+ * files, PAX, etc).
+ */
+ return pThis->enmState == RTZIPTARREADERSTATE_FIRST
+ || pThis->enmState == RTZIPTARREADERSTATE_ZERO;
+}
+
+
+/**
+ * Checks if the current TAR object is a hard link or not.
+ *
+ * @returns true if it is, false if not.
+ * @param pThis The TAR reader instance.
+ */
+static bool rtZipTarReaderIsHardlink(PRTZIPTARREADER pThis)
+{
+ return pThis->Hdr.Common.typeflag == RTZIPTAR_TF_LINK;
+}
+
+
+/**
+ * Checks if the TAR header includes a POSIX or GNU user name field.
+ *
+ * @returns true / false.
+ * @param pThis The TAR reader instance.
+ */
+DECLINLINE(bool) rtZipTarReaderHasUserName(PRTZIPTARREADER pThis)
+{
+ return pThis->Hdr.Common.uname[0] != '\0'
+ && ( pThis->enmType == RTZIPTARTYPE_POSIX
+ || pThis->enmType == RTZIPTARTYPE_GNU);
+}
+
+
+/**
+ * Checks if the TAR header includes a POSIX or GNU group name field.
+ *
+ * @returns true / false.
+ * @param pThis The TAR reader instance.
+ */
+DECLINLINE(bool) rtZipTarReaderHasGroupName(PRTZIPTARREADER pThis)
+{
+ return pThis->Hdr.Common.gname[0] != '\0'
+ && ( pThis->enmType == RTZIPTARTYPE_POSIX
+ || pThis->enmType == RTZIPTARTYPE_GNU);
+}
+
+
+/*
+ *
+ * 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) rtZipTarFssBaseObj_Close(void *pvThis)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+
+ /* Currently there is nothing we really have to do here. */
+ pThis->offHdr = -1;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFssBaseObj_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+
+ /*
+ * Copy the desired data.
+ */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER;
+ pObjInfo->Attr.u.UnixOwner.uid = pThis->ObjInfo.Attr.u.Unix.uid;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ if (rtZipTarReaderHasUserName(pThis->pTarReader))
+ RTStrCopy(pObjInfo->Attr.u.UnixOwner.szName, sizeof(pObjInfo->Attr.u.UnixOwner.szName),
+ pThis->pTarReader->Hdr.Common.uname);
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP;
+ pObjInfo->Attr.u.UnixGroup.gid = pThis->ObjInfo.Attr.u.Unix.gid;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ if (rtZipTarReaderHasGroupName(pThis->pTarReader))
+ RTStrCopy(pObjInfo->Attr.u.UnixGroup.szName, sizeof(pObjInfo->Attr.u.UnixGroup.szName),
+ pThis->pTarReader->Hdr.Common.gname);
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE;
+ RT_ZERO(pObjInfo->Attr.u);
+ break;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tar filesystem base object operations.
+ */
+static const RTVFSOBJOPS g_rtZipTarFssBaseObjOps =
+{
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_BASE,
+ "TarFsStream::Obj",
+ rtZipTarFssBaseObj_Close,
+ rtZipTarFssBaseObj_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_Close(void *pvThis)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)pvThis;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return rtZipTarFssBaseObj_Close(&pThis->BaseObj);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)pvThis;
+ return rtZipTarFssBaseObj_QueryInfo(&pThis->BaseObj, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)pvThis;
+ Assert(pSgBuf->cSegs == 1);
+
+ /*
+ * Make offset into a real offset so it's possible to do random access
+ * on TAR files that are seekable. Fend of reads beyond the end of the
+ * stream.
+ */
+ if (off < 0)
+ off = pThis->offFile;
+ if (off >= pThis->cbFile)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+
+ Assert(pThis->cbFile >= pThis->offFile);
+ uint64_t cbLeft = (uint64_t)(pThis->cbFile - off);
+ size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
+ 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, pThis->offStart + off, pSgBuf->paSegs[0].pvSeg, cbToRead, fBlocking, pcbRead);
+ pThis->offFile = off + *pcbRead;
+ if (pThis->offFile >= pThis->cbFile)
+ {
+ Assert(pThis->offFile == pThis->cbFile);
+ pThis->fEndOfStream = true;
+ RTVfsIoStrmSkip(pThis->hVfsIos, pThis->cbPadding);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_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) rtZipTarFssIos_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) rtZipTarFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)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) rtZipTarFssIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tar I/O stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_rtZipTarFssIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "TarFsStream::IoStream",
+ rtZipTarFssIos_Close,
+ rtZipTarFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipTarFssIos_Read,
+ rtZipTarFssIos_Write,
+ rtZipTarFssIos_Flush,
+ rtZipTarFssIos_PollOne,
+ rtZipTarFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarFssSym_Close(void *pvThis)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+ return rtZipTarFssBaseObj_Close(pThis);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFssSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+ return rtZipTarFssBaseObj_QueryInfo(pThis, pObjInfo, enmAddAttr);
+}
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtZipTarFssSym_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) rtZipTarFssSym_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) rtZipTarFssSym_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) rtZipTarFssSym_Read(void *pvThis, char *pszTarget, size_t cbTarget)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+ return RTStrCopy(pszTarget, cbTarget, pThis->pTarReader->szTarget);
+}
+
+
+/**
+ * Tar symbolic (and hardlink) operations.
+ */
+static const RTVFSSYMLINKOPS g_rtZipTarFssSymOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_SYMLINK,
+ "TarFsStream::Symlink",
+ rtZipTarFssSym_Close,
+ rtZipTarFssSym_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSSYMLINKOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj),
+ rtZipTarFssSym_SetMode,
+ rtZipTarFssSym_SetTimes,
+ rtZipTarFssSym_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtZipTarFssSym_Read,
+ RTVFSSYMLINKOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarFss_Close(void *pvThis)
+{
+ PRTZIPTARFSSTREAM pThis = (PRTZIPTARFSSTREAM)pvThis;
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFss_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARFSSTREAM pThis = (PRTZIPTARFSSTREAM)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}
+ */
+DECL_HIDDEN_CALLBACK(int) rtZipTarFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ PRTZIPTARFSSTREAM pThis = (PRTZIPTARFSSTREAM)pvThis;
+
+ /*
+ * Dispense with the current object.
+ */
+ if (pThis->hVfsCurObj != NIL_RTVFSOBJ)
+ {
+ if (pThis->pCurIosData)
+ {
+ pThis->pCurIosData->fEndOfStream = true;
+ pThis->pCurIosData->offFile = pThis->pCurIosData->cbFile;
+ pThis->pCurIosData = NULL;
+ }
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ }
+
+ /*
+ * 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;
+
+ /*
+ * Make sure the input stream is in the right place.
+ */
+ RTFOFF offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ while ( offHdr >= 0
+ && offHdr < pThis->offNextHdr)
+ {
+ int rc = RTVfsIoStrmSkip(pThis->hVfsIos, pThis->offNextHdr - offHdr);
+ if (RT_FAILURE(rc))
+ {
+ /** @todo Ignore if we're at the end of the stream? */
+ return pThis->rcFatal = rc;
+ }
+
+ offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ }
+
+ if (offHdr < 0)
+ return pThis->rcFatal = (int)offHdr;
+ if (offHdr > pThis->offNextHdr)
+ return pThis->rcFatal = VERR_INTERNAL_ERROR_3;
+ Assert(pThis->offNextHdr == offHdr);
+ pThis->offCurHdr = offHdr;
+
+ /*
+ * Consume TAR headers.
+ */
+ size_t cbHdrs = 0;
+ int rc;
+ do
+ {
+ /*
+ * Read the next header.
+ */
+ RTZIPTARHDR Hdr;
+ size_t cbRead;
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, &Hdr, sizeof(Hdr), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ if (rc == VINF_EOF && cbRead == 0)
+ {
+ pThis->fEndOfStream = true;
+ return rtZipTarReaderIsAtEnd(&pThis->TarReader) ? VERR_EOF : VERR_TAR_UNEXPECTED_EOS;
+ }
+ if (cbRead != sizeof(Hdr))
+ return pThis->rcFatal = VERR_TAR_UNEXPECTED_EOS;
+
+ cbHdrs += sizeof(Hdr);
+
+ /*
+ * Parse the it.
+ */
+ rc = rtZipTarReaderParseHeader(&pThis->TarReader, &Hdr);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ } while (rtZipTarReaderExpectingMoreHeaders(&pThis->TarReader));
+
+ pThis->offNextHdr = offHdr + cbHdrs;
+
+ /*
+ * Fill an object info structure from the current TAR state.
+ */
+ RTFSOBJINFO Info;
+ rc = rtZipTarReaderGetFsObjInfo(&pThis->TarReader, &Info);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ /*
+ * Create an object of the appropriate type.
+ */
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ RTFMODE fType = Info.Attr.fMode & RTFS_TYPE_MASK;
+ if (rtZipTarReaderIsHardlink(&pThis->TarReader))
+ fType = RTFS_TYPE_SYMLINK;
+ switch (fType)
+ {
+ /*
+ * Files are represented by a VFS I/O stream.
+ */
+ case RTFS_TYPE_FILE:
+ {
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPTARIOSTREAM pIosData;
+ rc = RTVfsNewIoStream(&g_rtZipTarFssIosOps,
+ sizeof(*pIosData),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIos,
+ (void **)&pIosData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pIosData->BaseObj.offHdr = offHdr;
+ pIosData->BaseObj.offNextHdr = pThis->offNextHdr;
+ pIosData->BaseObj.pTarReader = &pThis->TarReader;
+ pIosData->BaseObj.ObjInfo = Info;
+ pIosData->cbFile = Info.cbObject;
+ pIosData->offFile = 0;
+ pIosData->offStart = RTVfsIoStrmTell(pThis->hVfsIos);
+ pIosData->cbPadding = (uint32_t)(Info.cbAllocated - Info.cbObject);
+ pIosData->fEndOfStream = false;
+ pIosData->hVfsIos = pThis->hVfsIos;
+ RTVfsIoStrmRetain(pThis->hVfsIos);
+
+ pThis->pCurIosData = pIosData;
+ pThis->offNextHdr += pIosData->cbFile + pIosData->cbPadding;
+
+ enmType = RTVFSOBJTYPE_IO_STREAM;
+ hVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ break;
+ }
+
+ /*
+ * We represent hard links using a symbolic link object. This fits
+ * best with the way TAR stores it and there is currently no better
+ * fitting VFS type alternative.
+ */
+ case RTFS_TYPE_SYMLINK:
+ {
+ RTVFSSYMLINK hVfsSym;
+ PRTZIPTARBASEOBJ pBaseObjData;
+ rc = RTVfsNewSymlink(&g_rtZipTarFssSymOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsSym,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->offHdr = offHdr;
+ pBaseObjData->offNextHdr = pThis->offNextHdr;
+ pBaseObjData->pTarReader = &pThis->TarReader;
+ pBaseObjData->ObjInfo = Info;
+
+ enmType = RTVFSOBJTYPE_SYMLINK;
+ hVfsObj = RTVfsObjFromSymlink(hVfsSym);
+ RTVfsSymlinkRelease(hVfsSym);
+ break;
+ }
+
+ /*
+ * All other objects are repesented using a VFS base object since they
+ * carry no data streams (unless some TAR extension implements extended
+ * attributes / alternative streams).
+ */
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DIRECTORY:
+ case RTFS_TYPE_FIFO:
+ {
+ PRTZIPTARBASEOBJ pBaseObjData;
+ rc = RTVfsNewBaseObj(&g_rtZipTarFssBaseObjOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsObj,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->offHdr = offHdr;
+ pBaseObjData->offNextHdr = pThis->offNextHdr;
+ pBaseObjData->pTarReader = &pThis->TarReader;
+ pBaseObjData->ObjInfo = Info;
+
+ enmType = RTVFSOBJTYPE_BASE;
+ break;
+ }
+
+ default:
+ AssertFailed();
+ return pThis->rcFatal = VERR_INTERNAL_ERROR_5;
+ }
+ pThis->hVfsCurObj = hVfsObj;
+
+ /*
+ * Set the return data and we're done.
+ */
+ if (ppszName)
+ {
+ rc = RTStrDupEx(ppszName, pThis->TarReader.szName);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (phVfsObj)
+ {
+ RTVfsObjRetain(hVfsObj);
+ *phVfsObj = hVfsObj;
+ }
+
+ if (penmType)
+ *penmType = enmType;
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Tar filesystem stream operations.
+ */
+static const RTVFSFSSTREAMOPS rtZipTarFssOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "TarFsStream",
+ rtZipTarFss_Close,
+ rtZipTarFss_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipTarFss_Next,
+ NULL,
+ NULL,
+ NULL,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+/**
+ * Internal function use both by RTZipTarFsStreamFromIoStream() and by
+ * RTZipTarFsStreamForFile() in updating mode.
+ */
+DECLHIDDEN(void) rtZipTarReaderInit(PRTZIPTARFSSTREAM pThis, RTVFSIOSTREAM hVfsIos, uint64_t offStart)
+{
+ pThis->hVfsIos = hVfsIos;
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+ pThis->offStart = offStart;
+ pThis->offNextHdr = offStart;
+ pThis->fEndOfStream = false;
+ pThis->rcFatal = VINF_SUCCESS;
+ pThis->TarReader.enmPrevType= RTZIPTARTYPE_INVALID;
+ pThis->TarReader.enmType = RTZIPTARTYPE_INVALID;
+ pThis->TarReader.enmState = RTZIPTARREADERSTATE_FIRST;
+
+ /* Don't check if it's a TAR stream here, do that in the
+ rtZipTarFss_Next. */
+}
+
+
+RTDECL(int) RTZipTarFsStreamFromIoStream(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);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPTARFSSTREAM pThis;
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&rtZipTarFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_READ,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rtZipTarReaderInit(pThis, hVfsIosIn, fFlags);
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosIn);
+ return rc;
+}
+
+
+/**
+ * Used by RTZipTarFsStreamTruncate to resolve @a hVfsObj.
+ */
+DECLHIDDEN(PRTZIPTARBASEOBJ) rtZipTarFsStreamBaseObjToPrivate(PRTZIPTARFSSTREAM pThis, RTVFSOBJ hVfsObj)
+{
+ PRTZIPTARBASEOBJ pThisObj;
+ RTVFSOBJTYPE enmType = RTVfsObjGetType(hVfsObj);
+ switch (enmType)
+ {
+ case RTVFSOBJTYPE_IO_STREAM:
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, NULL);
+ PRTZIPTARIOSTREAM pThisStrm = (PRTZIPTARIOSTREAM)RTVfsIoStreamToPrivate(hVfsIos, &g_rtZipTarFssIosOps);
+ RTVfsIoStrmRelease(hVfsIos);
+ pThisObj = &pThisStrm->BaseObj;
+ break;
+ }
+
+ case RTVFSOBJTYPE_SYMLINK:
+ {
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, NULL);
+ pThisObj = (PRTZIPTARBASEOBJ)RTVfsSymlinkToPrivate(hVfsSymlink, &g_rtZipTarFssSymOps);
+ RTVfsSymlinkRelease(hVfsSymlink);
+ break;
+ }
+
+ case RTVFSOBJTYPE_BASE:
+ pThisObj = (PRTZIPTARBASEOBJ)RTVfsObjToPrivate(hVfsObj, &g_rtZipTarFssBaseObjOps);
+ break;
+
+ default:
+ /** @todo implement. */
+ AssertFailedReturn(NULL);
+ }
+
+ AssertReturn(pThisObj->pTarReader == &pThis->TarReader, NULL);
+ return pThisObj;
+}
+
diff --git a/src/VBox/Runtime/common/zip/tarvfsreader.h b/src/VBox/Runtime/common/zip/tarvfsreader.h
new file mode 100644
index 00000000..ebe74fbc
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tarvfsreader.h
@@ -0,0 +1,179 @@
+/* $Id: tarvfsreader.h $ */
+/** @file
+ * IPRT - TAR Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_zip_tarvfsreader_h
+#define IPRT_INCLUDED_SRC_common_zip_tarvfsreader_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "tar.h"
+
+
+/**
+ * TAR reader state machine states.
+ */
+typedef enum RTZIPTARREADERSTATE
+{
+ /** Invalid state. */
+ RTZIPTARREADERSTATE_INVALID = 0,
+ /** Expecting the next file/dir/whatever entry. */
+ RTZIPTARREADERSTATE_FIRST,
+ /** Expecting more zero headers or the end of the stream. */
+ RTZIPTARREADERSTATE_ZERO,
+ /** Expecting a GNU long name. */
+ RTZIPTARREADERSTATE_GNU_LONGNAME,
+ /** Expecting a GNU long link. */
+ RTZIPTARREADERSTATE_GNU_LONGLINK,
+ /** Expecting a normal header or another GNU specific one. */
+ RTZIPTARREADERSTATE_GNU_NEXT,
+ /** End of valid states (not included). */
+ RTZIPTARREADERSTATE_END
+} RTZIPTARREADERSTATE;
+
+/**
+ * Tar reader instance data.
+ */
+typedef struct RTZIPTARREADER
+{
+ /** Zero header counter. */
+ uint32_t cZeroHdrs;
+ /** The state machine state. */
+ RTZIPTARREADERSTATE enmState;
+ /** The type of the previous TAR header.
+ * @remarks Same a enmType for the first header in the TAR stream. */
+ RTZIPTARTYPE enmPrevType;
+ /** The type of the current TAR header. */
+ RTZIPTARTYPE enmType;
+ /** The current header. */
+ RTZIPTARHDR Hdr;
+ /** The expected long name/link length (GNU). */
+ uint32_t cbGnuLongExpect;
+ /** The current long name/link length (GNU). */
+ uint32_t offGnuLongCur;
+ /** The name of the current object.
+ * This is for handling GNU and PAX long names. */
+ char szName[RTPATH_MAX];
+ /** The current link target if symlink or hardlink. */
+ char szTarget[RTPATH_MAX];
+} RTZIPTARREADER;
+/** Pointer to the TAR reader instance data. */
+typedef RTZIPTARREADER *PRTZIPTARREADER;
+
+/**
+ * Tar directory, character device, block device, fifo socket or symbolic link.
+ */
+typedef struct RTZIPTARBASEOBJ
+{
+ /** The stream offset of the (first) header in the input stream/file. */
+ RTFOFF offHdr;
+ /** The stream offset of the first header of the next object (for truncating the
+ * tar file after this object (updating)). */
+ RTFOFF offNextHdr;
+ /** Pointer to the reader instance data (resides in the filesystem
+ * stream).
+ * @todo Fix this so it won't go stale... Back ref from this obj to fss? */
+ PRTZIPTARREADER pTarReader;
+ /** The object info with unix attributes. */
+ RTFSOBJINFO ObjInfo;
+} RTZIPTARBASEOBJ;
+/** Pointer to a TAR filesystem stream base object. */
+typedef RTZIPTARBASEOBJ *PRTZIPTARBASEOBJ;
+
+
+/**
+ * Tar file represented as a VFS I/O stream.
+ */
+typedef struct RTZIPTARIOSTREAM
+{
+ /** The basic TAR object data. */
+ RTZIPTARBASEOBJ BaseObj;
+ /** The number of bytes in the file. */
+ RTFOFF cbFile;
+ /** The current file position. */
+ RTFOFF offFile;
+ /** The start position in the hVfsIos (for seekable hVfsIos). */
+ RTFOFF offStart;
+ /** The number of padding bytes following the file. */
+ uint32_t cbPadding;
+ /** Set if we've reached the end of this file. */
+ bool fEndOfStream;
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+} RTZIPTARIOSTREAM;
+/** Pointer to a the private data of a TAR file I/O stream. */
+typedef RTZIPTARIOSTREAM *PRTZIPTARIOSTREAM;
+
+
+/**
+ * Tar filesystem stream private data.
+ */
+typedef struct RTZIPTARFSSTREAM
+{
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+
+ /** The current object (referenced). */
+ RTVFSOBJ hVfsCurObj;
+ /** Pointer to the private data if hVfsCurObj is representing a file. */
+ PRTZIPTARIOSTREAM pCurIosData;
+
+ /** The start offset. */
+ RTFOFF offStart;
+ /** The offset of the next header. */
+ RTFOFF offNextHdr;
+ /** The offset of the first header for the current object.
+ * When reaching the end, this will be the same as offNextHdr which will be
+ * pointing to the first zero header */
+ RTFOFF offCurHdr;
+
+ /** Set if we've reached the end of the stream. */
+ bool fEndOfStream;
+ /** Set if we've encountered a fatal error. */
+ int rcFatal;
+
+ /** The TAR reader instance data. */
+ RTZIPTARREADER TarReader;
+} RTZIPTARFSSTREAM;
+/** Pointer to a the private data of a TAR filesystem stream. */
+typedef RTZIPTARFSSTREAM *PRTZIPTARFSSTREAM;
+
+DECLHIDDEN(void) rtZipTarReaderInit(PRTZIPTARFSSTREAM pThis, RTVFSIOSTREAM hVfsIos, uint64_t offStart);
+DECL_HIDDEN_CALLBACK(int) rtZipTarFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj);
+DECLHIDDEN(PRTZIPTARBASEOBJ) rtZipTarFsStreamBaseObjToPrivate(PRTZIPTARFSSTREAM pThis, RTVFSOBJ hVfsObj);
+
+#endif /* !IPRT_INCLUDED_SRC_common_zip_tarvfsreader_h */
+
diff --git a/src/VBox/Runtime/common/zip/tarvfswriter.cpp b/src/VBox/Runtime/common/zip/tarvfswriter.cpp
new file mode 100644
index 00000000..ac5d603a
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tarvfswriter.cpp
@@ -0,0 +1,2363 @@
+/* $Id: tarvfswriter.cpp $ */
+/** @file
+ * IPRT - TAR Virtual Filesystem, Writer.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/zero.h>
+
+#include "tarvfsreader.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The TAR block size we're using in this implementation.
+ * @remarks Should technically be user configurable, but we don't currently need that. */
+#define RTZIPTAR_BLOCKSIZE sizeof(RTZIPTARHDR)
+
+/** Minimum file size we consider for sparse files. */
+#define RTZIPTAR_MIN_SPARSE _64K
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * A data span descriptor in a sparse file.
+ */
+typedef struct RTZIPTARSPARSESPAN
+{
+ /** Byte offset into the file of the data. */
+ uint64_t off;
+ /** Number of bytes of data, rounded up to a multiple of blocksize. */
+ uint64_t cb;
+} RTZIPTARSPARSESPAN;
+/** Pointer to a data span. */
+typedef RTZIPTARSPARSESPAN *PRTZIPTARSPARSESPAN;
+/** Pointer to a const data span. */
+typedef RTZIPTARSPARSESPAN const *PCRTZIPTARSPARSESPAN;
+
+/**
+ * Chunk of TAR sparse file data spans.
+ */
+typedef struct RTZIPTARSPARSECHUNK
+{
+ /** List entry. */
+ RTLISTNODE Entry;
+ /** Array of data spans. */
+ RTZIPTARSPARSESPAN aSpans[63];
+} RTZIPTARSPARSECHUNK;
+AssertCompile(sizeof(RTZIPTARSPARSECHUNK) <= 1024);
+AssertCompile(sizeof(RTZIPTARSPARSECHUNK) >= 1008);
+/** Pointer to a chunk of TAR data spans. */
+typedef RTZIPTARSPARSECHUNK *PRTZIPTARSPARSECHUNK;
+/** Pointer to a const chunk of TAR data spans. */
+typedef RTZIPTARSPARSECHUNK const *PCRTZIPTARSPARSECHUNK;
+
+/**
+ * TAR sparse file info.
+ */
+typedef struct RTZIPTARSPARSE
+{
+ /** Number of data bytes (real size). */
+ uint64_t cbDataSpans;
+ /** Number of data spans. */
+ uint32_t cDataSpans;
+ /** The index of the next span in the tail chunk (to avoid modulus 63). */
+ uint32_t iNextSpan;
+ /** Head of the data span chunk list (PRTZIPTARSPARSECHUNK). */
+ RTLISTANCHOR ChunkHead;
+} RTZIPTARSPARSE;
+/** Pointer to TAR sparse file info. */
+typedef RTZIPTARSPARSE *PRTZIPTARSPARSE;
+/** Pointer to a const TAR sparse file info. */
+typedef RTZIPTARSPARSE const *PCRTZIPTARSPARSE;
+
+
+/** Pointer to a the private data of a TAR filesystem stream. */
+typedef struct RTZIPTARFSSTREAMWRITER *PRTZIPTARFSSTREAMWRITER;
+
+
+/**
+ * Instance data for a file or I/O stream returned by
+ * RTVFSFSSTREAMOPS::pfnPushFile.
+ */
+typedef struct RTZIPTARFSSTREAMWRITERPUSH
+{
+ /** Pointer to the parent FS stream writer instance.
+ * This is set to NULL should the push object live longer than the stream. */
+ PRTZIPTARFSSTREAMWRITER pParent;
+ /** The header offset, UINT64_MAX if non-seekable output. */
+ uint64_t offHdr;
+ /** The data offset, UINT64_MAX if non-seekable output. */
+ uint64_t offData;
+ /** The current I/O stream position (relative to offData). */
+ uint64_t offCurrent;
+ /** The expected size amount of file content, or max file size if open-ended. */
+ uint64_t cbExpected;
+ /** The current amount of file content written. */
+ uint64_t cbCurrent;
+ /** Object info copy for rtZipTarWriterPush_QueryInfo. */
+ RTFSOBJINFO ObjInfo;
+ /** Set if open-ended file size requiring a tar header update when done. */
+ bool fOpenEnded;
+} RTZIPTARFSSTREAMWRITERPUSH;
+/** Pointer to a push I/O instance. */
+typedef RTZIPTARFSSTREAMWRITERPUSH *PRTZIPTARFSSTREAMWRITERPUSH;
+
+
+/**
+ * Tar filesystem stream private data.
+ */
+typedef struct RTZIPTARFSSTREAMWRITER
+{
+ /** The output I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+ /** Non-nil if the output is a file. */
+ RTVFSFILE hVfsFile;
+
+ /** The current push file. NULL if none. */
+ PRTZIPTARFSSTREAMWRITERPUSH pPush;
+
+ /** The TAR format. */
+ RTZIPTARFORMAT enmFormat;
+ /** Set if we've encountered a fatal error. */
+ int rcFatal;
+ /** Flags, RTZIPTAR_C_XXX. */
+ uint32_t fFlags;
+
+ /** Number of bytes written. */
+ uint64_t cbWritten;
+
+ /** @name Attribute overrides.
+ * @{
+ */
+ RTUID uidOwner; /**< Owner, NIL_RTUID if no change. */
+ char *pszOwner; /**< Owner, NULL if no change. */
+ RTGID gidGroup; /**< Group, NIL_RTGID if no change. */
+ char *pszGroup; /**< Group, NULL if no change. */
+ char *pszPrefix; /**< Path prefix, NULL if no change. */
+ size_t cchPrefix; /**< The length of pszPrefix. */
+ PRTTIMESPEC pModTime; /**< Modification time, NULL of no change. */
+ RTTIMESPEC ModTime; /**< pModTime points to this. */
+ RTFMODE fFileModeAndMask; /**< File mode AND mask. */
+ RTFMODE fFileModeOrMask; /**< File mode OR mask. */
+ RTFMODE fDirModeAndMask; /**< Directory mode AND mask. */
+ RTFMODE fDirModeOrMask; /**< Directory mode OR mask. */
+ /** @} */
+
+ /** When in update mode (RTZIPTAR_C_UPDATE) we have an reader FSS instance,
+ * though w/o the RTVFSFSSTREAM bits. (Allocated after this structure.) */
+ PRTZIPTARFSSTREAM pRead;
+ /** Set if we're in writing mode and pfnNext shall fail. */
+ bool fWriting;
+
+
+ /** Number of headers returned by rtZipTarFssWriter_ObjInfoToHdr. */
+ uint32_t cHdrs;
+ /** Header buffers returned by rtZipTarFssWriter_ObjInfoToHdr. */
+ RTZIPTARHDR aHdrs[3];
+} RTZIPTARFSSTREAMWRITER;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) rtZipTarWriterPush_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual);
+static int rtZipTarFssWriter_CompleteCurrentPushFile(PRTZIPTARFSSTREAMWRITER pThis);
+static int rtZipTarFssWriter_AddFile(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos,
+ PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm);
+
+
+/**
+ * Calculates the header checksum and stores it in the chksum field.
+ *
+ * @returns IPRT status code.
+ * @param pHdr The header.
+ */
+static int rtZipTarFssWriter_ChecksumHdr(PRTZIPTARHDR pHdr)
+{
+ int32_t iUnsignedChksum;
+ rtZipTarCalcChkSum(pHdr, &iUnsignedChksum, NULL);
+
+ int rc = RTStrFormatU32(pHdr->Common.chksum, sizeof(pHdr->Common.chksum), iUnsignedChksum,
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pHdr->Common.chksum) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Formats a 12 character wide file offset or size field.
+ *
+ * This is mainly used for RTZIPTARHDR::Common.size, but also for formatting the
+ * sparse map.
+ *
+ * @returns IPRT status code.
+ * @param pach12Field The 12 character wide destination field.
+ * @param off The offset to set.
+ */
+static int rtZipTarFssWriter_FormatOffset(char pach12Field[12], uint64_t off)
+{
+ /*
+ * Is the size small enough for the standard octal string encoding?
+ *
+ * Note! We could actually use the terminator character as well if we liked,
+ * but let not do that as it's easier to test this way.
+ */
+ if (off < _4G * 2U)
+ {
+ int rc = RTStrFormatU64(pach12Field, 12, off, 8 /*uBase*/, -1 /*cchWidth*/, 12 - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, rc);
+ }
+ /*
+ * No, use the base 256 extension. Set the highest bit of the left most
+ * character. We don't deal with negatives here, cause the size have to
+ * be greater than zero.
+ *
+ * Note! The base-256 extension are never used by gtar or libarchive
+ * with the "ustar \0" format version, only the later
+ * "ustar\000" version. However, this shouldn't cause much
+ * trouble as they are not picky about what they read.
+ */
+ /** @todo above note is wrong: GNU tar only uses base-256 with the GNU tar
+ * format, i.e. "ustar \0", see create.c line 303 in v1.29. */
+ else
+ {
+ size_t cchField = 12 - 1;
+ unsigned char *puchField = (unsigned char *)pach12Field;
+ puchField[0] = 0x80;
+ do
+ {
+ puchField[cchField--] = off & 0xff;
+ off >>= 8;
+ } while (cchField);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Creates one or more tar headers for the object.
+ *
+ * Returns RTZIPTARFSSTREAMWRITER::aHdrs and RTZIPTARFSSTREAMWRITER::cHdrs.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param hVfsIos The I/O stream of the file.
+ * @param fFlags The RTVFSFSSTREAMOPS::pfnAdd flags.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ * @param chType The tar record type, UINT8_MAX for default.
+ */
+static int rtZipTarFssWriter_ObjInfoToHdr(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, PCRTFSOBJINFO pObjInfo,
+ const char *pszOwnerNm, const char *pszGroupNm, uint8_t chType)
+{
+ pThis->cHdrs = 0;
+ RT_ZERO(pThis->aHdrs[0]);
+
+ /*
+ * The path name first. Make sure to flip DOS slashes.
+ */
+ size_t cchPath = strlen(pszPath);
+ if (cchPath < sizeof(pThis->aHdrs[0].Common.name))
+ {
+ memcpy(pThis->aHdrs[0].Common.name, pszPath, cchPath + 1);
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_UNIX
+ char *pszDosSlash = strchr(pThis->aHdrs[0].Common.name, '\\');
+ while (pszDosSlash)
+ {
+ *pszDosSlash = '/';
+ pszDosSlash = strchr(pszDosSlash + 1, '\\');
+ }
+#endif
+ }
+ else
+ {
+ /** @todo implement gnu and pax long name extensions. */
+ return VERR_TAR_NAME_TOO_LONG;
+ }
+
+ /*
+ * File mode. ASSUME that the unix part of the IPRT mode mask is
+ * compatible with the TAR/Unix world.
+ */
+ uint32_t uValue = pObjInfo->Attr.fMode & RTFS_UNIX_MASK;
+ if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ uValue = (uValue & pThis->fDirModeAndMask) | pThis->fDirModeOrMask;
+ else
+ uValue = (uValue & pThis->fFileModeAndMask) | pThis->fFileModeOrMask;
+ int rc = RTStrFormatU32(pThis->aHdrs[0].Common.mode, sizeof(pThis->aHdrs[0].Common.mode), uValue, 8 /*uBase*/,
+ -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.mode) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+
+ /*
+ * uid & gid. Just guard against NIL values as they won't fit.
+ */
+ uValue = pThis->uidOwner != NIL_RTUID ? pThis->uidOwner
+ : pObjInfo->Attr.u.Unix.uid != NIL_RTUID ? pObjInfo->Attr.u.Unix.uid : 0;
+ rc = RTStrFormatU32(pThis->aHdrs[0].Common.uid, sizeof(pThis->aHdrs[0].Common.uid), uValue,
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.uid) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+
+ uValue = pThis->gidGroup != NIL_RTGID ? pThis->gidGroup
+ : pObjInfo->Attr.u.Unix.gid != NIL_RTGID ? pObjInfo->Attr.u.Unix.gid : 0;
+ rc = RTStrFormatU32(pThis->aHdrs[0].Common.gid, sizeof(pThis->aHdrs[0].Common.gid), uValue,
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.gid) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+
+ /*
+ * The file size.
+ */
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, pObjInfo->cbObject);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Modification time relative to unix epoc.
+ */
+ rc = RTStrFormatU64(pThis->aHdrs[0].Common.mtime, sizeof(pThis->aHdrs[0].Common.mtime),
+ RTTimeSpecGetSeconds(pThis->pModTime ? pThis->pModTime : &pObjInfo->ModificationTime),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.mtime) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, rc);
+
+ /* Skipping checksum for now */
+
+ /*
+ * The type flag.
+ */
+ if (chType == UINT8_MAX)
+ switch (pObjInfo->Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FIFO: chType = RTZIPTAR_TF_FIFO; break;
+ case RTFS_TYPE_DEV_CHAR: chType = RTZIPTAR_TF_CHR; break;
+ case RTFS_TYPE_DIRECTORY: chType = RTZIPTAR_TF_DIR; break;
+ case RTFS_TYPE_DEV_BLOCK: chType = RTZIPTAR_TF_BLK; break;
+ case RTFS_TYPE_FILE: chType = RTZIPTAR_TF_NORMAL; break;
+ case RTFS_TYPE_SYMLINK: chType = RTZIPTAR_TF_SYMLINK; break;
+ case RTFS_TYPE_SOCKET: chType = RTZIPTAR_TF_FIFO; break;
+ case RTFS_TYPE_WHITEOUT: AssertFailedReturn(VERR_WRONG_TYPE);
+ }
+ pThis->aHdrs[0].Common.typeflag = chType;
+
+ /* No link name, at least not for now. Caller might set it. */
+
+ /*
+ * Set TAR record magic and version.
+ */
+ if (pThis->enmFormat == RTZIPTARFORMAT_GNU)
+ memcpy(pThis->aHdrs[0].Gnu.magic, RTZIPTAR_GNU_MAGIC, sizeof(pThis->aHdrs[0].Gnu.magic));
+ else if ( pThis->enmFormat == RTZIPTARFORMAT_USTAR
+ || pThis->enmFormat == RTZIPTARFORMAT_PAX)
+ {
+ memcpy(pThis->aHdrs[0].Common.magic, RTZIPTAR_USTAR_MAGIC, sizeof(pThis->aHdrs[0].Common.magic));
+ memcpy(pThis->aHdrs[0].Common.version, RTZIPTAR_USTAR_VERSION, sizeof(pThis->aHdrs[0].Common.version));
+ }
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Owner and group names. Silently truncate them for now.
+ */
+ RTStrCopy(pThis->aHdrs[0].Common.uname, sizeof(pThis->aHdrs[0].Common.uname), pThis->pszOwner ? pThis->pszOwner : pszOwnerNm);
+ RTStrCopy(pThis->aHdrs[0].Common.gname, sizeof(pThis->aHdrs[0].Common.uname), pThis->pszGroup ? pThis->pszGroup : pszGroupNm);
+
+ /*
+ * Char/block device numbers.
+ */
+ if ( RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode)
+ || RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode) )
+ {
+ rc = RTStrFormatU32(pThis->aHdrs[0].Common.devmajor, sizeof(pThis->aHdrs[0].Common.devmajor),
+ RTDEV_MAJOR(pObjInfo->Attr.u.Unix.Device),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.devmajor) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+
+ rc = RTStrFormatU32(pThis->aHdrs[0].Common.devminor, sizeof(pThis->aHdrs[0].Common.devmajor),
+ RTDEV_MINOR(pObjInfo->Attr.u.Unix.Device),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.devmajor) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+ }
+
+#if 0 /** @todo why doesn't this work? */
+ /*
+ * Set GNU specific stuff.
+ */
+ if (pThis->enmFormat == RTZIPTARFORMAT_GNU)
+ {
+ rc = RTStrFormatU64(pThis->aHdrs[0].Gnu.ctime, sizeof(pThis->aHdrs[0].Gnu.ctime),
+ RTTimeSpecGetSeconds(&pObjInfo->ChangeTime),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Gnu.ctime) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, rc);
+
+ rc = RTStrFormatU64(pThis->aHdrs[0].Gnu.atime, sizeof(pThis->aHdrs[0].Gnu.atime),
+ RTTimeSpecGetSeconds(&pObjInfo->ChangeTime),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Gnu.atime) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, rc);
+ }
+#endif
+
+ /*
+ * Finally the checksum.
+ */
+ pThis->cHdrs = 1;
+ return rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+}
+
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Close(void *pvThis)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ if (pParent)
+ {
+ if (pParent->pPush == pPush)
+ rtZipTarFssWriter_CompleteCurrentPushFile(pParent);
+ else
+ AssertFailedStmt(pPush->pParent = NULL);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+
+ /* Basic info (w/ additional unix attribs). */
+ *pObjInfo = pPush->ObjInfo;
+ pObjInfo->cbObject = pPush->cbCurrent;
+ pObjInfo->cbAllocated = RT_ALIGN_64(pPush->cbCurrent, RTZIPTAR_BLOCKSIZE);
+
+ /* Additional info. */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ Assert(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX);
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = pPush->ObjInfo.Attr.u.Unix.uid;
+ if (pPush->pParent)
+ strcpy(pObjInfo->Attr.u.UnixOwner.szName, pPush->pParent->aHdrs[0].Common.uname);
+ else
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = pPush->ObjInfo.Attr.u.Unix.gid;
+ if (pPush->pParent)
+ strcpy(pObjInfo->Attr.u.UnixGroup.szName, pPush->pParent->aHdrs[0].Common.uname);
+ else
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ break;
+
+ default:
+ AssertFailed();
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ /* No read support, sorry. */
+ RT_NOREF(pvThis, off, pSgBuf, fBlocking, pcbRead);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ AssertPtrReturn(pParent, VERR_WRONG_ORDER);
+
+ int rc = pParent->rcFatal;
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Single segment at a time.
+ */
+ Assert(pSgBuf->cSegs == 1);
+ size_t cbToWrite = pSgBuf->paSegs[0].cbSeg;
+ void const *pvToWrite = pSgBuf->paSegs[0].pvSeg;
+
+ /*
+ * Hopefully we don't need to seek. But if we do, let the seek method do
+ * it as it's not entirely trivial.
+ */
+ if ( off < 0
+ || (uint64_t)off == pPush->offCurrent)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtZipTarWriterPush_Seek(pvThis, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pPush->offCurrent <= pPush->cbExpected);
+ Assert(pPush->offCurrent <= pPush->cbCurrent);
+ AssertMsgReturn(cbToWrite <= pPush->cbExpected - pPush->offCurrent,
+ ("offCurrent=%#RX64 + cbToWrite=%#zx = %#RX64; cbExpected=%RX64\n",
+ pPush->offCurrent, cbToWrite, pPush->offCurrent + cbToWrite, pPush->cbExpected),
+ VERR_DISK_FULL);
+ size_t cbWritten = 0;
+ rc = RTVfsIoStrmWrite(pParent->hVfsIos, pvToWrite, cbToWrite, fBlocking, &cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ pPush->offCurrent += cbWritten;
+ if (pPush->offCurrent > pPush->cbCurrent)
+ {
+ pParent->cbWritten = pPush->offCurrent - pPush->cbCurrent;
+ pPush->cbCurrent = pPush->offCurrent;
+ }
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ }
+ }
+
+ /*
+ * Fatal errors get down here, non-fatal ones returns earlier.
+ */
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ pParent->rcFatal = rc;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Flush(void *pvThis)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ AssertPtrReturn(pParent, VERR_WRONG_ORDER);
+ int rc = pParent->rcFatal;
+ if (RT_SUCCESS(rc))
+ pParent->rcFatal = rc = RTVfsIoStrmFlush(pParent->hVfsIos);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ AssertPtrReturn(pParent, VERR_WRONG_ORDER);
+ return RTVfsIoStrmPoll(pParent->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ *poffActual = (RTFOFF)pPush->offCurrent;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnSkip}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Skip(void *pvThis, RTFOFF cb)
+{
+ RT_NOREF(pvThis, cb);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ RT_NOREF(pvThis, fMode, fMask);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ AssertPtrReturn(pParent, VERR_WRONG_ORDER);
+
+ int rc = pParent->rcFatal;
+ AssertRCReturn(rc, rc);
+ Assert(pPush->offCurrent <= pPush->cbCurrent);
+
+ /*
+ * Calculate the new file offset.
+ */
+ RTFOFF offNewSigned;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNewSigned = offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNewSigned = pPush->offCurrent + offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNewSigned = pPush->cbCurrent + offSeek;
+ break;
+ default:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Check the new file offset against expectations.
+ */
+ AssertMsgReturn(offNewSigned >= 0, ("offNewSigned=%RTfoff\n", offNewSigned), VERR_NEGATIVE_SEEK);
+
+ uint64_t offNew = (uint64_t)offNewSigned;
+ AssertMsgReturn(offNew <= pPush->cbExpected, ("offNew=%#RX64 cbExpected=%#Rx64\n", offNew, pPush->cbExpected), VERR_SEEK);
+
+ /*
+ * Any change at all? We can always hope...
+ */
+ if (offNew == pPush->offCurrent)
+ { }
+ /*
+ * Gap that needs zero filling?
+ */
+ else if (offNew > pPush->cbCurrent)
+ {
+ if (pPush->offCurrent != pPush->cbCurrent)
+ {
+ AssertReturn(pParent->hVfsFile != NIL_RTVFSFILE, VERR_NOT_A_FILE);
+ rc = RTVfsFileSeek(pParent->hVfsFile, pPush->offData + pPush->cbCurrent, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(rc))
+ return pParent->rcFatal = rc;
+ pPush->offCurrent = pPush->cbCurrent;
+ }
+
+ uint64_t cbToZero = offNew - pPush->cbCurrent;
+ rc = RTVfsIoStrmZeroFill(pParent->hVfsIos, cbToZero);
+ if (RT_FAILURE(rc))
+ return pParent->rcFatal = rc;
+ pParent->cbWritten += cbToZero;
+ pPush->cbCurrent = pPush->offCurrent = offNew;
+ }
+ /*
+ * Just change the file position to somewhere we've already written.
+ */
+ else
+ {
+ AssertReturn(pParent->hVfsFile != NIL_RTVFSFILE, VERR_NOT_A_FILE);
+ rc = RTVfsFileSeek(pParent->hVfsFile, pPush->offData + offNew, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(rc))
+ return pParent->rcFatal = rc;
+ pPush->offCurrent = offNew;
+ }
+ Assert(pPush->offCurrent <= pPush->cbCurrent);
+
+ if (poffActual)
+ *poffActual = pPush->offCurrent;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ *pcbFile = pPush->cbCurrent;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * TAR writer push I/O stream operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_rtZipTarWriterIoStrmOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "TAR push I/O Stream",
+ rtZipTarWriterPush_Close,
+ rtZipTarWriterPush_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipTarWriterPush_Read,
+ rtZipTarWriterPush_Write,
+ rtZipTarWriterPush_Flush,
+ rtZipTarWriterPush_PollOne,
+ rtZipTarWriterPush_Tell,
+ rtZipTarWriterPush_Skip,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+};
+
+
+/**
+ * TAR writer push file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtZipTarWriterFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "TAR push file",
+ rtZipTarWriterPush_Close,
+ rtZipTarWriterPush_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipTarWriterPush_Read,
+ rtZipTarWriterPush_Write,
+ rtZipTarWriterPush_Flush,
+ rtZipTarWriterPush_PollOne,
+ rtZipTarWriterPush_Tell,
+ rtZipTarWriterPush_Skip,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtZipTarWriterPush_SetMode,
+ rtZipTarWriterPush_SetTimes,
+ rtZipTarWriterPush_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtZipTarWriterPush_Seek,
+ rtZipTarWriterPush_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION
+};
+
+
+
+/**
+ * Checks rcFatal and completes any current push file.
+ *
+ * On return the output stream position will be at the next header location.
+ *
+ * After this call, the push object no longer can write anything.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ */
+static int rtZipTarFssWriter_CompleteCurrentPushFile(PRTZIPTARFSSTREAMWRITER pThis)
+{
+ /*
+ * Check if there is a push file pending, remove it if there is.
+ * We also check for fatal errors at this point so the caller doesn't need to.
+ */
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = pThis->pPush;
+ if (!pPush)
+ {
+ AssertRC(pThis->rcFatal);
+ return pThis->rcFatal;
+ }
+
+ pThis->pPush = NULL;
+ pPush->pParent = NULL;
+
+ int rc = pThis->rcFatal;
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Do we need to update the header. pThis->aHdrs[0] will retain the current
+ * content at pPush->offHdr and we only need to update the size.
+ */
+ if (pPush->fOpenEnded)
+ {
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, pPush->cbCurrent);
+ if (RT_SUCCESS(rc))
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWriteAt(pThis->hVfsFile, pPush->offHdr, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileSeek(pThis->hVfsFile, pPush->offData + pPush->cbCurrent, RTFILE_SEEK_BEGIN, NULL);
+ }
+ }
+ /*
+ * Check that we've received all the data we were promissed in the PushFile
+ * call, fail if we weren't.
+ */
+ else
+ AssertMsgStmt(pPush->cbCurrent == pPush->cbExpected,
+ ("cbCurrent=%#RX64 cbExpected=%#RX64\n", pPush->cbCurrent, pPush->cbExpected),
+ rc = VERR_BUFFER_UNDERFLOW);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do zero padding if necessary.
+ */
+ if (pPush->cbCurrent & (RTZIPTAR_BLOCKSIZE - 1))
+ {
+ size_t cbToZero = RTZIPTAR_BLOCKSIZE - (pPush->cbCurrent & (RTZIPTAR_BLOCKSIZE - 1));
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, cbToZero, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->cbWritten += cbToZero;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ pThis->rcFatal = rc;
+ return rc;
+}
+
+
+/**
+ * Does the actual work for rtZipTarFssWriter_SwitchToWriteMode().
+ *
+ * @note We won't be here if we've truncate the tar file. Truncation
+ * switches it into write mode.
+ */
+DECL_NO_INLINE(static, int) rtZipTarFssWriter_SwitchToWriteModeSlow(PRTZIPTARFSSTREAMWRITER pThis)
+{
+ /* Always go thru rtZipTarFssWriter_SwitchToWriteMode(). */
+ AssertRCReturn(pThis->rcFatal, pThis->rcFatal);
+ AssertReturn(!pThis->fWriting, VINF_SUCCESS);
+ AssertReturn(pThis->fFlags & RTZIPTAR_C_UPDATE, VERR_INTERNAL_ERROR_3);
+
+ /*
+ * If we're not at the end, locate the end of the tar file.
+ * Because I'm lazy, we do that using rtZipTarFss_Next. This isn't entirely
+ * optimial as it involves VFS object instantations and such.
+ */
+ /** @todo Optimize skipping to end of tar file in update mode. */
+ while (!pThis->pRead->fEndOfStream)
+ {
+ int rc = rtZipTarFss_Next(pThis->pRead, NULL, NULL, NULL);
+ if (rc == VERR_EOF)
+ break;
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Seek to the desired cut-off point and indicate that we've switched to writing.
+ */
+ Assert(pThis->pRead->offNextHdr == pThis->pRead->offCurHdr);
+ int rc = RTVfsFileSeek(pThis->hVfsFile, pThis->pRead->offNextHdr, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ if (RT_SUCCESS(rc))
+ pThis->fWriting = true;
+ else
+ pThis->rcFatal = rc;
+
+ return rc;
+}
+
+
+/**
+ * Switches the stream into writing mode if necessary.
+ *
+ * @returns VBox status code.
+ * @param pThis The TAR writer instance.
+ *
+ */
+DECLINLINE(int) rtZipTarFssWriter_SwitchToWriteMode(PRTZIPTARFSSTREAMWRITER pThis)
+{
+ if (pThis->fWriting)
+ return VINF_SUCCESS; /* ASSUMES caller already checked pThis->rcFatal. */
+ return rtZipTarFssWriter_SwitchToWriteModeSlow(pThis);
+}
+
+
+/**
+ * Allocates a buffer for transfering file data.
+ *
+ * @note Will use the 3rd TAR header as fallback buffer if we're out of
+ * memory!
+ *
+ * @returns Pointer to buffer (won't ever fail).
+ * @param pThis The TAR writer instance.
+ * @param pcbBuf Where to return the buffer size. This will be a
+ * multiple of the TAR block size.
+ * @param ppvFree Where to return the pointer to pass to RTMemTmpFree
+ * when done with the buffer.
+ * @param cbFile The file size. Used as a buffer size hint.
+ */
+static uint8_t *rtZipTarFssWriter_AllocBuf(PRTZIPTARFSSTREAMWRITER pThis, size_t *pcbBuf, void **ppvFree, uint64_t cbObject)
+{
+ uint8_t *pbBuf;
+
+ /*
+ * If this is a large file, try for a large buffer with 16KB alignment.
+ */
+ if (cbObject >= _64M)
+ {
+ pbBuf = (uint8_t *)RTMemTmpAlloc(_2M + _16K - 1);
+ if (pbBuf)
+ {
+ *pcbBuf = _2M;
+ *ppvFree = pbBuf;
+ return RT_ALIGN_PT(pbBuf, _16K, uint8_t *);
+ }
+ }
+ /*
+ * 4KB aligned 512KB buffer if larger 512KB or larger.
+ */
+ else if (cbObject >= _512K)
+ {
+ pbBuf = (uint8_t *)RTMemTmpAlloc(_512K + _4K - 1);
+ if (pbBuf)
+ {
+ *pcbBuf = _512K;
+ *ppvFree = pbBuf;
+ return RT_ALIGN_PT(pbBuf, _4K, uint8_t *);
+ }
+ }
+ /*
+ * Otherwise a 4KB aligned 128KB buffer.
+ */
+ else
+ {
+ pbBuf = (uint8_t *)RTMemTmpAlloc(_128K + _4K - 1);
+ if (pbBuf)
+ {
+ *pcbBuf = _128K;
+ *ppvFree = pbBuf;
+ return RT_ALIGN_PT(pbBuf, _4K, uint8_t *);
+ }
+ }
+
+ /*
+ * If allocation failed, fallback on a 16KB buffer without any extra alignment.
+ */
+ pbBuf = (uint8_t *)RTMemTmpAlloc(_16K);
+ if (pbBuf)
+ {
+ *pcbBuf = _16K;
+ *ppvFree = pbBuf;
+ return pbBuf;
+ }
+
+ /*
+ * Final fallback, 512KB buffer using the 3rd header.
+ */
+ AssertCompile(RT_ELEMENTS(pThis->aHdrs) >= 3);
+ *pcbBuf = sizeof(pThis->aHdrs[2]);
+ *ppvFree = NULL;
+ return (uint8_t *)&pThis->aHdrs[2];
+}
+
+
+/**
+ * Frees the sparse info for a TAR file.
+ *
+ * @param pSparse The sparse info to free.
+ */
+static void rtZipTarFssWriter_SparseInfoDestroy(PRTZIPTARSPARSE pSparse)
+{
+ PRTZIPTARSPARSECHUNK pCur;
+ PRTZIPTARSPARSECHUNK pNext;
+ RTListForEachSafe(&pSparse->ChunkHead, pCur, pNext, RTZIPTARSPARSECHUNK, Entry)
+ RTMemTmpFree(pCur);
+ RTMemTmpFree(pSparse);
+}
+
+
+/**
+ * Adds a data span to the sparse info.
+ *
+ * @returns VINF_SUCCESS or VINF_NO_TMP_MEMORY.
+ * @param pSparse The sparse info to free.
+ * @param offSpan Offset of the span.
+ * @param cbSpan Number of bytes.
+ */
+static int rtZipTarFssWriter_SparseInfoAddSpan(PRTZIPTARSPARSE pSparse, uint64_t offSpan, uint64_t cbSpan)
+{
+ /*
+ * Get the chunk we're adding it to.
+ */
+ PRTZIPTARSPARSECHUNK pChunk;
+ if (pSparse->iNextSpan != 0)
+ {
+ pChunk = RTListGetLast(&pSparse->ChunkHead, RTZIPTARSPARSECHUNK, Entry);
+ Assert(pSparse->iNextSpan < RT_ELEMENTS(pChunk->aSpans));
+ }
+ else
+ {
+ pChunk = (PRTZIPTARSPARSECHUNK)RTMemTmpAllocZ(sizeof(*pChunk));
+ if (!pChunk)
+ return VERR_NO_TMP_MEMORY;
+ RTListAppend(&pSparse->ChunkHead, &pChunk->Entry);
+ }
+
+ /*
+ * Append it.
+ */
+ pSparse->cDataSpans += 1;
+ pSparse->cbDataSpans += cbSpan;
+ pChunk->aSpans[pSparse->iNextSpan].cb = cbSpan;
+ pChunk->aSpans[pSparse->iNextSpan].off = offSpan;
+ if (++pSparse->iNextSpan >= RT_ELEMENTS(pChunk->aSpans))
+ pSparse->iNextSpan = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Scans the input stream recording non-zero blocks.
+ */
+static int rtZipTarFssWriter_ScanSparseFile(PRTZIPTARFSSTREAMWRITER pThis, RTVFSFILE hVfsFile, uint64_t cbFile,
+ size_t cbBuf, uint8_t *pbBuf, PRTZIPTARSPARSE *ppSparse)
+{
+ RT_NOREF(pThis);
+
+ /*
+ * Create an empty sparse info bundle.
+ */
+ PRTZIPTARSPARSE pSparse = (PRTZIPTARSPARSE)RTMemTmpAlloc(sizeof(*pSparse));
+ AssertReturn(pSparse, VERR_NO_MEMORY);
+ pSparse->cbDataSpans = 0;
+ pSparse->cDataSpans = 0;
+ pSparse->iNextSpan = 0;
+ RTListInit(&pSparse->ChunkHead);
+
+ /*
+ * Scan the file from the start.
+ */
+ int rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ bool fZeroSpan = false;
+ uint64_t offSpan = 0;
+ uint64_t cbSpan = 0;
+
+ for (uint64_t off = 0; off < cbFile;)
+ {
+ uint64_t cbLeft = cbFile - off;
+ size_t cbToRead = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft;
+ rc = RTVfsFileRead(hVfsFile, pbBuf, cbToRead, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ size_t cBlocks = cbToRead / RTZIPTAR_BLOCKSIZE;
+
+ /* Zero pad the final buffer to a multiple of the blocksize. */
+ if (!(cbToRead & (RTZIPTAR_BLOCKSIZE - 1)))
+ { /* likely */ }
+ else
+ {
+ AssertBreakStmt(cbLeft == cbToRead, rc = VERR_INTERNAL_ERROR_3);
+ RT_BZERO(&pbBuf[cbToRead], RTZIPTAR_BLOCKSIZE - (cbToRead & (RTZIPTAR_BLOCKSIZE - 1)));
+ cBlocks++;
+ }
+
+ /*
+ * Process the blocks we've just read one by one.
+ */
+ uint8_t const *pbBlock = pbBuf;
+ for (size_t iBlock = 0; iBlock < cBlocks; iBlock++)
+ {
+ bool fZeroBlock = ASMMemIsZero(pbBlock, RTZIPTAR_BLOCKSIZE);
+ if (fZeroBlock == fZeroSpan)
+ cbSpan += RTZIPTAR_BLOCKSIZE;
+ else
+ {
+ if (!fZeroSpan && cbSpan)
+ {
+ rc = rtZipTarFssWriter_SparseInfoAddSpan(pSparse, offSpan, cbSpan);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ fZeroSpan = fZeroBlock;
+ offSpan = off;
+ cbSpan = RTZIPTAR_BLOCKSIZE;
+ }
+
+ /* next block. */
+ pbBlock += RTZIPTAR_BLOCKSIZE;
+ off += RTZIPTAR_BLOCKSIZE;
+ }
+ }
+
+ /*
+ * Deal with the final span. If we've got zeros thowards the end, we
+ * must add a zero byte data span at the end.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (!fZeroSpan && cbSpan)
+ {
+ if (cbFile & (RTZIPTAR_BLOCKSIZE - 1))
+ {
+ Assert(!(cbSpan & (RTZIPTAR_BLOCKSIZE - 1)));
+ cbSpan -= RTZIPTAR_BLOCKSIZE;
+ cbSpan |= cbFile & (RTZIPTAR_BLOCKSIZE - 1);
+ }
+ rc = rtZipTarFssWriter_SparseInfoAddSpan(pSparse, offSpan, cbSpan);
+ }
+ if (RT_SUCCESS(rc))
+ rc = rtZipTarFssWriter_SparseInfoAddSpan(pSparse, cbFile, 0);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Return the file back to the start position before we return so that we
+ * can segue into the regular rtZipTarFssWriter_AddFile without further ado.
+ */
+ rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ *ppSparse = pSparse;
+ return VINF_SUCCESS;
+ }
+ }
+
+ rtZipTarFssWriter_SparseInfoDestroy(pSparse);
+ *ppSparse = NULL;
+ return rc;
+}
+
+
+/**
+ * Writes GNU the sparse file headers.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ * @param pSparse The sparse file info.
+ */
+static int rtZipTarFssWriter_WriteGnuSparseHeaders(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, PCRTFSOBJINFO pObjInfo,
+ const char *pszOwnerNm, const char *pszGroupNm, PCRTZIPTARSPARSE pSparse)
+{
+ /*
+ * Format the first header.
+ */
+ int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, RTZIPTAR_TF_GNU_SPARSE);
+ AssertRCReturn(rc, rc);
+ AssertReturn(pThis->cHdrs == 1, VERR_INTERNAL_ERROR_2);
+
+ /* data size. */
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, pSparse->cbDataSpans);
+ AssertRCReturn(rc, rc);
+
+ /* realsize. */
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Gnu.realsize, pObjInfo->cbObject);
+ AssertRCReturn(rc, rc);
+
+ Assert(pThis->aHdrs[0].Gnu.isextended == 0);
+
+ /*
+ * Walk the sparse spans, fill and write headers one by one.
+ */
+ PRTZIPTARGNUSPARSE paSparse = &pThis->aHdrs[0].Gnu.sparse[0];
+ uint32_t cSparse = RT_ELEMENTS(pThis->aHdrs[0].Gnu.sparse);
+ uint32_t iSparse = 0;
+
+ PRTZIPTARSPARSECHUNK const pLastChunk = RTListGetLast(&pSparse->ChunkHead, RTZIPTARSPARSECHUNK, Entry);
+ PRTZIPTARSPARSECHUNK pChunk;
+ RTListForEach(&pSparse->ChunkHead, pChunk, RTZIPTARSPARSECHUNK, Entry)
+ {
+ uint32_t cSpans = pChunk != pLastChunk || pSparse->iNextSpan == 0
+ ? RT_ELEMENTS(pChunk->aSpans) : pSparse->iNextSpan;
+ for (uint32_t iSpan = 0; iSpan < cSpans; iSpan++)
+ {
+ /* Flush the header? */
+ if (iSparse >= cSparse)
+ {
+ if (cSparse != RT_ELEMENTS(pThis->aHdrs[0].Gnu.sparse))
+ pThis->aHdrs[0].GnuSparse.isextended = 1; /* more headers to come */
+ else
+ {
+ pThis->aHdrs[0].Gnu.isextended = 1; /* more headers to come */
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ RT_ZERO(pThis->aHdrs[0]);
+ cSparse = RT_ELEMENTS(pThis->aHdrs[0].GnuSparse.sp);
+ iSparse = 0;
+ paSparse = &pThis->aHdrs[0].GnuSparse.sp[0];
+ }
+
+ /* Append sparse data segment. */
+ rc = rtZipTarFssWriter_FormatOffset(paSparse[iSparse].offset, pChunk->aSpans[iSpan].off);
+ AssertRCReturn(rc, rc);
+ rc = rtZipTarFssWriter_FormatOffset(paSparse[iSparse].numbytes, pChunk->aSpans[iSpan].cb);
+ AssertRCReturn(rc, rc);
+ iSparse++;
+ }
+ }
+
+ /*
+ * The final header.
+ */
+ if (iSparse != 0)
+ {
+ if (cSparse != RT_ELEMENTS(pThis->aHdrs[0].Gnu.sparse))
+ Assert(pThis->aHdrs[0].GnuSparse.isextended == 0);
+ else
+ {
+ Assert(pThis->aHdrs[0].Gnu.isextended == 0);
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ }
+ pThis->cHdrs = 0;
+ return rc;
+}
+
+
+/**
+ * Adds a potentially sparse file to the output.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param hVfsFile The potentially sparse file.
+ * @param hVfsIos The I/O stream of the file. Same as @a hVfsFile.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddFileSparse(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSFILE hVfsFile,
+ RTVFSIOSTREAM hVfsIos, PCRTFSOBJINFO pObjInfo,
+ const char *pszOwnerNm, const char *pszGroupNm)
+{
+ /*
+ * Scan the input file to locate all zero blocks.
+ */
+ void *pvBufFree;
+ size_t cbBuf;
+ uint8_t *pbBuf = rtZipTarFssWriter_AllocBuf(pThis, &cbBuf, &pvBufFree, pObjInfo->cbObject);
+
+ PRTZIPTARSPARSE pSparse;
+ int rc = rtZipTarFssWriter_ScanSparseFile(pThis, hVfsFile, pObjInfo->cbObject, cbBuf, pbBuf, &pSparse);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * If there aren't at least 2 zero blocks in the file, don't bother
+ * doing the sparse stuff and store it as a normal file.
+ */
+ if (pSparse->cbDataSpans + RTZIPTAR_BLOCKSIZE > (uint64_t)pObjInfo->cbObject)
+ {
+ rtZipTarFssWriter_SparseInfoDestroy(pSparse);
+ RTMemTmpFree(pvBufFree);
+ return rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, pObjInfo, pszOwnerNm, pszGroupNm);
+ }
+
+ /*
+ * Produce and write the headers.
+ */
+ if (pThis->enmFormat == RTZIPTARFORMAT_GNU)
+ rc = rtZipTarFssWriter_WriteGnuSparseHeaders(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, pSparse);
+ else
+ AssertStmt(pThis->enmFormat != RTZIPTARFORMAT_GNU, rc = VERR_NOT_IMPLEMENTED);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Write the file bytes.
+ */
+ PRTZIPTARSPARSECHUNK const pLastChunk = RTListGetLast(&pSparse->ChunkHead, RTZIPTARSPARSECHUNK, Entry);
+ PRTZIPTARSPARSECHUNK pChunk;
+ RTListForEach(&pSparse->ChunkHead, pChunk, RTZIPTARSPARSECHUNK, Entry)
+ {
+ uint32_t cSpans = pChunk != pLastChunk || pSparse->iNextSpan == 0
+ ? RT_ELEMENTS(pChunk->aSpans) : pSparse->iNextSpan;
+ for (uint32_t iSpan = 0; iSpan < cSpans; iSpan++)
+ {
+ rc = RTVfsFileSeek(hVfsFile, pChunk->aSpans[iSpan].off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ uint64_t cbLeft = pChunk->aSpans[iSpan].cb;
+ Assert( !(cbLeft & (RTZIPTAR_BLOCKSIZE - 1))
+ || (iSpan + 1 == cSpans && pChunk == pLastChunk));
+ while (cbLeft > 0)
+ {
+ size_t cbToRead = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft;
+ rc = RTVfsFileRead(hVfsFile, pbBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pbBuf, cbToRead, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += cbToRead;
+ cbLeft -= cbToRead;
+ continue;
+ }
+ }
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ /*
+ * Do the zero padding.
+ */
+ if ( RT_SUCCESS(rc)
+ && (pSparse->cbDataSpans & (RTZIPTAR_BLOCKSIZE - 1)))
+ {
+ size_t cbToZero = RTZIPTAR_BLOCKSIZE - (pSparse->cbDataSpans & (RTZIPTAR_BLOCKSIZE - 1));
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, cbToZero, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->cbWritten += cbToZero;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ pThis->rcFatal = rc;
+ rtZipTarFssWriter_SparseInfoDestroy(pSparse);
+ }
+ RTMemTmpFree(pvBufFree);
+ return rc;
+}
+
+
+/**
+ * Adds an I/O stream of indeterminate length to the TAR file.
+ *
+ * This requires the output to be seekable, i.e. a file, because we need to go
+ * back and update @c size field of the TAR header after pumping all the data
+ * bytes thru and establishing the file length.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param hVfsIos The I/O stream of the file.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddFileStream(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos,
+ PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm)
+{
+ AssertReturn(pThis->hVfsFile != NIL_RTVFSFILE, VERR_NOT_A_FILE);
+
+ /*
+ * Append the header.
+ */
+ int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, UINT8_MAX);
+ if (RT_SUCCESS(rc))
+ {
+ RTFOFF const offHdr = RTVfsFileTell(pThis->hVfsFile);
+ if (offHdr >= 0)
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+
+ /*
+ * Transfer the bytes.
+ */
+ void *pvBufFree;
+ size_t cbBuf;
+ uint8_t *pbBuf = rtZipTarFssWriter_AllocBuf(pThis, &cbBuf, &pvBufFree,
+ pObjInfo->cbObject > 0 && pObjInfo->cbObject != RTFOFF_MAX
+ ? pObjInfo->cbObject : _1G);
+
+ uint64_t cbReadTotal = 0;
+ for (;;)
+ {
+ size_t cbRead = 0;
+ int rc2 = rc = RTVfsIoStrmRead(hVfsIos, pbBuf, cbBuf, true /*fBlocking*/, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ cbReadTotal += cbRead;
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pbBuf, cbRead, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += cbRead;
+ if (rc2 != VINF_EOF)
+ continue;
+ }
+ }
+ Assert(rc != VERR_EOF /* expecting VINF_EOF! */);
+ break;
+ }
+
+ RTMemTmpFree(pvBufFree);
+
+ /*
+ * Do the zero padding.
+ */
+ if ((cbReadTotal & (RTZIPTAR_BLOCKSIZE - 1)) && RT_SUCCESS(rc))
+ {
+ size_t cbToZero = RTZIPTAR_BLOCKSIZE - (cbReadTotal & (RTZIPTAR_BLOCKSIZE - 1));
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, cbToZero, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->cbWritten += cbToZero;
+ }
+
+ /*
+ * Update the header. We ASSUME that aHdr[0] is unmodified
+ * from before the data pumping above and just update the size.
+ */
+ if ((RTFOFF)cbReadTotal != pObjInfo->cbObject && RT_SUCCESS(rc))
+ {
+ RTFOFF const offRestore = RTVfsFileTell(pThis->hVfsFile);
+ if (offRestore >= 0)
+ {
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, cbReadTotal);
+ if (RT_SUCCESS(rc))
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWriteAt(pThis->hVfsFile, offHdr, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileSeek(pThis->hVfsFile, offRestore, RTFILE_SEEK_BEGIN, NULL);
+ }
+ }
+ else
+ rc = (int)offRestore;
+ }
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = (int)offHdr;
+ pThis->rcFatal = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a file to the stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param hVfsIos The I/O stream of the file.
+ * @param fFlags The RTVFSFSSTREAMOPS::pfnAdd flags.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddFile(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos,
+ PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm)
+{
+ /*
+ * Append the header.
+ */
+ int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, UINT8_MAX);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+
+ /*
+ * Copy the bytes. Padding the last buffer to a multiple of 512.
+ */
+ void *pvBufFree;
+ size_t cbBuf;
+ uint8_t *pbBuf = rtZipTarFssWriter_AllocBuf(pThis, &cbBuf, &pvBufFree, pObjInfo->cbObject);
+
+ uint64_t cbLeft = pObjInfo->cbObject;
+ while (cbLeft > 0)
+ {
+ size_t cbRead = cbLeft > cbBuf ? cbBuf : (size_t)cbLeft;
+ rc = RTVfsIoStrmRead(hVfsIos, pbBuf, cbRead, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ size_t cbToWrite = cbRead;
+ if (cbRead & (RTZIPTAR_BLOCKSIZE - 1))
+ {
+ size_t cbToZero = RTZIPTAR_BLOCKSIZE - (cbRead & (RTZIPTAR_BLOCKSIZE - 1));
+ memset(&pbBuf[cbRead], 0, cbToZero);
+ cbToWrite += cbToZero;
+ }
+
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pbBuf, cbToWrite, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ pThis->cbWritten += cbToWrite;
+ cbLeft -= cbRead;
+ }
+
+ RTMemTmpFree(pvBufFree);
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ pThis->rcFatal = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a symbolic link to the stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the object.
+ * @param hVfsSymlink The symbolic link object to add.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddSymlink(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSSYMLINK hVfsSymlink,
+ PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm)
+{
+ /*
+ * Read the symlink target first and check that it's not too long.
+ * Flip DOS slashes.
+ */
+ char szTarget[RTPATH_MAX];
+ int rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
+ if (RT_SUCCESS(rc))
+ {
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_UNIX
+ char *pszDosSlash = strchr(szTarget, '\\');
+ while (pszDosSlash)
+ {
+ *pszDosSlash = '/';
+ pszDosSlash = strchr(pszDosSlash + 1, '\\');
+ }
+#endif
+ size_t cchTarget = strlen(szTarget);
+ if (cchTarget < sizeof(pThis->aHdrs[0].Common.linkname))
+ {
+ /*
+ * Create a header, add the link target and push it out.
+ */
+ rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, UINT8_MAX);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(pThis->aHdrs[0].Common.linkname, szTarget, cchTarget + 1);
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]),
+ true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+ return VINF_SUCCESS;
+ }
+ pThis->rcFatal = rc;
+ }
+ }
+ }
+ else
+ {
+ /** @todo implement gnu and pax long name extensions. */
+ rc = VERR_TAR_NAME_TOO_LONG;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a simple object to the stream.
+ *
+ * Simple objects only contains metadata, no actual data bits. Directories,
+ * devices, fifos, sockets and such.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the object.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddSimpleObject(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, PCRTFSOBJINFO pObjInfo,
+ const char *pszOwnerNm, const char *pszGroupNm)
+{
+ int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, UINT8_MAX);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+ return VINF_SUCCESS;
+ }
+ pThis->rcFatal = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_Close(void *pvThis)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ rtZipTarFssWriter_CompleteCurrentPushFile(pThis);
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ if (pThis->hVfsFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pThis->hVfsFile);
+ pThis->hVfsFile = NIL_RTVFSFILE;
+ }
+
+ if (pThis->pszOwner)
+ {
+ RTStrFree(pThis->pszOwner);
+ pThis->pszOwner = NULL;
+ }
+ if (pThis->pszGroup)
+ {
+ RTStrFree(pThis->pszGroup);
+ pThis->pszGroup = NULL;
+ }
+ if (pThis->pszPrefix)
+ {
+ RTStrFree(pThis->pszPrefix);
+ pThis->pszPrefix = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)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) rtZipTarFssWriter_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ /*
+ * This only works in update mode and up to the point where
+ * modifications takes place (truncating the archive or appending files).
+ */
+ AssertReturn(pThis->pRead, VERR_ACCESS_DENIED);
+ AssertReturn(pThis->fFlags & RTZIPTAR_C_UPDATE, VERR_ACCESS_DENIED);
+
+ AssertReturn(!pThis->fWriting, VERR_WRONG_ORDER);
+
+ return rtZipTarFss_Next(pThis->pRead, ppszName, penmType, phVfsObj);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnAdd}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_Add(void *pvThis, const char *pszPath, RTVFSOBJ hVfsObj, uint32_t fFlags)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ /*
+ * Before we continue we must complete any current push file and check rcFatal.
+ */
+ int rc = rtZipTarFssWriter_CompleteCurrentPushFile(pThis);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Query information about the object.
+ */
+ RTFSOBJINFO ObjInfo;
+ rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ AssertRCReturn(rc, rc);
+
+ RTFSOBJINFO ObjOwnerName;
+ rc = RTVfsObjQueryInfo(hVfsObj, &ObjOwnerName, RTFSOBJATTRADD_UNIX_OWNER);
+ if (RT_FAILURE(rc) || ObjOwnerName.Attr.u.UnixOwner.szName[0] == '\0')
+ strcpy(ObjOwnerName.Attr.u.UnixOwner.szName, "someone");
+
+ RTFSOBJINFO ObjGrpName;
+ rc = RTVfsObjQueryInfo(hVfsObj, &ObjGrpName, RTFSOBJATTRADD_UNIX_GROUP);
+ if (RT_FAILURE(rc) || ObjGrpName.Attr.u.UnixGroup.szName[0] == '\0')
+ strcpy(ObjGrpName.Attr.u.UnixGroup.szName, "somegroup");
+
+ /*
+ * Switch the stream into write mode if necessary.
+ */
+ rc = rtZipTarFssWriter_SwitchToWriteMode(pThis);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Do type specific handling. File have several options and variations to
+ * take into account, thus the mess.
+ */
+ if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_WRONG_TYPE);
+
+ if (fFlags & RTVFSFSSTRM_ADD_F_STREAM)
+ rc = rtZipTarFssWriter_AddFileStream(pThis, pszPath, hVfsIos, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ else if ( !(pThis->fFlags & RTZIPTAR_C_SPARSE)
+ || ObjInfo.cbObject < RTZIPTAR_MIN_SPARSE)
+ rc = rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ else
+ {
+ RTVFSFILE hVfsFile = RTVfsObjToFile(hVfsObj);
+ if (hVfsFile != NIL_RTVFSFILE)
+ {
+ rc = rtZipTarFssWriter_AddFileSparse(pThis, pszPath, hVfsFile, hVfsIos, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ rc = rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ }
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ {
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, VERR_WRONG_TYPE);
+ rc = rtZipTarFssWriter_AddSymlink(pThis, pszPath, hVfsSymlink, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ RTVfsSymlinkRelease(hVfsSymlink);
+ }
+ else
+ rc = rtZipTarFssWriter_AddSimpleObject(pThis, pszPath, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnPushFile}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_PushFile(void *pvThis, const char *pszPath, uint64_t cbFile, PCRTFSOBJINFO paObjInfo,
+ uint32_t cObjInfo, uint32_t fFlags, PRTVFSIOSTREAM phVfsIos)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ /*
+ * We can only deal with output of indeterminate length if the output is
+ * seekable (see also rtZipTarFssWriter_AddFileStream).
+ */
+ AssertReturn(cbFile != UINT64_MAX || pThis->hVfsFile != NIL_RTVFSFILE, VERR_NOT_A_FILE);
+ AssertReturn(RT_BOOL(cbFile == UINT64_MAX) == RT_BOOL(fFlags & RTVFSFSSTRM_ADD_F_STREAM), VERR_INVALID_FLAGS);
+
+ /*
+ * Before we continue we must complete any current push file and check rcFatal.
+ */
+ int rc = rtZipTarFssWriter_CompleteCurrentPushFile(pThis);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * If no object info was provideded, fake up some.
+ */
+ const char *pszOwnerNm = "someone";
+ const char *pszGroupNm = "somegroup";
+ RTFSOBJINFO ObjInfo;
+ if (cObjInfo == 0)
+ {
+ /* Fake up a info. */
+ RT_ZERO(ObjInfo);
+ ObjInfo.cbObject = cbFile != UINT64_MAX ? cbFile : 0;
+ ObjInfo.cbAllocated = cbFile != UINT64_MAX ? RT_ALIGN_64(cbFile, RTZIPTAR_BLOCKSIZE) : UINT64_MAX;
+ RTTimeNow(&ObjInfo.ModificationTime);
+ ObjInfo.BirthTime = ObjInfo.ModificationTime;
+ ObjInfo.ChangeTime = ObjInfo.ModificationTime;
+ ObjInfo.AccessTime = ObjInfo.ModificationTime;
+ ObjInfo.Attr.fMode = RTFS_TYPE_FILE | 0666;
+ ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ ObjInfo.Attr.u.Unix.uid = NIL_RTUID;
+ ObjInfo.Attr.u.Unix.gid = NIL_RTGID;
+ ObjInfo.Attr.u.Unix.cHardlinks = 1;
+ //ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ //ObjInfo.Attr.u.Unix.INodeId = 0;
+ //ObjInfo.Attr.u.Unix.fFlags = 0;
+ //ObjInfo.Attr.u.Unix.GenerationId = 0;
+ //ObjInfo.Attr.u.Unix.Device = 0;
+ }
+ else
+ {
+ /* Make a copy of the object info and adjust the size, if necessary. */
+ ObjInfo = paObjInfo[0];
+ Assert(ObjInfo.Attr.enmAdditional == RTFSOBJATTRADD_UNIX);
+ Assert(RTFS_IS_FILE(ObjInfo.Attr.fMode));
+ if ((uint64_t)ObjInfo.cbObject != cbFile)
+ {
+ ObjInfo.cbObject = cbFile != UINT64_MAX ? cbFile : 0;
+ ObjInfo.cbAllocated = cbFile != UINT64_MAX ? RT_ALIGN_64(cbFile, RTZIPTAR_BLOCKSIZE) : UINT64_MAX;
+ }
+
+ /* Lookup the group and user names. */
+ for (uint32_t i = 0; i < cObjInfo; i++)
+ if ( paObjInfo[i].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_OWNER
+ && paObjInfo[i].Attr.u.UnixOwner.szName[0] != '\0')
+ pszOwnerNm = paObjInfo[i].Attr.u.UnixOwner.szName;
+ else if ( paObjInfo[i].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_GROUP
+ && paObjInfo[i].Attr.u.UnixGroup.szName[0] != '\0')
+ pszGroupNm = paObjInfo[i].Attr.u.UnixGroup.szName;
+ }
+
+ /*
+ * Switch the stream into write mode if necessary.
+ */
+ rc = rtZipTarFssWriter_SwitchToWriteMode(pThis);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create an I/O stream object for the caller to use.
+ */
+ RTFOFF const offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ AssertReturn(offHdr >= 0, (int)offHdr);
+
+ PRTZIPTARFSSTREAMWRITERPUSH pPush;
+ RTVFSIOSTREAM hVfsIos;
+ if (pThis->hVfsFile == NIL_RTVFSFILE)
+ {
+ rc = RTVfsNewIoStream(&g_rtZipTarWriterIoStrmOps, sizeof(*pPush), RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsIos, (void **)&pPush);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTVfsNewFile(&g_rtZipTarWriterFileOps, sizeof(*pPush), RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsFile, (void **)&pPush);
+ if (RT_FAILURE(rc))
+ return rc;
+ hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+ pPush->pParent = NULL;
+ pPush->cbExpected = cbFile;
+ pPush->offHdr = (uint64_t)offHdr;
+ pPush->offData = 0;
+ pPush->offCurrent = 0;
+ pPush->cbCurrent = 0;
+ pPush->ObjInfo = ObjInfo;
+ pPush->fOpenEnded = cbFile == UINT64_MAX;
+
+ /*
+ * Produce and write file headers.
+ */
+ rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, &ObjInfo, pszOwnerNm, pszGroupNm, RTZIPTAR_TF_NORMAL);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbHdrs = pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, cbHdrs, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += cbHdrs;
+
+ /*
+ * Complete the object and return.
+ */
+ pPush->offData = pPush->offHdr + cbHdrs;
+ if (cbFile == UINT64_MAX)
+ pPush->cbExpected = (uint64_t)(RTFOFF_MAX - _4K) - pPush->offData;
+ pPush->pParent = pThis;
+ pThis->pPush = pPush;
+
+ *phVfsIos = hVfsIos;
+ return VINF_SUCCESS;
+ }
+ pThis->rcFatal = rc;
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnEnd}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_End(void *pvThis)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ /*
+ * Make sure to complete any pending push file and that rcFatal is fine.
+ */
+ int rc = rtZipTarFssWriter_CompleteCurrentPushFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * There are supposed to be two zero headers at the end of the archive.
+ * GNU tar may write more because of the way it does buffering,
+ * libarchive OTOH writes exactly two.
+ */
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, RTZIPTAR_BLOCKSIZE * 2, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += RTZIPTAR_BLOCKSIZE * 2;
+
+ /*
+ * Flush the output.
+ */
+ rc = RTVfsIoStrmFlush(pThis->hVfsIos);
+
+ /*
+ * If we're in update mode, set the end-of-file here to make sure
+ * unwanted bytes are really discarded.
+ */
+ if (RT_SUCCESS(rc) && (pThis->fFlags & RTZIPTAR_C_UPDATE))
+ {
+ RTFOFF cbTarFile = RTVfsFileTell(pThis->hVfsFile);
+ if (cbTarFile >= 0)
+ rc = RTVfsFileSetSize(pThis->hVfsFile, (uint64_t)cbTarFile, RTVFSFILE_SIZE_F_NORMAL);
+ else
+ rc = (int)cbTarFile;
+ }
+
+ /*
+ * Success?
+ */
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ pThis->rcFatal = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Tar filesystem stream operations.
+ */
+static const RTVFSFSSTREAMOPS g_rtZipTarFssOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "TarFsStreamWriter",
+ rtZipTarFssWriter_Close,
+ rtZipTarFssWriter_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipTarFssWriter_Next,
+ rtZipTarFssWriter_Add,
+ rtZipTarFssWriter_PushFile,
+ rtZipTarFssWriter_End,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+RTDECL(int) RTZipTarFsStreamToIoStream(RTVFSIOSTREAM hVfsIosOut, RTZIPTARFORMAT enmFormat,
+ uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertPtrReturn(hVfsIosOut, VERR_INVALID_HANDLE);
+ AssertReturn(enmFormat > RTZIPTARFORMAT_INVALID && enmFormat < RTZIPTARFORMAT_END, VERR_INVALID_PARAMETER);
+ AssertReturn(!(fFlags & ~RTZIPTAR_C_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!(fFlags & RTZIPTAR_C_UPDATE), VERR_NOT_SUPPORTED); /* Must use RTZipTarFsStreamForFile! */
+
+ if (enmFormat == RTZIPTARFORMAT_DEFAULT)
+ enmFormat = RTZIPTARFORMAT_GNU;
+ AssertReturn( enmFormat == RTZIPTARFORMAT_GNU
+ || enmFormat == RTZIPTARFORMAT_USTAR
+ , VERR_NOT_IMPLEMENTED); /* Only implementing GNU and USTAR output at the moment. */
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosOut);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPTARFSSTREAMWRITER pThis;
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&g_rtZipTarFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_WRITE,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIosOut;
+ pThis->hVfsFile = RTVfsIoStrmToFile(hVfsIosOut);
+
+ pThis->enmFormat = enmFormat;
+ pThis->fFlags = fFlags;
+ pThis->rcFatal = VINF_SUCCESS;
+
+ pThis->uidOwner = NIL_RTUID;
+ pThis->pszOwner = NULL;
+ pThis->gidGroup = NIL_RTGID;
+ pThis->pszGroup = NULL;
+ pThis->pszPrefix = NULL;
+ pThis->pModTime = NULL;
+ pThis->fFileModeAndMask = ~(RTFMODE)0;
+ pThis->fFileModeOrMask = 0;
+ pThis->fDirModeAndMask = ~(RTFMODE)0;
+ pThis->fDirModeOrMask = 0;
+ pThis->fWriting = true;
+
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosOut);
+ return rc;
+}
+
+
+RTDECL(int) RTZipTarFsStreamForFile(RTVFSFILE hVfsFile, RTZIPTARFORMAT enmFormat, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertReturn(hVfsFile != NIL_RTVFSFILE, VERR_INVALID_HANDLE);
+ AssertReturn(enmFormat > RTZIPTARFORMAT_INVALID && enmFormat < RTZIPTARFORMAT_END, VERR_INVALID_PARAMETER);
+ AssertReturn(!(fFlags & ~RTZIPTAR_C_VALID_MASK), VERR_INVALID_FLAGS);
+
+ if (enmFormat == RTZIPTARFORMAT_DEFAULT)
+ enmFormat = RTZIPTARFORMAT_GNU;
+ AssertReturn( enmFormat == RTZIPTARFORMAT_GNU
+ || enmFormat == RTZIPTARFORMAT_USTAR
+ , VERR_NOT_IMPLEMENTED); /* Only implementing GNU and USTAR output at the moment. */
+
+ RTFOFF const offStart = RTVfsFileTell(hVfsFile);
+ AssertReturn(offStart >= 0, (int)offStart);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ AssertReturnStmt(hVfsIos != NIL_RTVFSIOSTREAM, RTVfsFileRelease(hVfsFile), VERR_INVALID_HANDLE);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPTARFSSTREAMWRITER pThis;
+ size_t const cbThis = sizeof(*pThis) + (fFlags & RTZIPTAR_C_UPDATE ? sizeof(*pThis->pRead) : 0);
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&g_rtZipTarFssOps, cbThis, NIL_RTVFS, NIL_RTVFSLOCK,
+ fFlags & RTZIPTAR_C_UPDATE ? RTFILE_O_READWRITE : RTFILE_O_WRITE,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIos;
+ pThis->hVfsFile = hVfsFile;
+
+ pThis->enmFormat = enmFormat;
+ pThis->fFlags = fFlags;
+ pThis->rcFatal = VINF_SUCCESS;
+
+ pThis->uidOwner = NIL_RTUID;
+ pThis->pszOwner = NULL;
+ pThis->gidGroup = NIL_RTGID;
+ pThis->pszGroup = NULL;
+ pThis->pszPrefix = NULL;
+ pThis->pModTime = NULL;
+ pThis->fFileModeAndMask = ~(RTFMODE)0;
+ pThis->fFileModeOrMask = 0;
+ pThis->fDirModeAndMask = ~(RTFMODE)0;
+ pThis->fDirModeOrMask = 0;
+ if (!(fFlags & RTZIPTAR_C_UPDATE))
+ pThis->fWriting = true;
+ else
+ {
+ pThis->fWriting = false;
+ pThis->pRead = (PRTZIPTARFSSTREAM)(pThis + 1);
+ rtZipTarReaderInit(pThis->pRead, hVfsIos, (uint64_t)offStart);
+ }
+
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ RTVfsFileRelease(hVfsFile);
+ return rc;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetOwner(RTVFSFSSTREAM hVfsFss, RTUID uid, const char *pszOwner)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ pThis->uidOwner = uid;
+ if (pThis->pszOwner)
+ {
+ RTStrFree(pThis->pszOwner);
+ pThis->pszOwner = NULL;
+ }
+ if (pszOwner)
+ {
+ pThis->pszOwner = RTStrDup(pszOwner);
+ AssertReturn(pThis->pszOwner, VERR_NO_STR_MEMORY);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetGroup(RTVFSFSSTREAM hVfsFss, RTGID gid, const char *pszGroup)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ pThis->gidGroup = gid;
+ if (pThis->pszGroup)
+ {
+ RTStrFree(pThis->pszGroup);
+ pThis->pszGroup = NULL;
+ }
+ if (pszGroup)
+ {
+ pThis->pszGroup = RTStrDup(pszGroup);
+ AssertReturn(pThis->pszGroup, VERR_NO_STR_MEMORY);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetPrefix(RTVFSFSSTREAM hVfsFss, const char *pszPrefix)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+ AssertReturn(!pszPrefix || *pszPrefix, VERR_INVALID_NAME);
+
+ if (pThis->pszPrefix)
+ {
+ RTStrFree(pThis->pszPrefix);
+ pThis->pszPrefix = NULL;
+ pThis->cchPrefix = 0;
+ }
+ if (pszPrefix)
+ {
+ /*
+ * Make a copy of the prefix, make sure it ends with a slash,
+ * then flip DOS slashes.
+ */
+ size_t cchPrefix = strlen(pszPrefix);
+ char *pszCopy = RTStrAlloc(cchPrefix + 3);
+ AssertReturn(pszCopy, VERR_NO_STR_MEMORY);
+ memcpy(pszCopy, pszPrefix, cchPrefix + 1);
+
+ RTPathEnsureTrailingSeparator(pszCopy, cchPrefix + 3);
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_UNIX
+ char *pszDosSlash = strchr(pszCopy, '\\');
+ while (pszDosSlash)
+ {
+ *pszDosSlash = '/';
+ pszDosSlash = strchr(pszDosSlash + 1, '\\');
+ }
+#endif
+
+ pThis->cchPrefix = cchPrefix + strlen(&pszCopy[cchPrefix]);
+ pThis->pszPrefix = pszCopy;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetModTime(RTVFSFSSTREAM hVfsFss, PCRTTIMESPEC pModificationTime)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ if (pModificationTime)
+ {
+ pThis->ModTime = *pModificationTime;
+ pThis->pModTime = &pThis->ModTime;
+ }
+ else
+ pThis->pModTime = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetFileMode(RTVFSFSSTREAM hVfsFss, RTFMODE fAndMode, RTFMODE fOrMode)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ pThis->fFileModeAndMask = fAndMode | ~RTFS_UNIX_ALL_PERMS;
+ pThis->fFileModeOrMask = fOrMode & RTFS_UNIX_ALL_PERMS;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetDirMode(RTVFSFSSTREAM hVfsFss, RTFMODE fAndMode, RTFMODE fOrMode)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ pThis->fDirModeAndMask = fAndMode | ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDirModeOrMask = fOrMode & RTFS_UNIX_ALL_PERMS;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamTruncate(RTVFSFSSTREAM hVfsFss, RTVFSOBJ hVfsObj, bool fAfter)
+{
+ /*
+ * Translate and validate the input.
+ */
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ AssertReturn(hVfsObj != NIL_RTVFSOBJ, VERR_INVALID_HANDLE);
+ PRTZIPTARBASEOBJ pThisObj = rtZipTarFsStreamBaseObjToPrivate(pThis->pRead, hVfsObj);
+ AssertReturn(pThis, VERR_NOT_OWNER);
+
+ AssertReturn(pThis->pRead, VERR_ACCESS_DENIED);
+ AssertReturn(pThis->fFlags & RTZIPTAR_C_UPDATE, VERR_ACCESS_DENIED);
+ AssertReturn(!pThis->fWriting, VERR_WRONG_ORDER);
+
+ /*
+ * Seek to the desired cut-off point and indicate that we've switched to writing.
+ */
+ int rc = RTVfsFileSeek(pThis->hVfsFile, fAfter ? pThisObj->offNextHdr : pThisObj->offHdr,
+ RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ if (RT_SUCCESS(rc))
+ pThis->fWriting = true;
+ else
+ pThis->rcFatal = rc;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/zip/unzipcmd.cpp b/src/VBox/Runtime/common/zip/unzipcmd.cpp
new file mode 100644
index 00000000..58d70289
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/unzipcmd.cpp
@@ -0,0 +1,480 @@
+/* $Id: unzipcmd.cpp $ */
+/** @file
+ * IPRT - A mini UNZIP Command.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+#include <iprt/asm.h>
+#include <iprt/getopt.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/stream.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * IPRT UNZIP option structure.
+ */
+typedef struct RTZIPUNZIPCMDOPS
+{
+ /** The operation. */
+ int iOperation;
+ /** The long operation option name. */
+ const char *pszOperation;
+ /** The directory to change into when upacking. */
+ const char *pszDirectory;
+ /** The unzip file name. */
+ const char *pszFile;
+ /** The number of files/directories to be extracted from archive specified. */
+ uint32_t cFiles;
+ /** Wether we're verbose or quiet. */
+ bool fVerbose;
+ /** Skip the restauration of the modification time for directories. */
+ bool fNoModTimeDirectories;
+ /** Skip the restauration of the modification time for files. */
+ bool fNoModTimeFiles;
+ /** Array of files/directories, terminated by a NULL entry. */
+ const char * const *papszFiles;
+} RTZIPUNZIPCMDOPS;
+/** Pointer to the UNZIP options. */
+typedef RTZIPUNZIPCMDOPS *PRTZIPUNZIPCMDOPS;
+
+/**
+ * Callback used by rtZipUnzipDoWithMembers
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The Unzip options.
+ * @param hVfsObj The Unzip object to display
+ * @param pszName The name.
+ * @param rcExit The current exit code.
+ */
+typedef RTEXITCODE (*PFNDOWITHMEMBER)(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes);
+
+
+/**
+ *
+ */
+static RTEXITCODE rtZipUnzipCmdListCallback(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj,
+ const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes)
+{
+ RT_NOREF_PV(pOpts);
+
+ /*
+ * Query all the information.
+ */
+ RTFSOBJINFO UnixInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
+
+ RTTIME time;
+ if (!RTTimeExplode(&time, &UnixInfo.ModificationTime))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot explode time on '%s'", pszName);
+
+ RTPrintf("%9RU64 %04d-%02d-%02d %02d:%02d %s\n",
+ UnixInfo.cbObject,
+ time.i32Year, time.u8Month, time.u8MonthDay,
+ time.u8Hour, time.u8Minute,
+ pszName);
+
+ *pcBytes = UnixInfo.cbObject;
+ return rcExit;
+}
+
+
+/**
+ * Extracts a file.
+ */
+static RTEXITCODE rtZipUnzipCmdExtractFile(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit,
+ const char *pszDst, PCRTFSOBJINFO pUnixInfo)
+{
+ /*
+ * Open the destination file and create a stream object for it.
+ */
+ uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
+ | (pUnixInfo->Attr.fMode << RTFILE_O_CREATE_MODE_SHIFT);
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszDst, fOpen);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating file: %Rrc", pszDst, rc);
+
+ RTVFSIOSTREAM hVfsIosDst;
+ rc = RTVfsIoStrmFromRTFile(hFile, fOpen, true /*fLeaveOpen*/, &hVfsIosDst);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Pump the data thru.
+ */
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj);
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(pUnixInfo->cbObject, _1M));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Correct the file mode and other attributes.
+ */
+ if (!pOpts->fNoModTimeFiles)
+ {
+ rc = RTFileSetTimes(hFile, NULL, &pUnixInfo->ModificationTime, NULL, NULL);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error setting times: %Rrc", pszDst, rc);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error writing out file: %Rrc", pszDst, rc);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating I/O stream for file: %Rrc", pszDst, rc);
+
+ return rcExit;
+}
+
+
+/**
+ *
+ */
+static RTEXITCODE rtZipUnzipCmdExtractCallback(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj,
+ const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes)
+{
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszName);
+
+ /*
+ * Query all the information.
+ */
+ RTFSOBJINFO UnixInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
+
+ *pcBytes = UnixInfo.cbObject;
+
+ char szDst[RTPATH_MAX];
+ rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to construct destination path for: %Rrc", pszName, rc);
+
+ /*
+ * Extract according to the type.
+ */
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ return rtZipUnzipCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo);
+
+ case RTFS_TYPE_DIRECTORY:
+ rc = RTDirCreateFullPath(szDst, UnixInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating directory: %Rrc", szDst, rc);
+ break;
+
+ default:
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Unknown file type.", pszName);
+ }
+
+ if (!pOpts->fNoModTimeDirectories)
+ {
+ rc = RTPathSetTimesEx(szDst, NULL, &UnixInfo.ModificationTime, NULL, NULL, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED && rc != VERR_NS_SYMLINK_SET_TIME)
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error changing modification time: %Rrc.", pszName, rc);
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Checks if @a pszName is a member of @a papszNames, optionally returning the
+ * index.
+ *
+ * @returns true if the name is in the list, otherwise false.
+ * @param pszName The name to find.
+ * @param papszNames The array of names.
+ * @param piName Where to optionally return the array index.
+ */
+static bool rtZipUnzipCmdIsNameInArray(const char *pszName, const char * const *papszNames, uint32_t *piName)
+{
+ for (uint32_t iName = 0; papszNames[iName]; ++iName)
+ if (!strcmp(papszNames[iName], pszName))
+ {
+ if (piName)
+ *piName = iName;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Opens the input archive specified by the options.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param phVfsFss Where to return the UNZIP filesystem stream handle.
+ */
+static RTEXITCODE rtZipUnzipCmdOpenInputArchive(PRTZIPUNZIPCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Open the input file.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ int rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
+
+ rc = RTZipPkzipFsStreamFromIoStream(hVfsIos, 0 /*fFlags*/, phVfsFss);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open pkzip filesystem stream: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Worker for the --list and --extract commands.
+ *
+ * @returns The appropriate exit code.
+ * @param pOpts The Unzip options.
+ * @param pfnCallback The command specific callback.
+ */
+static RTEXITCODE rtZipUnzipDoWithMembers(PRTZIPUNZIPCMDOPS pOpts, PFNDOWITHMEMBER pfnCallback,
+ uint32_t *pcFiles, PRTFOFF pcBytes)
+{
+ /*
+ * Allocate a bitmap to go with the file list. This will be used to
+ * indicate which files we've processed and which not.
+ */
+ uint32_t *pbmFound = NULL;
+ if (pOpts->cFiles)
+ {
+ pbmFound = (uint32_t *)RTMemAllocZ(((pOpts->cFiles + 31) / 32) * sizeof(uint32_t));
+ if (!pbmFound)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate the found-file-bitmap");
+ }
+
+ uint32_t cFiles = 0;
+ RTFOFF cBytesSum = 0;
+
+ /*
+ * Open the input archive.
+ */
+ RTVFSFSSTREAM hVfsFssIn;
+ RTEXITCODE rcExit = rtZipUnzipCmdOpenInputArchive(pOpts, &hVfsFssIn);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Process the stream.
+ */
+ for (;;)
+ {
+ /*
+ * Retrieve the next object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ int rc = RTVfsFsStrmNext(hVfsFssIn, &pszName, NULL, &hVfsObj);
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_EOF)
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext returned %Rrc", rc);
+ break;
+ }
+
+ /*
+ * Should we process this object?
+ */
+ uint32_t iFile = UINT32_MAX;
+ if ( !pOpts->cFiles
+ || rtZipUnzipCmdIsNameInArray(pszName, pOpts->papszFiles, &iFile))
+ {
+ if (pbmFound)
+ ASMBitSet(pbmFound, iFile);
+
+ RTFOFF cBytes = 0;
+ rcExit = pfnCallback(pOpts, hVfsObj, pszName, rcExit, &cBytes);
+
+ cBytesSum += cBytes;
+ cFiles++;
+ }
+
+ /*
+ * Release the current object and string.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ }
+
+ /*
+ * Complain about any files we didn't find.
+ */
+ for (uint32_t iFile = 0; iFile <pOpts->cFiles; iFile++)
+ if (!ASMBitTest(pbmFound, iFile))
+ {
+ RTMsgError("%s: Was not found in the archive", pOpts->papszFiles[iFile]);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ RTVfsFsStrmRelease(hVfsFssIn);
+ }
+
+ RTMemFree(pbmFound);
+
+ *pcFiles = cFiles;
+ *pcBytes = cBytesSum;
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+RTDECL(RTEXITCODE) RTZipUnzipCmd(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* options */
+ { NULL, 'c', RTGETOPT_REQ_NOTHING }, /* extract files to stdout/stderr */
+ { NULL, 'd', RTGETOPT_REQ_STRING }, /* extract files to this directory */
+ { NULL, 'l', RTGETOPT_REQ_NOTHING }, /* list archive files (short format) */
+ { NULL, 'p', RTGETOPT_REQ_NOTHING }, /* extract files to stdout */
+ { NULL, 't', RTGETOPT_REQ_NOTHING }, /* test archive files */
+ { NULL, 'v', RTGETOPT_REQ_NOTHING }, /* verbose */
+
+ /* modifiers */
+ { NULL, 'a', RTGETOPT_REQ_NOTHING }, /* convert text files */
+ { NULL, 'b', RTGETOPT_REQ_NOTHING }, /* no conversion, treat as binary */
+ { NULL, 'D', RTGETOPT_REQ_NOTHING }, /* don't restore timestamps for directories
+ (and files) */
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTZIPUNZIPCMDOPS Opts;
+ RT_ZERO(Opts);
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'd':
+ if (Opts.pszDirectory)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -d once");
+ Opts.pszDirectory = ValueUnion.psz;
+ break;
+
+ case 'D':
+ if (!Opts.fNoModTimeDirectories)
+ Opts.fNoModTimeDirectories = true; /* -D */
+ else
+ Opts.fNoModTimeFiles = true; /* -DD */
+ break;
+
+ case 'l':
+ case 't': /* treat 'test' like 'list' */
+ if (Opts.iOperation)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Conflicting unzip operation (%s already set, now %s)",
+ Opts.pszOperation, ValueUnion.pDef->pszLong);
+ Opts.iOperation = 'l';
+ Opts.pszOperation = ValueUnion.pDef->pszLong;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ default:
+ Opts.pszFile = ValueUnion.psz;
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ Assert((unsigned)GetState.iNext - 1 <= cArgs);
+ Opts.pszFile = papszArgs[GetState.iNext - 1];
+ if ((unsigned)GetState.iNext <= cArgs)
+ {
+ Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext];
+ Opts.cFiles = cArgs - GetState.iNext;
+ }
+ }
+
+ if (!Opts.pszFile)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No input archive specified");
+
+ RTFOFF cBytes = 0;
+ uint32_t cFiles = 0;
+ switch (Opts.iOperation)
+ {
+ case 'l':
+ {
+ RTPrintf(" Length Date Time Name\n"
+ "--------- ---------- ----- ----\n");
+ RTEXITCODE rcExit = rtZipUnzipDoWithMembers(&Opts, rtZipUnzipCmdListCallback, &cFiles, &cBytes);
+ RTPrintf("--------- -------\n"
+ "%9RU64 %u file%s\n",
+ cBytes, cFiles, cFiles != 1 ? "s" : "");
+
+ return rcExit;
+ }
+
+ default:
+ return rtZipUnzipDoWithMembers(&Opts, rtZipUnzipCmdExtractCallback, &cFiles, &cBytes);
+ }
+}
diff --git a/src/VBox/Runtime/common/zip/xarvfs.cpp b/src/VBox/Runtime/common/zip/xarvfs.cpp
new file mode 100644
index 00000000..571a51c7
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/xarvfs.cpp
@@ -0,0 +1,2156 @@
+/* $Id: xarvfs.cpp $ */
+/** @file
+ * IPRT - XAR Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/md5.h>
+#include <iprt/poll.h>
+#include <iprt/file.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/formats/xar.h>
+#include <iprt/cpp/xml.h>
+
+
+/*********************************************************************************************************************************
+* 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<const xml::ElementNode *>(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,
+ NULL,
+ 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,
+ NULL,
+ 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,
+ NULL,
+ 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_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtZipXarFssFile_SetMode,
+ rtZipXarFssFile_SetTimes,
+ rtZipXarFssFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtZipXarFssFile_Seek,
+ rtZipXarFssFile_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ 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,
+ NULL,
+ 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 cbTarget)
+{
+ PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis;
+#if 0
+ return RTStrCopy(pszTarget, cbXarget, pThis->pXarReader->szTarget);
+#else
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pszTarget); RT_NOREF_PV(cbTarget);
+ 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,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSSYMLINKOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj),
+ 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,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipXarFss_Next,
+ NULL,
+ NULL,
+ NULL,
+ 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;
+ RT_NOREF_PV(pXarHdr);
+
+ /*
+ * 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 &)
+ {
+ 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, RTFILE_O_READ,
+ &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;
+}
+
diff --git a/src/VBox/Runtime/common/zip/zip.cpp b/src/VBox/Runtime/common/zip/zip.cpp
new file mode 100644
index 00000000..68c2a9c6
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/zip.cpp
@@ -0,0 +1,2016 @@
+/* $Id: zip.cpp $ */
+/** @file
+ * IPRT - Compression.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox 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.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTZIP_USE_STORE 1
+#define RTZIP_USE_ZLIB 1
+//#define RTZIP_USE_BZLIB 1
+#if !defined(IN_GUEST) && !defined(IPRT_NO_CRT)
+# define RTZIP_USE_LZF 1
+#endif
+#define RTZIP_LZF_BLOCK_BY_BLOCK
+//#define RTZIP_USE_LZJB 1
+//#define RTZIP_USE_LZO 1
+
+/** @todo FastLZ? QuickLZ? Others? */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cdefs.h>
+#ifdef RTZIP_USE_BZLIB
+# include <bzlib.h>
+#endif
+#ifdef RTZIP_USE_ZLIB
+# include <zlib.h>
+#endif
+#ifdef RTZIP_USE_LZF
+ RT_C_DECLS_BEGIN
+# include <lzf.h>
+ RT_C_DECLS_END
+# include <iprt/crc.h>
+#endif
+#ifdef RTZIP_USE_LZJB
+# include "lzjb.h"
+#endif
+#ifdef RTZIP_USE_LZO
+# include <lzo/lzo1x.h>
+#endif
+
+#include <iprt/zip.h>
+#include "internal/iprt.h"
+
+/*#include <iprt/asm.h>*/
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/string.h>
+
+#ifndef IPRT_NO_CRT
+# include <errno.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+#ifdef RTZIP_USE_LZF
+
+/**
+ * LZF block header.
+ */
+#pragma pack(1) /* paranoia */
+typedef struct RTZIPLZFHDR
+{
+ /** Magic word (RTZIPLZFHDR_MAGIC). */
+ uint16_t u16Magic;
+ /** The number of bytes of data following this header. */
+ uint16_t cbData;
+ /** The CRC32 of the block. */
+ uint32_t u32CRC;
+ /** The size of the uncompressed data in bytes. */
+ uint16_t cbUncompressed;
+} RTZIPLZFHDR;
+#pragma pack()
+/** Pointer to a LZF block header. */
+typedef RTZIPLZFHDR *PRTZIPLZFHDR;
+/** Pointer to a const LZF block header. */
+typedef const RTZIPLZFHDR *PCRTZIPLZFHDR;
+
+/** The magic of a LZF block header. */
+#define RTZIPLZFHDR_MAGIC ('Z' | ('V' << 8))
+
+/** The max compressed data size.
+ * The maximum size of a block is currently 16KB.
+ * This is very important so we don't have to move input buffers around. */
+#define RTZIPLZF_MAX_DATA_SIZE (16384 - sizeof(RTZIPLZFHDR))
+
+/** The max uncompressed data size.
+ * This is important so we don't overflow the spill buffer in the decompressor. */
+#define RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE (32*_1K)
+
+#endif /* RTZIP_USE_LZF */
+
+
+/**
+ * Compressor/Decompressor instance data.
+ */
+typedef struct RTZIPCOMP
+{
+ /** Output buffer. */
+ uint8_t abBuffer[_128K];
+ /** Compression output consumer. */
+ PFNRTZIPOUT pfnOut;
+ /** User argument for the callback. */
+ void *pvUser;
+
+ /**
+ * @copydoc RTZipCompress
+ */
+ DECLCALLBACKMEMBER(int, pfnCompress,(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf));
+
+ /**
+ * @copydoc RTZipCompFinish
+ */
+ DECLCALLBACKMEMBER(int, pfnFinish,(PRTZIPCOMP pZip));
+
+ /**
+ * @copydoc RTZipCompDestroy
+ */
+ DECLCALLBACKMEMBER(int, pfnDestroy,(PRTZIPCOMP pZip));
+
+ /** Compression type. */
+ RTZIPTYPE enmType;
+ /** Type specific data. */
+ union
+ {
+#ifdef RTZIP_USE_STORE
+ /** Simple storing. */
+ struct
+ {
+ /** Current buffer position. (where to start write) */
+ uint8_t *pb;
+ } Store;
+#endif
+#ifdef RTZIP_USE_ZLIB
+ /** Zlib stream. */
+ z_stream Zlib;
+#endif
+#ifdef RTZIP_USE_BZLIB
+ /** BZlib stream. */
+ bz_stream BZlib;
+#endif
+#ifdef RTZIP_USE_LZF
+ /** LZF stream. */
+ struct
+ {
+ /** Current output buffer position. */
+ uint8_t *pbOutput;
+ /** The input buffer position. */
+ uint8_t *pbInput;
+ /** The number of free bytes in the input buffer. */
+ size_t cbInputFree;
+ /** The input buffer. */
+ uint8_t abInput[RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE];
+ } LZF;
+#endif
+
+ } u;
+} RTZIPCOMP;
+
+
+
+/**
+ * Decompressor instance data.
+ */
+typedef struct RTZIPDECOMP
+{
+ /** Input buffer. */
+ uint8_t abBuffer[_128K];
+ /** Decompression input producer. */
+ PFNRTZIPIN pfnIn;
+ /** User argument for the callback. */
+ void *pvUser;
+
+ /**
+ * @copydoc RTZipDecompress
+ */
+ DECLCALLBACKMEMBER(int, pfnDecompress,(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten));
+
+ /**
+ * @copydoc RTZipDecompDestroy
+ */
+ DECLCALLBACKMEMBER(int, pfnDestroy,(PRTZIPDECOMP pZip));
+
+ /** Compression type. */
+ RTZIPTYPE enmType;
+ /** Type specific data. */
+ union
+ {
+#ifdef RTZIP_USE_STORE
+ /** Simple storing. */
+ struct
+ {
+ /** Current buffer position. (where to start read) */
+ uint8_t *pb;
+ /** Number of bytes left in the buffer. */
+ size_t cbBuffer;
+ } Store;
+#endif
+#ifdef RTZIP_USE_ZLIB
+ /** Zlib stream. */
+ z_stream Zlib;
+#endif
+#ifdef RTZIP_USE_BZLIB
+ /** BZlib stream. */
+ bz_stream BZlib;
+#endif
+#ifdef RTZIP_USE_LZF
+ /** LZF 'stream'. */
+ struct
+ {
+# ifndef RTZIP_LZF_BLOCK_BY_BLOCK
+ /** Current input buffer position. */
+ uint8_t *pbInput;
+ /** The number of bytes left in the input buffer. */
+ size_t cbInput;
+# endif
+ /** The spill buffer.
+ * LZF is a block based compressor and not a stream compressor. So,
+ * we have to decompress full blocks if we want to get any of the data.
+ * This buffer is to store the spill after decompressing a block. */
+ uint8_t abSpill[RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE];
+ /** The number of bytes left spill buffer. */
+ unsigned cbSpill;
+ /** The current spill buffer position. */
+ uint8_t *pbSpill;
+ } LZF;
+#endif
+
+ } u;
+} RTZIPDECOM;
+
+
+
+#ifdef RTZIP_USE_STORE
+
+/**
+ * @copydoc RTZipCompress
+ */
+static DECLCALLBACK(int) rtZipStoreCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+ uint8_t *pbDst = pZip->u.Store.pb;
+ while (cbBuf)
+ {
+ /*
+ * Flush.
+ */
+ size_t cb = sizeof(pZip->abBuffer) - (size_t)(pbDst - &pZip->abBuffer[0]); /* careful here, g++ 4.1.2 screws up easily */
+ if (cb == 0)
+ {
+ int rc = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ cb = sizeof(pZip->abBuffer);
+ pbDst = &pZip->abBuffer[0];
+ }
+
+ /*
+ * Add to the buffer and advance.
+ */
+ if (cbBuf < cb)
+ cb = cbBuf;
+ memcpy(pbDst, pvBuf, cb);
+
+ pbDst += cb;
+ cbBuf -= cb;
+ pvBuf = (uint8_t *)pvBuf + cb;
+ }
+ pZip->u.Store.pb = pbDst;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompFinish
+ */
+static DECLCALLBACK(int) rtZipStoreCompFinish(PRTZIPCOMP pZip)
+{
+ size_t cb = (uintptr_t)pZip->u.Store.pb - (uintptr_t)&pZip->abBuffer[0];
+ if (cb > 0)
+ {
+ int rc = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompDestroy
+ */
+static DECLCALLBACK(int) rtZipStoreCompDestroy(PRTZIPCOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initializes the compressor instance.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param enmLevel The desired compression level.
+ */
+static DECLCALLBACK(int) rtZipStoreCompInit(PRTZIPCOMP pZip, RTZIPLEVEL enmLevel)
+{
+ NOREF(enmLevel);
+ pZip->pfnCompress = rtZipStoreCompress;
+ pZip->pfnFinish = rtZipStoreCompFinish;
+ pZip->pfnDestroy = rtZipStoreCompDestroy;
+
+ pZip->u.Store.pb = &pZip->abBuffer[1];
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipStoreDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ size_t cbWritten = 0;
+ while (cbBuf)
+ {
+ /*
+ * Fill buffer.
+ */
+ size_t cb = pZip->u.Store.cbBuffer;
+ if (cb <= 0)
+ {
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer), &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.Store.cbBuffer = cb;
+ pZip->u.Store.pb = &pZip->abBuffer[0];
+ }
+
+ /*
+ * No more data?
+ */
+ if (cb == 0)
+ {
+ if (pcbWritten)
+ {
+ *pcbWritten = cbWritten;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_DATA;
+ }
+
+ /*
+ * Add to the buffer and advance.
+ */
+ if (cbBuf < cb)
+ cb = cbBuf;
+ memcpy(pvBuf, pZip->u.Store.pb, cb);
+ pZip->u.Store.pb += cb;
+ pZip->u.Store.cbBuffer -= cb;
+ cbBuf -= cb;
+ pvBuf = (char *)pvBuf + cb;
+ cbWritten += cb;
+ }
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipStoreDecompDestroy(PRTZIPDECOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize the decompressor instance.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+static DECLCALLBACK(int) rtZipStoreDecompInit(PRTZIPDECOMP pZip)
+{
+ pZip->pfnDecompress = rtZipStoreDecompress;
+ pZip->pfnDestroy = rtZipStoreDecompDestroy;
+
+ pZip->u.Store.pb = &pZip->abBuffer[0];
+ pZip->u.Store.cbBuffer = 0;
+ return VINF_SUCCESS;
+}
+
+#endif /* RTZIP_USE_STORE */
+
+
+#ifdef RTZIP_USE_ZLIB
+
+/*
+ * Missing definitions from zutil.h. We need these constants for calling
+ * inflateInit2() / deflateInit2().
+ */
+# ifndef Z_DEF_WBITS
+# define Z_DEF_WBITS MAX_WBITS
+# endif
+# ifndef Z_DEF_MEM_LEVEL
+# define Z_DEF_MEM_LEVEL 8
+# endif
+
+/**
+ * Convert from zlib errno to iprt status code.
+ * @returns iprt status code.
+ * @param rc Zlib error code.
+ * @param fCompressing Set if we're compressing, clear if decompressing.
+ */
+static int zipErrConvertFromZlib(int rc, bool fCompressing)
+{
+ switch (rc)
+ {
+ case Z_OK:
+ return VINF_SUCCESS;
+
+ case Z_STREAM_ERROR:
+ return VERR_ZIP_CORRUPTED;
+
+ case Z_DATA_ERROR:
+ return fCompressing ? VERR_ZIP_ERROR : VERR_ZIP_CORRUPTED;
+
+ case Z_MEM_ERROR:
+ return VERR_ZIP_NO_MEMORY;
+
+ case Z_BUF_ERROR:
+ return VERR_ZIP_ERROR;
+
+ case Z_VERSION_ERROR:
+ return VERR_ZIP_UNSUPPORTED_VERSION;
+
+ case Z_ERRNO: /* We shouldn't see this status! */
+ default:
+ AssertMsgFailed(("%d\n", rc));
+ if (rc >= 0)
+ return VINF_SUCCESS;
+ return VERR_ZIP_ERROR;
+ }
+}
+
+
+/**
+ * @copydoc RTZipCompress
+ */
+static DECLCALLBACK(int) rtZipZlibCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+ pZip->u.Zlib.next_in = (Bytef *)pvBuf;
+ pZip->u.Zlib.avail_in = (uInt)cbBuf; Assert(pZip->u.Zlib.avail_in == cbBuf);
+ while (pZip->u.Zlib.avail_in > 0)
+ {
+ /*
+ * Flush output buffer?
+ */
+ if (pZip->u.Zlib.avail_out <= 0)
+ {
+ int rc = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer) - pZip->u.Zlib.avail_out);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.Zlib.avail_out = sizeof(pZip->abBuffer);
+ pZip->u.Zlib.next_out = &pZip->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ int rc = deflate(&pZip->u.Zlib, Z_NO_FLUSH);
+ if (rc != Z_OK)
+ return zipErrConvertFromZlib(rc, true /*fCompressing*/);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompFinish
+ */
+static DECLCALLBACK(int) rtZipZlibCompFinish(PRTZIPCOMP pZip)
+{
+ int rc = Z_OK;
+ for (;;)
+ {
+ /*
+ * Flush outstanding stuff. writes.
+ */
+ if (rc == Z_STREAM_END || pZip->u.Zlib.avail_out <= 0)
+ {
+ int rc2 = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer) - pZip->u.Zlib.avail_out);
+ if (RT_FAILURE(rc2))
+ return rc2;
+ pZip->u.Zlib.avail_out = sizeof(pZip->abBuffer);
+ pZip->u.Zlib.next_out = &pZip->abBuffer[0];
+ if (rc == Z_STREAM_END)
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Tell zlib to flush.
+ */
+ rc = deflate(&pZip->u.Zlib, Z_FINISH);
+ if (rc != Z_OK && rc != Z_STREAM_END)
+ return zipErrConvertFromZlib(rc, true /*fCompressing*/);
+ }
+}
+
+
+/**
+ * @copydoc RTZipCompDestroy
+ */
+static DECLCALLBACK(int) rtZipZlibCompDestroy(PRTZIPCOMP pZip)
+{
+ /*
+ * Terminate the deflate instance.
+ */
+ int rc = deflateEnd(&pZip->u.Zlib);
+ if (rc != Z_OK)
+ rc = zipErrConvertFromZlib(rc, true /*fCompressing*/);
+ return rc;
+}
+
+
+/**
+ * Initializes the compressor instance.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param enmLevel The desired compression level.
+ * @param fZlibHeader If true, write the Zlib header.
+ */
+static DECLCALLBACK(int) rtZipZlibCompInit(PRTZIPCOMP pZip, RTZIPLEVEL enmLevel, bool fZlibHeader)
+{
+ pZip->pfnCompress = rtZipZlibCompress;
+ pZip->pfnFinish = rtZipZlibCompFinish;
+ pZip->pfnDestroy = rtZipZlibCompDestroy;
+
+ int iLevel = Z_DEFAULT_COMPRESSION;
+ switch (enmLevel)
+ {
+ case RTZIPLEVEL_STORE: iLevel = 0; break;
+ case RTZIPLEVEL_FAST: iLevel = 2; break;
+ case RTZIPLEVEL_DEFAULT: iLevel = Z_DEFAULT_COMPRESSION; break;
+ case RTZIPLEVEL_MAX: iLevel = 9; break;
+ }
+
+ memset(&pZip->u.Zlib, 0, sizeof(pZip->u.Zlib));
+ pZip->u.Zlib.next_out = &pZip->abBuffer[1];
+ pZip->u.Zlib.avail_out = sizeof(pZip->abBuffer) - 1;
+ pZip->u.Zlib.opaque = pZip;
+
+ int rc = deflateInit2(&pZip->u.Zlib, iLevel, Z_DEFLATED, fZlibHeader ? Z_DEF_WBITS : -Z_DEF_WBITS,
+ Z_DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+ return rc >= 0 ? rc = VINF_SUCCESS : zipErrConvertFromZlib(rc, true /*fCompressing*/);
+}
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipZlibDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ pZip->u.Zlib.next_out = (Bytef *)pvBuf;
+ pZip->u.Zlib.avail_out = (uInt)cbBuf;
+ Assert(pZip->u.Zlib.avail_out == cbBuf);
+
+ /*
+ * Be greedy reading input, even if no output buffer is left. It's possible
+ * that it's just the end of stream marker which needs to be read. Happens
+ * for incompressible blocks just larger than the input buffer size.
+ */
+ while (pZip->u.Zlib.avail_out > 0 || pZip->u.Zlib.avail_in <= 0)
+ {
+ /*
+ * Read more input?
+ */
+ if (pZip->u.Zlib.avail_in <= 0)
+ {
+ size_t cb = sizeof(pZip->abBuffer);
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer), &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.Zlib.avail_in = (uInt)cb; Assert(pZip->u.Zlib.avail_in == cb);
+ pZip->u.Zlib.next_in = &pZip->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ int rc = inflate(&pZip->u.Zlib, Z_NO_FLUSH);
+ if (rc == Z_STREAM_END)
+ {
+ if (pcbWritten)
+ *pcbWritten = cbBuf - pZip->u.Zlib.avail_out;
+ else if (pZip->u.Zlib.avail_out > 0)
+ return VERR_NO_DATA;
+ break;
+ }
+ if (rc != Z_OK)
+ return zipErrConvertFromZlib(rc, false /*fCompressing*/);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipZlibDecompDestroy(PRTZIPDECOMP pZip)
+{
+ /*
+ * Terminate the deflate instance.
+ */
+ int rc = inflateEnd(&pZip->u.Zlib);
+ if (rc != Z_OK)
+ rc = zipErrConvertFromZlib(rc, false /*fCompressing*/);
+ return rc;
+}
+
+
+/**
+ * Initialize the decompressor instance.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ * @param fZlibHeader If true, expect the Zlib header.
+ */
+static DECLCALLBACK(int) rtZipZlibDecompInit(PRTZIPDECOMP pZip, bool fZlibHeader)
+{
+ pZip->pfnDecompress = rtZipZlibDecompress;
+ pZip->pfnDestroy = rtZipZlibDecompDestroy;
+
+ memset(&pZip->u.Zlib, 0, sizeof(pZip->u.Zlib));
+ pZip->u.Zlib.opaque = pZip;
+
+ int rc = inflateInit2(&pZip->u.Zlib, fZlibHeader ? Z_DEF_WBITS : -Z_DEF_WBITS);
+ return rc >= 0 ? VINF_SUCCESS : zipErrConvertFromZlib(rc, false /*fCompressing*/);
+}
+
+#endif /* RTZIP_USE_ZLIB */
+
+
+#ifdef RTZIP_USE_BZLIB
+/**
+ * Convert from BZlib errno to iprt status code.
+ * @returns iprt status code.
+ * @param rc BZlib error code.
+ */
+static int zipErrConvertFromBZlib(int rc)
+{
+ /** @todo proper bzlib error conversion. */
+ switch (rc)
+ {
+ case BZ_SEQUENCE_ERROR:
+ AssertMsgFailed(("BZ_SEQUENCE_ERROR shall not happen!\n"));
+ return VERR_GENERAL_FAILURE;
+ case BZ_PARAM_ERROR:
+ return VERR_INVALID_PARAMETER;
+ case BZ_MEM_ERROR:
+ return VERR_NO_MEMORY;
+ case BZ_DATA_ERROR:
+ case BZ_DATA_ERROR_MAGIC:
+ case BZ_IO_ERROR:
+ case BZ_UNEXPECTED_EOF:
+ case BZ_CONFIG_ERROR:
+ return VERR_GENERAL_FAILURE;
+ case BZ_OUTBUFF_FULL:
+ AssertMsgFailed(("BZ_OUTBUFF_FULL shall not happen!\n"));
+ return VERR_GENERAL_FAILURE;
+ default:
+ if (rc >= 0)
+ return VINF_SUCCESS;
+ return VERR_GENERAL_FAILURE;
+ }
+}
+
+
+/**
+ * @copydoc RTZipCompress
+ */
+static DECLCALLBACK(int) rtZipBZlibCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+ pZip->u.BZlib.next_in = (char *)pvBuf;
+ pZip->u.BZlib.avail_in = cbBuf;
+ while (pZip->u.BZlib.avail_in > 0)
+ {
+ /*
+ * Flush output buffer?
+ */
+ if (pZip->u.BZlib.avail_out <= 0)
+ {
+ int rc = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer) - pZip->u.BZlib.avail_out);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.BZlib.avail_out = sizeof(pZip->abBuffer);
+ pZip->u.BZlib.next_out = (char *)&pZip->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ int rc = BZ2_bzCompress(&pZip->u.BZlib, BZ_RUN);
+ if (rc < 0 && rc != BZ_OUTBUFF_FULL)
+ return zipErrConvertFromBZlib(rc);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompFinish
+ */
+static DECLCALLBACK(int) rtZipBZlibCompFinish(PRTZIPCOMP pZip)
+{
+ int rc = BZ_FINISH_OK;
+ for (;;)
+ {
+ /*
+ * Flush output buffer?
+ */
+ if (rc == BZ_STREAM_END || pZip->u.BZlib.avail_out <= 0)
+ {
+ int rc2 = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer) - pZip->u.BZlib.avail_out);
+ if (RT_FAILURE(rc2))
+ return rc2;
+ pZip->u.BZlib.avail_out = sizeof(pZip->abBuffer);
+ pZip->u.BZlib.next_out = (char *)&pZip->abBuffer[0];
+ if (rc == BZ_STREAM_END)
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Tell BZlib to finish it.
+ */
+ rc = BZ2_bzCompress(&pZip->u.BZlib, BZ_FINISH);
+ if (rc < 0 && rc != BZ_OUTBUFF_FULL)
+ return zipErrConvertFromBZlib(rc);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompDestroy
+ */
+static DECLCALLBACK(int) rtZipBZlibCompDestroy(PRTZIPCOMP pZip)
+{
+ /*
+ * Terminate the deflate instance.
+ */
+ int rc = BZ2_bzCompressEnd(&pZip->u.BZlib);
+ if (rc != BZ_OK)
+ rc = zipErrConvertFromBZlib(rc);
+ return rc;
+}
+
+
+/**
+ * Initializes the compressor instance.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param enmLevel The desired compression level.
+ */
+static DECLCALLBACK(int) rtZipBZlibCompInit(PRTZIPCOMP pZip, RTZIPLEVEL enmLevel)
+{
+ pZip->pfnCompress = rtZipBZlibCompress;
+ pZip->pfnFinish = rtZipBZlibCompFinish;
+ pZip->pfnDestroy = rtZipBZlibCompDestroy;
+
+ int iSize = 6;
+ int iWork = 0;
+ switch (enmLevel)
+ {
+ case RTZIPLEVEL_STORE: iSize = 1; iWork = 2; break;
+ case RTZIPLEVEL_FAST: iSize = 2; iWork = 0; break;
+ case RTZIPLEVEL_DEFAULT: iSize = 5; iWork = 0; break;
+ case RTZIPLEVEL_MAX: iSize = 9; iWork = 0; break;
+ }
+
+ memset(&pZip->u.BZlib, 0, sizeof(pZip->u.BZlib));
+ pZip->u.BZlib.next_out = (char *)&pZip->abBuffer[1];
+ pZip->u.BZlib.avail_out = sizeof(pZip->abBuffer) - 1;
+ pZip->u.BZlib.opaque = pZip;
+
+ int rc = BZ2_bzCompressInit(&pZip->u.BZlib, iSize, 0, iWork);
+ return rc >= 0 ? VINF_SUCCESS : zipErrConvertFromBZlib(rc);;
+}
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipBZlibDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ pZip->u.BZlib.next_out = (char *)pvBuf;
+ pZip->u.BZlib.avail_out = cbBuf;
+ while (pZip->u.BZlib.avail_out > 0)
+ {
+ /*
+ * Read more output buffer?
+ */
+ if (pZip->u.BZlib.avail_in <= 0)
+ {
+ size_t cb;
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer), &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.BZlib.avail_in = cb;
+ pZip->u.BZlib.next_in = (char *)&pZip->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ int rc = BZ2_bzDecompress(&pZip->u.BZlib);
+ if (rc == BZ_STREAM_END || rc == BZ_OUTBUFF_FULL)
+ {
+ if (pcbWritten)
+ *pcbWritten = cbBuf - pZip->u.BZlib.avail_out;
+ else if (pZip->u.BZlib.avail_out > 0)
+ return VERR_NO_DATA;
+ break;
+ }
+ if (rc < 0)
+ return zipErrConvertFromBZlib(rc);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipBZlibDecompDestroy(PRTZIPDECOMP pZip)
+{
+ /*
+ * Terminate the deflate instance.
+ */
+ int rc = BZ2_bzDecompressEnd(&pZip->u.BZlib);
+ if (rc != BZ_OK)
+ rc = zipErrConvertFromBZlib(rc);
+ return rc;
+}
+
+
+/**
+ * Initialize the decompressor instance.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+static DECLCALLBACK(int) rtZipBZlibDecompInit(PRTZIPDECOMP pZip)
+{
+ pZip->pfnDecompress = rtZipBZlibDecompress;
+ pZip->pfnDestroy = rtZipBZlibDecompDestroy;
+
+ memset(&pZip->u.BZlib, 0, sizeof(pZip->u.BZlib));
+ pZip->u.BZlib.opaque = pZip;
+
+ int rc = BZ2_bzDecompressInit(&pZip->u.BZlib, 0, 0);
+ return rc >= 0 ? VINF_SUCCESS : zipErrConvertFromBZlib(rc);
+}
+
+#endif /* RTZIP_USE_BZLIB */
+
+
+#ifdef RTZIP_USE_LZF
+
+/**
+ * Flushes the output buffer.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ */
+static int rtZipLZFCompFlushOutput(PRTZIPCOMP pZip)
+{
+ size_t cb = pZip->u.LZF.pbOutput - &pZip->abBuffer[0];
+ pZip->u.LZF.pbOutput = &pZip->abBuffer[0];
+ return pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], cb);
+}
+
+
+/**
+ * Compresses a buffer using LZF.
+ *
+ * @returns VBox status code.
+ * @param pZip The compressor instance.
+ * @param pbBuf What to compress.
+ * @param cbBuf How much to compress.
+ */
+static int rtZipLZFCompressBuffer(PRTZIPCOMP pZip, const uint8_t *pbBuf, size_t cbBuf)
+{
+ bool fForceFlush = false;
+ while (cbBuf > 0)
+ {
+ /*
+ * Flush output buffer?
+ */
+ unsigned cbFree = (unsigned)(sizeof(pZip->abBuffer) - (pZip->u.LZF.pbOutput - &pZip->abBuffer[0]));
+ if ( fForceFlush
+ || cbFree < RTZIPLZF_MAX_DATA_SIZE + sizeof(RTZIPLZFHDR))
+ {
+ int rc = rtZipLZFCompFlushOutput(pZip);
+ if (RT_FAILURE(rc))
+ return rc;
+ fForceFlush = false;
+ cbFree = sizeof(pZip->abBuffer);
+ }
+
+ /*
+ * Setup the block header.
+ */
+ PRTZIPLZFHDR pHdr = (PRTZIPLZFHDR)pZip->u.LZF.pbOutput; /* warning: This might be unaligned! */
+ pHdr->u16Magic = RTZIPLZFHDR_MAGIC;
+ pHdr->cbData = 0;
+ pHdr->u32CRC = 0;
+ pHdr->cbUncompressed = 0;
+ cbFree -= sizeof(*pHdr);
+ pZip->u.LZF.pbOutput += sizeof(*pHdr);
+
+ /*
+ * Compress data for the block.
+ *
+ * We try compress as much as we have freespace for at first,
+ * but if it turns out the compression is inefficient, we'll
+ * reduce the size of data we try compress till it fits the
+ * output space.
+ */
+ cbFree = RT_MIN(cbFree, RTZIPLZF_MAX_DATA_SIZE);
+ unsigned cbInput = (unsigned)RT_MIN(RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE, cbBuf);
+ unsigned cbOutput = lzf_compress(pbBuf, cbInput, pZip->u.LZF.pbOutput, cbFree);
+ if (!cbOutput)
+ {
+ /** @todo add an alternative method which stores the raw data if bad compression. */
+ do
+ {
+ cbInput /= 2;
+ if (!cbInput)
+ {
+ AssertMsgFailed(("lzf_compress bug! cbFree=%zu\n", cbFree));
+ return VERR_INTERNAL_ERROR;
+ }
+ cbOutput = lzf_compress(pbBuf, cbInput, pZip->u.LZF.pbOutput, cbFree);
+ } while (!cbOutput);
+ fForceFlush = true;
+ }
+
+ /*
+ * Update the header and advance the input buffer.
+ */
+ pHdr->cbData = cbOutput;
+ //pHdr->u32CRC = RTCrc32(pbBuf, cbInput); - too slow
+ pHdr->cbUncompressed = cbInput;
+
+ pZip->u.LZF.pbOutput += cbOutput;
+ cbBuf -= cbInput;
+ pbBuf += cbInput;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Flushes the input buffer.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ */
+static int rtZipLZFCompFlushInput(PRTZIPCOMP pZip)
+{
+ size_t cb = pZip->u.LZF.pbInput - &pZip->u.LZF.abInput[0];
+ pZip->u.LZF.pbInput = &pZip->u.LZF.abInput[0];
+ pZip->u.LZF.cbInputFree = sizeof(pZip->u.LZF.abInput);
+ if (cb)
+ return rtZipLZFCompressBuffer(pZip, pZip->u.LZF.abInput, cb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompress
+ */
+static DECLCALLBACK(int) rtZipLZFCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+#define RTZIPLZF_SMALL_CHUNK (128)
+
+ /*
+ * Flush the input buffer if necessary.
+ */
+ if ( ( cbBuf <= RTZIPLZF_SMALL_CHUNK
+ && cbBuf > pZip->u.LZF.cbInputFree)
+ || ( cbBuf > RTZIPLZF_SMALL_CHUNK
+ && pZip->u.LZF.cbInputFree != sizeof(pZip->u.LZF.abInput))
+ )
+ {
+ int rc = rtZipLZFCompFlushInput(pZip);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * If it's a relativly small block put it in the input buffer, elsewise
+ * compress directly it.
+ */
+ if (cbBuf <= RTZIPLZF_SMALL_CHUNK)
+ {
+ Assert(pZip->u.LZF.cbInputFree >= cbBuf);
+ memcpy(pZip->u.LZF.pbInput, pvBuf, cbBuf);
+ pZip->u.LZF.pbInput += cbBuf;
+ pZip->u.LZF.cbInputFree -= cbBuf;
+ }
+ else
+ {
+ Assert(pZip->u.LZF.cbInputFree == sizeof(pZip->u.LZF.abInput));
+ int rc = rtZipLZFCompressBuffer(pZip, (const uint8_t *)pvBuf, cbBuf);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompFinish
+ */
+static DECLCALLBACK(int) rtZipLZFCompFinish(PRTZIPCOMP pZip)
+{
+ int rc = rtZipLZFCompFlushInput(pZip);
+ if (RT_SUCCESS(rc))
+ rc = rtZipLZFCompFlushOutput(pZip);
+ return rc;
+}
+
+
+/**
+ * @copydoc RTZipCompDestroy
+ */
+static DECLCALLBACK(int) rtZipLZFCompDestroy(PRTZIPCOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initializes the compressor instance.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param enmLevel The desired compression level.
+ */
+static DECLCALLBACK(int) rtZipLZFCompInit(PRTZIPCOMP pZip, RTZIPLEVEL enmLevel)
+{
+ NOREF(enmLevel);
+ pZip->pfnCompress = rtZipLZFCompress;
+ pZip->pfnFinish = rtZipLZFCompFinish;
+ pZip->pfnDestroy = rtZipLZFCompDestroy;
+
+ pZip->u.LZF.pbOutput = &pZip->abBuffer[1];
+ pZip->u.LZF.pbInput = &pZip->u.LZF.abInput[0];
+ pZip->u.LZF.cbInputFree = sizeof(pZip->u.LZF.abInput);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * This will validate a header and to all the necessary bitching if it's invalid.
+ * @returns true if valid.
+ * @returns false if invalid.
+ * @param pHdr Pointer to the header.\
+ */
+static bool rtZipLZFValidHeader(PCRTZIPLZFHDR pHdr)
+{
+ if ( pHdr->u16Magic != RTZIPLZFHDR_MAGIC
+ || !pHdr->cbData
+ || pHdr->cbData > RTZIPLZF_MAX_DATA_SIZE
+ || !pHdr->cbUncompressed
+ || pHdr->cbUncompressed > RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE
+ )
+ {
+ AssertMsgFailed(("Invalid LZF header! %.*Rhxs\n", sizeof(*pHdr), pHdr));
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipLZFDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ /*
+ * Decompression loop.
+ *
+ * This a bit ugly because we have to deal with reading block...
+ * To simplify matters we've put a max block size and will never
+ * fill the input buffer with more than allows us to complete
+ * any partially read blocks.
+ *
+ * When possible we decompress directly to the user buffer, when
+ * not possible we'll use the spill buffer.
+ */
+# ifdef RTZIP_LZF_BLOCK_BY_BLOCK
+ size_t cbWritten = 0;
+ while (cbBuf > 0)
+ {
+ /*
+ * Anything in the spill buffer?
+ */
+ if (pZip->u.LZF.cbSpill > 0)
+ {
+ unsigned cb = (unsigned)RT_MIN(pZip->u.LZF.cbSpill, cbBuf);
+ memcpy(pvBuf, pZip->u.LZF.pbSpill, cb);
+ pZip->u.LZF.pbSpill += cb;
+ pZip->u.LZF.cbSpill -= cb;
+ cbWritten += cb;
+ cbBuf -= cb;
+ if (!cbBuf)
+ break;
+ pvBuf = (uint8_t *)pvBuf + cb;
+ }
+
+ /*
+ * We always read and work one block at a time.
+ */
+ RTZIPLZFHDR Hdr;
+ int rc = pZip->pfnIn(pZip->pvUser, &Hdr, sizeof(Hdr), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!rtZipLZFValidHeader(&Hdr))
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ if (Hdr.cbData > 0)
+ {
+ rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0], Hdr.cbData, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Does the uncompressed data fit into the supplied buffer?
+ * If so we uncompress it directly into the user buffer, else we'll have to use the spill buffer.
+ */
+ unsigned cbUncompressed = Hdr.cbUncompressed;
+ if (cbUncompressed <= cbBuf)
+ {
+ unsigned cbOutput = lzf_decompress(&pZip->abBuffer[0], Hdr.cbData, pvBuf, cbUncompressed);
+ if (cbOutput != cbUncompressed)
+ {
+# ifndef IPRT_NO_CRT /* no errno */
+ AssertMsgFailed(("Decompression error, errno=%d. cbOutput=%#x cbUncompressed=%#x\n",
+ errno, cbOutput, cbUncompressed));
+# endif
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ cbBuf -= cbUncompressed;
+ pvBuf = (uint8_t *)pvBuf + cbUncompressed;
+ cbWritten += cbUncompressed;
+ }
+ else
+ {
+ unsigned cbOutput = lzf_decompress(&pZip->abBuffer[0], Hdr.cbData, pZip->u.LZF.abSpill, cbUncompressed);
+ if (cbOutput != cbUncompressed)
+ {
+# ifndef IPRT_NO_CRT /* no errno */
+ AssertMsgFailed(("Decompression error, errno=%d. cbOutput=%#x cbUncompressed=%#x\n",
+ errno, cbOutput, cbUncompressed));
+# endif
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ pZip->u.LZF.pbSpill = &pZip->u.LZF.abSpill[0];
+ pZip->u.LZF.cbSpill = cbUncompressed;
+ }
+ }
+
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+# else /* !RTZIP_LZF_BLOCK_BY_BLOCK */
+ while (cbBuf > 0)
+ {
+ /*
+ * Anything in the spill buffer?
+ */
+ if (pZip->u.LZF.cbSpill > 0)
+ {
+ unsigned cb = (unsigned)RT_MIN(pZip->u.LZF.cbSpill, cbBuf);
+ memcpy(pvBuf, pZip->u.LZF.pbSpill, cb);
+ pZip->u.LZF.pbSpill += cb;
+ pZip->u.LZF.cbSpill -= cb;
+ cbBuf -= cb;
+ if (pcbWritten)
+ *pcbWritten = cb;
+ if (!cbBuf)
+ break;
+ pvBuf = (uint8_t *)pvBuf + cb;
+ }
+
+ /*
+ * Incomplete header or nothing at all.
+ */
+ PCRTZIPLZFHDR pHdr;
+ if (pZip->u.LZF.cbInput < sizeof(RTZIPLZFHDR))
+ {
+ if (pZip->u.LZF.cbInput <= 0)
+ {
+ /* empty, fill the buffer. */
+ size_t cb = 0;
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0],
+ sizeof(pZip->abBuffer) - RTZIPLZF_MAX_DATA_SIZE, &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.LZF.pbInput = &pZip->abBuffer[0];
+ pZip->u.LZF.cbInput = cb;
+ pHdr = (PCRTZIPLZFHDR)pZip->u.LZF.pbInput;
+ }
+ else
+ {
+ /* move the header up and fill the buffer. */
+ size_t cbCur = pZip->u.LZF.cbInput;
+ memmove(&pZip->abBuffer[0], pZip->u.LZF.pbInput, cbCur);
+ pZip->u.LZF.pbInput = &pZip->abBuffer[0];
+
+ size_t cb = 0;
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[cbCur],
+ sizeof(pZip->abBuffer) - RTZIPLZF_MAX_DATA_SIZE - cbCur, &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pHdr = (PCRTZIPLZFHDR)pZip->u.LZF.pbInput;
+ pZip->u.LZF.cbInput += cb;
+ }
+
+ /*
+ * Validate the header.
+ */
+ if (!rtZipLZFValidHeader(pHdr))
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ else
+ {
+ /*
+ * Validate the header and check if it's an incomplete block.
+ */
+ pHdr = (PCRTZIPLZFHDR)pZip->u.LZF.pbInput;
+ if (!rtZipLZFValidHeader(pHdr))
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+
+ if (pHdr->cbData > pZip->u.LZF.cbInput - sizeof(*pHdr))
+ {
+ /* read the remainder of the block. */
+ size_t cbToRead = pHdr->cbData - (pZip->u.LZF.cbInput - sizeof(*pHdr));
+ Assert(&pZip->u.LZF.pbInput[pZip->u.LZF.cbInput + cbToRead] <= &pZip->u.LZF.pbInput[sizeof(pZip->abBuffer)]);
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->u.LZF.pbInput[pZip->u.LZF.cbInput],
+ cbToRead, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.LZF.cbInput += cbToRead;
+ }
+ }
+ AssertMsgReturn(sizeof(*pHdr) + pHdr->cbData <= pZip->u.LZF.cbInput,
+ ("cbData=%#x cbInput=%#x\n", pHdr->cbData, pZip->u.LZF.cbInput),
+ VERR_GENERAL_FAILURE); /** @todo Get better error codes for RTZip! */
+
+ /*
+ * Does the uncompressed data fit into the supplied buffer?
+ * If so we uncompress it directly into the user buffer, else we'll have to use the spill buffer.
+ */
+ unsigned cbUncompressed = pHdr->cbUncompressed;
+ if (cbUncompressed <= cbBuf)
+ {
+ unsigned cbOutput = lzf_decompress(pHdr + 1, pHdr->cbData, pvBuf, cbUncompressed);
+ if (cbOutput != cbUncompressed)
+ {
+ AssertMsgFailed(("Decompression error, errno=%d. cbOutput=%#x cbUncompressed=%#x\n",
+ errno, cbOutput, cbUncompressed));
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ cbBuf -= cbUncompressed;
+ pvBuf = (uint8_t *)pvBuf + cbUncompressed;
+ }
+ else
+ {
+ unsigned cbOutput = lzf_decompress(pHdr + 1, pHdr->cbData, pZip->u.LZF.abSpill, cbUncompressed);
+ if (cbOutput != cbUncompressed)
+ {
+ AssertMsgFailed(("Decompression error, errno=%d. cbOutput=%#x cbUncompressed=%#x\n",
+ errno, cbOutput, cbUncompressed));
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ pZip->u.LZF.pbSpill = &pZip->u.LZF.abSpill[0];
+ pZip->u.LZF.cbSpill = cbUncompressed;
+ }
+
+ /* advance the input buffer */
+ pZip->u.LZF.cbInput -= pHdr->cbData + sizeof(*pHdr);
+ pZip->u.LZF.pbInput += pHdr->cbData + sizeof(*pHdr);
+ if (pcbWritten)
+ *pcbWritten += cbUncompressed;
+ }
+# endif /* !RTZIP_LZF_BLOCK_BY_BLOCK */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipLZFDecompDestroy(PRTZIPDECOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize the decompressor instance.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+static DECLCALLBACK(int) rtZipLZFDecompInit(PRTZIPDECOMP pZip)
+{
+ pZip->pfnDecompress = rtZipLZFDecompress;
+ pZip->pfnDestroy = rtZipLZFDecompDestroy;
+
+# ifndef RTZIP_LZF_BLOCK_BY_BLOCK
+ pZip->u.LZF.pbInput = NULL;
+ pZip->u.LZF.cbInput = 0;
+# endif
+ pZip->u.LZF.cbSpill = 0;
+ pZip->u.LZF.pbSpill = NULL;
+
+ return VINF_SUCCESS;
+}
+
+#endif /* RTZIP_USE_LZF */
+
+
+/**
+ * Create a compressor instance.
+ *
+ * @returns iprt status code.
+ * @param ppZip Where to store the instance handle.
+ * @param pvUser User argument which will be passed on to pfnOut and pfnIn.
+ * @param pfnOut Callback for consuming output of compression.
+ * @param enmType Type of compressor to create.
+ * @param enmLevel Compression level.
+ */
+RTDECL(int) RTZipCompCreate(PRTZIPCOMP *ppZip, void *pvUser, PFNRTZIPOUT pfnOut, RTZIPTYPE enmType, RTZIPLEVEL enmLevel)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(enmType >= RTZIPTYPE_INVALID && enmType < RTZIPTYPE_END, VERR_INVALID_PARAMETER);
+ AssertReturn(enmLevel >= RTZIPLEVEL_STORE && enmLevel <= RTZIPLEVEL_MAX, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pfnOut, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppZip, VERR_INVALID_POINTER);
+
+ /*
+ * Allocate memory for the instance data.
+ */
+ PRTZIPCOMP pZip = (PRTZIPCOMP)RTMemAlloc(sizeof(RTZIPCOMP));
+ if (!pZip)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Determine auto type.
+ */
+ if (enmType == RTZIPTYPE_AUTO)
+ {
+ if (enmLevel == RTZIPLEVEL_STORE)
+ enmType = RTZIPTYPE_STORE;
+ else
+ {
+#if defined(RTZIP_USE_ZLIB) && defined(RTZIP_USE_BZLIB)
+ if (enmLevel == RTZIPLEVEL_MAX)
+ enmType = RTZIPTYPE_BZLIB;
+ else
+ enmType = RTZIPTYPE_ZLIB;
+#elif defined(RTZIP_USE_ZLIB)
+ enmType = RTZIPTYPE_ZLIB;
+#elif defined(RTZIP_USE_BZLIB)
+ enmType = RTZIPTYPE_BZLIB;
+#else
+ enmType = RTZIPTYPE_STORE;
+#endif
+ }
+ }
+
+ /*
+ * Init instance.
+ */
+ pZip->pfnOut = pfnOut;
+ pZip->enmType = enmType;
+ pZip->pvUser = pvUser;
+ pZip->abBuffer[0] = enmType; /* first byte is the compression type. */
+ int rc = VERR_NOT_IMPLEMENTED;
+ switch (enmType)
+ {
+ case RTZIPTYPE_STORE:
+#ifdef RTZIP_USE_STORE
+ rc = rtZipStoreCompInit(pZip, enmLevel);
+#endif
+ break;
+
+ case RTZIPTYPE_ZLIB:
+ case RTZIPTYPE_ZLIB_NO_HEADER:
+#ifdef RTZIP_USE_ZLIB
+ rc = rtZipZlibCompInit(pZip, enmLevel, enmType == RTZIPTYPE_ZLIB /*fZlibHeader*/);
+#endif
+ break;
+
+ case RTZIPTYPE_BZLIB:
+#ifdef RTZIP_USE_BZLIB
+ rc = rtZipBZlibCompInit(pZip, enmLevel);
+#endif
+ break;
+
+ case RTZIPTYPE_LZF:
+#ifdef RTZIP_USE_LZF
+ rc = rtZipLZFCompInit(pZip, enmLevel);
+#endif
+ break;
+
+ case RTZIPTYPE_LZJB:
+ case RTZIPTYPE_LZO:
+ break;
+
+ default:
+ AssertFailedBreak();
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppZip = pZip;
+ else
+ RTMemFree(pZip);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTZipCompCreate);
+
+
+/**
+ * Compresses a chunk of memory.
+ *
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param pvBuf Pointer to buffer containing the bits to compress.
+ * @param cbBuf Number of bytes to compress.
+ */
+RTDECL(int) RTZipCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+ if (!cbBuf)
+ return VINF_SUCCESS;
+ return pZip->pfnCompress(pZip, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTZipCompress);
+
+
+/**
+ * Finishes the compression.
+ * This will flush all data and terminate the compression data stream.
+ *
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ */
+RTDECL(int) RTZipCompFinish(PRTZIPCOMP pZip)
+{
+ return pZip->pfnFinish(pZip);
+}
+RT_EXPORT_SYMBOL(RTZipCompFinish);
+
+
+/**
+ * Destroys the compressor instance.
+ *
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ */
+RTDECL(int) RTZipCompDestroy(PRTZIPCOMP pZip)
+{
+ /*
+ * Compressor specific destruction attempt first.
+ */
+ int rc = pZip->pfnDestroy(pZip);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Free the instance memory.
+ */
+ pZip->enmType = RTZIPTYPE_INVALID;
+ RTMemFree(pZip);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTZipCompDestroy);
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipStubDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ NOREF(pZip); NOREF(pvBuf); NOREF(cbBuf); NOREF(pcbWritten);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipStubDecompDestroy(PRTZIPDECOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Create a decompressor instance.
+ *
+ * @returns iprt status code.
+ * @param ppZip Where to store the instance handle.
+ * @param pvUser User argument which will be passed on to pfnOut and pfnIn.
+ * @param pfnIn Callback for producing input for decompression.
+ */
+RTDECL(int) RTZipDecompCreate(PRTZIPDECOMP *ppZip, void *pvUser, PFNRTZIPIN pfnIn)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pfnIn, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppZip, VERR_INVALID_POINTER);
+
+ /*
+ * Allocate memory for the instance data.
+ */
+ PRTZIPDECOMP pZip = (PRTZIPDECOMP)RTMemAlloc(sizeof(RTZIPDECOMP));
+ if (!pZip)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Init instance.
+ */
+ pZip->pfnIn = pfnIn;
+ pZip->enmType = RTZIPTYPE_INVALID;
+ pZip->pvUser = pvUser;
+ pZip->pfnDecompress = NULL;
+ pZip->pfnDestroy = rtZipStubDecompDestroy;
+
+ *ppZip = pZip;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTZipDecompCreate);
+
+
+/**
+ * Lazy init of the decompressor.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+static int rtzipDecompInit(PRTZIPDECOMP pZip)
+{
+ /*
+ * Read the first byte from the stream so we can determine the type.
+ */
+ uint8_t u8Type;
+ int rc = pZip->pfnIn(pZip->pvUser, &u8Type, sizeof(u8Type), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Determine type and do type specific init.
+ */
+ pZip->enmType = (RTZIPTYPE)u8Type;
+ rc = VERR_NOT_SUPPORTED;
+ switch (pZip->enmType)
+ {
+ case RTZIPTYPE_STORE:
+#ifdef RTZIP_USE_STORE
+ rc = rtZipStoreDecompInit(pZip);
+#else
+ AssertMsgFailed(("Store is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_ZLIB:
+ case RTZIPTYPE_ZLIB_NO_HEADER:
+#ifdef RTZIP_USE_ZLIB
+ rc = rtZipZlibDecompInit(pZip, pZip->enmType == RTZIPTYPE_ZLIB /*fHeader*/);
+#else
+ AssertMsgFailed(("Zlib is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_BZLIB:
+#ifdef RTZIP_USE_BZLIB
+ rc = rtZipBZlibDecompInit(pZip);
+#else
+ AssertMsgFailed(("BZlib is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_LZF:
+#ifdef RTZIP_USE_LZF
+ rc = rtZipLZFDecompInit(pZip);
+#else
+ AssertMsgFailed(("LZF is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_LZJB:
+#ifdef RTZIP_USE_LZJB
+ AssertMsgFailed(("LZJB streaming support is not implemented yet!\n"));
+#else
+ AssertMsgFailed(("LZJB is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_LZO:
+#ifdef RTZIP_USE_LZJB
+ AssertMsgFailed(("LZO streaming support is not implemented yet!\n"));
+#else
+ AssertMsgFailed(("LZO is not include in this build!\n"));
+#endif
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid compression type %d (%#x)!\n", pZip->enmType, pZip->enmType));
+ rc = VERR_INVALID_MAGIC;
+ break;
+ }
+ if (RT_FAILURE(rc))
+ {
+ pZip->pfnDecompress = rtZipStubDecompress;
+ pZip->pfnDestroy = rtZipStubDecompDestroy;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Decompresses a chunk of memory.
+ *
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ * @param pvBuf Where to store the decompressed data.
+ * @param cbBuf Number of bytes to produce. If pcbWritten is set
+ * any number of bytes up to cbBuf might be returned.
+ * @param pcbWritten Number of bytes actually written to the buffer. If NULL
+ * cbBuf number of bytes must be written.
+ */
+RTDECL(int) RTZipDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ /*
+ * Skip empty requests.
+ */
+ if (!cbBuf)
+ return VINF_SUCCESS;
+
+ /*
+ * Lazy init.
+ */
+ if (!pZip->pfnDecompress)
+ {
+ int rc = rtzipDecompInit(pZip);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * 'Read' the decompressed stream.
+ */
+ return pZip->pfnDecompress(pZip, pvBuf, cbBuf, pcbWritten);
+}
+RT_EXPORT_SYMBOL(RTZipDecompress);
+
+
+/**
+ * Destroys the decompressor instance.
+ *
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+RTDECL(int) RTZipDecompDestroy(PRTZIPDECOMP pZip)
+{
+ /*
+ * Destroy compressor instance and flush the output buffer.
+ */
+ int rc = pZip->pfnDestroy(pZip);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Free the instance memory.
+ */
+ pZip->enmType = RTZIPTYPE_INVALID;
+ RTMemFree(pZip);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTZipDecompDestroy);
+
+
+RTDECL(int) RTZipBlockCompress(RTZIPTYPE enmType, RTZIPLEVEL enmLevel, uint32_t fFlags,
+ void const *pvSrc, size_t cbSrc,
+ void *pvDst, size_t cbDst, size_t *pcbDstActual) RT_NO_THROW_DEF
+{
+ /* input validation - the crash and burn approach as speed is essential here. */
+ Assert(enmLevel <= RTZIPLEVEL_MAX && enmLevel >= RTZIPLEVEL_STORE); RT_NOREF_PV(enmLevel);
+ Assert(!fFlags); RT_NOREF_PV(fFlags);
+
+ /*
+ * Deal with flags involving prefixes.
+ */
+ /** @todo later: type and/or compressed length prefix. */
+
+ /*
+ * The type specific part.
+ */
+ switch (enmType)
+ {
+ case RTZIPTYPE_LZF:
+ {
+#ifdef RTZIP_USE_LZF
+# if 0
+ static const uint8_t s_abZero4K[] =
+ {
+ 0x01, 0x00, 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff,
+ 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0,
+ 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00,
+ 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff,
+ 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0,
+ 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00,
+ 0xe0, 0x7d, 0x00
+ };
+ if ( cbSrc == _4K
+ && !((uintptr_t)pvSrc & 15)
+ && ASMMemIsZeroPage(pvSrc))
+ {
+ if (RT_UNLIKELY(cbDst < sizeof(s_abZero4K)))
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pvDst, s_abZero4K, sizeof(s_abZero4K));
+ *pcbDstActual = sizeof(s_abZero4K);
+ break;
+ }
+# endif
+
+ unsigned cbDstActual = lzf_compress(pvSrc, (unsigned)cbSrc, pvDst, (unsigned)cbDst); /** @todo deal with size type overflows */
+ if (RT_UNLIKELY(cbDstActual < 1))
+ return VERR_BUFFER_OVERFLOW;
+ *pcbDstActual = cbDstActual;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_STORE:
+ {
+ if (cbDst < cbSrc)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pvDst, pvSrc, cbSrc);
+ *pcbDstActual = cbSrc;
+ break;
+ }
+
+ case RTZIPTYPE_LZJB:
+ {
+#ifdef RTZIP_USE_LZJB
+ AssertReturn(cbDst > cbSrc, VERR_BUFFER_OVERFLOW);
+ size_t cbDstActual = lzjb_compress((void *)pvSrc, (uint8_t *)pvDst + 1, cbSrc, cbSrc, 0 /*??*/);
+ if (cbDstActual == cbSrc)
+ *(uint8_t *)pvDst = 0;
+ else
+ *(uint8_t *)pvDst = 1;
+ *pcbDstActual = cbDstActual + 1;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_LZO:
+ {
+#ifdef RTZIP_USE_LZO
+ uint64_t Scratch[RT_ALIGN(LZO1X_1_MEM_COMPRESS, sizeof(uint64_t)) / sizeof(uint64_t)];
+ int rc = lzo_init();
+ if (RT_UNLIKELY(rc != LZO_E_OK))
+ return VERR_INTERNAL_ERROR;
+
+ lzo_uint cbDstInOut = cbDst;
+ rc = lzo1x_1_compress((const lzo_bytep)pvSrc, cbSrc, (lzo_bytep )pvDst, &cbDstInOut, &Scratch[0]);
+ if (RT_UNLIKELY(rc != LZO_E_OK))
+ switch (rc)
+ {
+ case LZO_E_OUTPUT_OVERRUN: return VERR_BUFFER_OVERFLOW;
+ default: return VERR_GENERAL_FAILURE;
+ }
+ *pcbDstActual = cbDstInOut;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_ZLIB:
+ case RTZIPTYPE_BZLIB:
+ return VERR_NOT_SUPPORTED;
+
+ default:
+ AssertMsgFailed(("%d\n", enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTZipBlockCompress);
+
+
+RTDECL(int) RTZipBlockDecompress(RTZIPTYPE enmType, uint32_t fFlags,
+ void const *pvSrc, size_t cbSrc, size_t *pcbSrcActual,
+ void *pvDst, size_t cbDst, size_t *pcbDstActual) RT_NO_THROW_DEF
+{
+ /* input validation - the crash and burn approach as speed is essential here. */
+ Assert(!fFlags); RT_NOREF_PV(fFlags);
+
+ /*
+ * Deal with flags involving prefixes.
+ */
+ /** @todo later: type and/or compressed length prefix. */
+
+ /*
+ * The type specific part.
+ */
+ switch (enmType)
+ {
+ case RTZIPTYPE_LZF:
+ {
+#ifdef RTZIP_USE_LZF
+ unsigned cbDstActual = lzf_decompress(pvSrc, (unsigned)cbSrc, pvDst, (unsigned)cbDst); /** @todo deal with size type overflows */
+ if (RT_UNLIKELY(cbDstActual < 1))
+ {
+# ifndef IPRT_NO_CRT /* no errno */
+ if (errno == E2BIG)
+ return VERR_BUFFER_OVERFLOW;
+ Assert(errno == EINVAL);
+# endif
+ return VERR_GENERAL_FAILURE;
+ }
+ if (pcbDstActual)
+ *pcbDstActual = cbDstActual;
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_STORE:
+ {
+ if (cbDst < cbSrc)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pvDst, pvSrc, cbSrc);
+ if (pcbDstActual)
+ *pcbDstActual = cbSrc;
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc;
+ break;
+ }
+
+ case RTZIPTYPE_LZJB:
+ {
+#ifdef RTZIP_USE_LZJB
+ if (*(uint8_t *)pvSrc == 1)
+ {
+ int rc = lzjb_decompress((uint8_t *)pvSrc + 1, pvDst, cbSrc - 1, cbDst, 0 /*??*/);
+ if (RT_UNLIKELY(rc != 0))
+ return VERR_GENERAL_FAILURE;
+ if (pcbDstActual)
+ *pcbDstActual = cbDst;
+ }
+ else
+ {
+ AssertReturn(cbDst >= cbSrc - 1, VERR_BUFFER_OVERFLOW);
+ memcpy(pvDst, (uint8_t *)pvSrc + 1, cbSrc - 1);
+ if (pcbDstActual)
+ *pcbDstActual = cbSrc - 1;
+ }
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_LZO:
+ {
+#ifdef RTZIP_USE_LZO
+ int rc = lzo_init();
+ if (RT_UNLIKELY(rc != LZO_E_OK))
+ return VERR_INTERNAL_ERROR;
+ lzo_uint cbDstInOut = cbDst;
+ rc = lzo1x_decompress((const lzo_bytep)pvSrc, cbSrc, (lzo_bytep)pvDst, &cbDstInOut, NULL);
+ if (RT_UNLIKELY(rc != LZO_E_OK))
+ switch (rc)
+ {
+ case LZO_E_OUTPUT_OVERRUN: return VERR_BUFFER_OVERFLOW;
+ default:
+ case LZO_E_INPUT_OVERRUN: return VERR_GENERAL_FAILURE;
+ }
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc;
+ if (pcbDstActual)
+ *pcbDstActual = cbDstInOut;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_ZLIB:
+ case RTZIPTYPE_ZLIB_NO_HEADER:
+ {
+#ifdef RTZIP_USE_ZLIB
+ AssertReturn(cbSrc == (uInt)cbSrc, VERR_TOO_MUCH_DATA);
+ AssertReturn(cbDst == (uInt)cbDst, VERR_OUT_OF_RANGE);
+
+ z_stream ZStrm;
+ RT_ZERO(ZStrm);
+ ZStrm.next_in = (Bytef *)pvSrc;
+ ZStrm.avail_in = (uInt)cbSrc;
+ ZStrm.next_out = (Bytef *)pvDst;
+ ZStrm.avail_out = (uInt)cbDst;
+
+ int rc;
+ if (enmType == RTZIPTYPE_ZLIB)
+ rc = inflateInit(&ZStrm);
+ else if (enmType == RTZIPTYPE_ZLIB_NO_HEADER)
+ rc = inflateInit2(&ZStrm, -Z_DEF_WBITS);
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR);
+
+ if (RT_UNLIKELY(rc != Z_OK))
+ return zipErrConvertFromZlib(rc, false /*fCompressing*/);
+ rc = inflate(&ZStrm, Z_FINISH);
+ if (rc != Z_STREAM_END)
+ {
+ inflateEnd(&ZStrm);
+ if ((rc == Z_BUF_ERROR && ZStrm.avail_in == 0) || rc == Z_NEED_DICT)
+ return VERR_ZIP_CORRUPTED;
+ if (rc == Z_BUF_ERROR)
+ return VERR_BUFFER_OVERFLOW;
+ AssertReturn(rc < Z_OK, VERR_GENERAL_FAILURE);
+ return zipErrConvertFromZlib(rc, false /*fCompressing*/);
+ }
+ rc = inflateEnd(&ZStrm);
+ if (rc != Z_OK)
+ return zipErrConvertFromZlib(rc, false /*fCompressing*/);
+
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc - ZStrm.avail_in;
+ if (pcbDstActual)
+ *pcbDstActual = ZStrm.total_out;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_BZLIB:
+ return VERR_NOT_SUPPORTED;
+
+ default:
+ AssertMsgFailed(("%d\n", enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTZipBlockDecompress);
+