diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Runtime/common/zip/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/cpiovfs.cpp | 1146 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/cpiovfsreader.h | 169 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/gzipcmd.cpp | 606 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/gzipvfs.cpp | 1035 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/pkzip.cpp | 259 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/pkzipvfs.cpp | 1296 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/tar.h | 146 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/tarcmd.cpp | 1960 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/tarvfs.cpp | 1477 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/tarvfsreader.h | 179 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/tarvfswriter.cpp | 2363 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/unzipcmd.cpp | 480 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/xarvfs.cpp | 2156 | ||||
-rw-r--r-- | src/VBox/Runtime/common/zip/zip.cpp | 2016 |
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); + |