/* $Id$ */
/** @file
* IPRT - TAR Virtual Filesystem.
*/
/*
* Copyright (C) 2010-2011 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/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 "tar.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* 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. */
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. */
RTFOFF offHdr;
/** 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 the 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;
/** 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;
/**
* 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.
*/
int64_t i64 = !(0x40 & *puchField) ? 0 : -1;
i64 = (i64 << 6) | (*puchField & 0x3f);
cchField--;
puchField++;
/*
* The remaining bytes are used in full.
*/
while (cchField-- > 0)
{
if (RT_UNLIKELY(i64 > INT64_MAX / 256))
return VERR_TAR_NUM_VALUE_TOO_LARGE;
if (RT_UNLIKELY(i64 < INT64_MIN / 256))
return VERR_TAR_NUM_VALUE_TOO_LARGE;
i64 = (i64 << 8) | *puchField++;
}
*pi64 = i64;
}
return VINF_SUCCESS;
}
/**
* 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.
*/
static 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;
}
/**
* 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;
/*
* 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 (ASMMemIsAllU32(pHdr, sizeof(*pHdr), 0) != NULL)
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 */
}
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)
{
/* Turns out our own tar writer code doesn't get this crap right.
Kludge our way around it. */
if (!pThis->cZeroHdrs)
return pThis->enmPrevType == RTZIPTARTYPE_GNU ? true /* IPRT tar.cpp */ : false;
/* Here is a kludge to try deal with archivers not putting at least two
zero headers at the end. Afraid it may require further relaxing
later on, but let's try be strict about things for now. */
return pThis->cZeroHdrs >= (pThis->enmPrevType == RTZIPTARTYPE_POSIX ? 2U : 1U);
}
/**
* 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,
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 - pThis->offFile);
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,
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,
RTVFSOBJOPS_VERSION
},
RTVFSSYMLINKOPS_VERSION,
0,
{ /* ObjSet */
RTVFSOBJSETOPS_VERSION,
RT_OFFSETOF(RTVFSSYMLINKOPS, Obj) - RT_OFFSETOF(RTVFSSYMLINKOPS, ObjSet),
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}
*/
static DECLCALLBACK(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;
/*
* 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.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->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->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,
RTVFSOBJOPS_VERSION
},
RTVFSFSSTREAMOPS_VERSION,
0,
rtZipTarFss_Next,
RTVFSFSSTREAMOPS_VERSION
};
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, &hVfsFss, (void **)&pThis);
if (RT_SUCCESS(rc))
{
pThis->hVfsIos = hVfsIosIn;
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. */
*phVfsFss = hVfsFss;
return VINF_SUCCESS;
}
RTVfsIoStrmRelease(hVfsIosIn);
return rc;
}