summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common/zip/tarvfswriter.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/Runtime/common/zip/tarvfswriter.cpp2137
1 files changed, 2137 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/zip/tarvfswriter.cpp b/src/VBox/Runtime/common/zip/tarvfswriter.cpp
new file mode 100644
index 00000000..e97fe272
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tarvfswriter.cpp
@@ -0,0 +1,2137 @@
+/* $Id: tarvfswriter.cpp $ */
+/** @file
+ * IPRT - TAR Virtual Filesystem, Writer.
+ */
+
+/*
+ * Copyright (C) 2010-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/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 "tar.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. */
+ 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. */
+ /** @} */
+
+
+ /** 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,
+ 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,
+ 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;
+}
+
+
+/**
+ * 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 *rtZipTarFssWrite_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 = rtZipTarFssWrite_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 = rtZipTarFssWrite_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 = rtZipTarFssWrite_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,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);
+ if (RT_FAILURE(rc))
+ return 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");
+
+ /*
+ * 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);
+ if (RT_FAILURE(rc))
+ return 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;
+ }
+
+ /*
+ * 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 (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,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ NULL,
+ 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);
+
+ 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, false /*fReadOnly*/,
+ &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;
+
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosOut);
+ 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;
+}
+