zipgzip.cpp revision 1e9c7d1236e5efe007e5984e75dad70243214559
/* $Id$ */
/** @file
* IPRT - GZIP Compressor and Decompressor I/O Stream.
*/
/*
* Copyright (C) 2010 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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.
*/
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
#include <iprt/vfslowlevel.h>
#include <zlib.h>
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
#pragma pack(1)
typedef struct RTZIPGZIPHDR
{
/** RTZIPGZIPHDR_ID1. */
/** RTZIPGZIPHDR_ID2. */
/** CM - The compression method. */
/** FLG - Flags. */
/** Modification time of the source file or the timestamp at the time the
* compression took place. Can also be zero. Is the number of seconds since
* unix epoch. */
/** Flags specific to the compression method. */
/** An ID indicating which OS or FS gzip ran on. */
} RTZIPGZIPHDR;
#pragma pack()
/** Pointer to a const gzip header. */
typedef RTZIPGZIPHDR const *PCRTZIPGZIPHDR;
/** gzip header identification no 1. */
#define RTZIPGZIPHDR_ID1 0x1f
/** gzip header identification no 2. */
#define RTZIPGZIPHDR_ID2 0x8b
/** gzip deflate compression method. */
#define RTZIPGZIPHDR_CM_DEFLATE 8
/** @name gzip header flags
* @{ */
/** Probably a text file */
/** Header CRC present (crc32 of header cast to uint16_t). */
/** Length prefixed xtra field is present. */
/** A name field is present (latin-1). */
/** A comment field is present (latin-1). */
/** Mask of valid flags. */
/** @} */
/** @name gzip default xtra flag values
* @{ */
/** @} */
/** @name Operating system / Filesystem IDs
* @{ */
/** @} */
/**
* The internal data of a GZIP I/O stream.
*/
typedef struct RTZIPGZIPSTREAM
{
/** The stream we're reading or writing the compressed data from or to. */
/** Set if it's a decompressor, clear if it's a compressor. */
bool fDecompress;
/** Set if zlib reported a fatal error. */
bool fFatalError;
/** Set if we've reached the end of the zlib stream. */
bool fEndOfStream;
/** The stream offset for pfnTell. */
/** The zlib stream. */
/** The data buffer. */
/** Scatter gather segment describing abBuffer. */
/** Scatter gather buffer describing abBuffer. */
/** The original file name (decompressor only). */
char *pszOrgName;
/** The comment (decompressor only). */
char *pszComment;
/** The gzip header. */
/** Pointer to a the internal data of a GZIP I/O stream. */
typedef RTZIPGZIPSTREAM *PRTZIPGZIPSTREAM;
/**
* Convert from zlib to IPRT status codes.
*
* This will also set the fFatalError flag when appropriate.
*
* @returns IPRT status code.
* @param pThis The gzip I/O stream instance data.
* @param rc Zlib error code.
*/
{
switch (rc)
{
case Z_OK:
return VINF_SUCCESS;
case Z_BUF_ERROR:
/* This isn't fatal. */
return VINF_SUCCESS;
case Z_ERRNO:
case Z_STREAM_ERROR:
case Z_DATA_ERROR:
case Z_MEM_ERROR:
case Z_VERSION_ERROR:
pThis->fFatalError = true;
switch (rc)
{
case Z_ERRNO: return VERR_INTERNAL_ERROR_5;
case Z_STREAM_ERROR: return VERR_INTERNAL_ERROR_3;
case Z_DATA_ERROR: return VERR_ZIP_ERROR;
case Z_MEM_ERROR: return VERR_ZIP_NO_MEMORY;
case Z_VERSION_ERROR: return VERR_ZIP_UNSUPPORTED_VERSION;
}
/* not reached */
default:
if (rc >= 0)
return VINF_SUCCESS;
pThis->fFatalError = true;
return VERR_ZIP_ERROR;
}
}
/**
* @interface_method_impl{RTVFSOBJOPS,pfnClose}
*/
{
int rc;
if (pThis->fDecompress)
else
return rc;
}
/**
* @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
*/
static DECLCALLBACK(int) rtZipGzip_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
{
}
/**
* Reads one segment.
*
* @returns IPRT status code.
* @param pThis The gzip I/O stream instance data.
* @param pvBuf Where to put the read bytes.
* @param cbToRead The number of bytes to read.
* @param fBlocking Whether to block or not.
* @param pcbRead Where to store the number of bytes actually read.
*/
static int rtZipGzip_ReadOneSeg(PRTZIPGZIPSTREAM pThis, void *pvBuf, size_t cbToRead, bool fBlocking, size_t *pcbRead)
{
/*
* This simplifies life a wee bit below.
*/
if (pThis->fEndOfStream)
/*
* Set up the output buffer.
*/
/*
* Be greedy reading input, even if no output buffer is left. It's possible
* that it's just the end of stream marker which needs to be read. Happens
* for incompressible blocks just larger than the input buffer size.
*/
int rc = VINF_SUCCESS;
{
/*
* Read more input?
*
* N.B. The assertions here validate the RTVfsIoStrmSgRead behavior
* since the API is new and untested. They could be removed later
* but, better leaving them in.
*/
{
if (rc != VINF_SUCCESS)
{
if (rc == VERR_INTERRUPTED)
{
continue;
}
{
break;
}
}
AssertMsgBreakStmt(cbReadIn > 0 && cbReadIn <= sizeof(pThis->abBuffer), ("%zu %Rrc\n", cbReadIn, rc),
}
/*
* Pass it on to zlib.
*/
{
if (rc == Z_STREAM_END)
{
pThis->fEndOfStream = true;
rc = VINF_SUCCESS;
else
}
else
break;
}
rc = VINF_SUCCESS;
}
/*
* Update the read counters before returning.
*/
if (pcbRead)
return rc;
}
/**
* @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
*/
static DECLCALLBACK(int) rtZipGzip_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
{
int rc;
if (!pThis->fDecompress)
return VERR_ACCESS_DENIED;
rc = rtZipGzip_ReadOneSeg(pThis, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, fBlocking, pcbRead);
else
{
rc = VINF_SUCCESS;
{
cbReadSeg = 0;
rc = rtZipGzip_ReadOneSeg(pThis, pSgBuf->paSegs[iSeg].pvSeg, pSgBuf->paSegs[iSeg].cbSeg, fBlocking, pcbReadSeg);
if (RT_FAILURE(rc))
break;
if (pcbRead)
{
break;
}
}
if (pcbRead)
}
return rc;
}
/**
* @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
*/
static DECLCALLBACK(int) rtZipGzip_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
{
//int rc;
if (pThis->fDecompress)
return VERR_ACCESS_DENIED;
/** @todo implement compression. */
return VERR_NOT_IMPLEMENTED;
}
/**
* @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
*/
{
}
/**
* @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
*/
static DECLCALLBACK(int) rtZipGzip_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
{
/*
* Collect our own events first and see if that satisfies the request. If
* not forward the call to the compressed stream.
*/
uint32_t fRetEvents = 0;
if (pThis->fFatalError)
if (pThis->fDecompress)
{
fEvents &= ~RTPOLL_EVT_WRITE;
}
else
{
fEvents &= ~RTPOLL_EVT_READ;
}
int rc = VINF_SUCCESS;
fRetEvents &= fEvents;
if (!fRetEvents)
return rc;
}
/**
* @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
*/
{
}
/**
* The GZIP I/O stream vtable.
*/
static RTVFSIOSTREAMOPS g_rtZipGzipOps =
{
{ /* Obj */
"gzip",
},
0,
NULL /* Skip */,
NULL /*ZeroFill*/,
};
RTDECL(int) RTZipGzipDecompressIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSIOSTREAM phVfsIosOut)
{
/*
* Create the decompression I/O stream.
*/
int rc = RTVfsNewIoStream(&g_rtZipGzipOps, sizeof(RTZIPGZIPSTREAM), RTFILE_O_READ, NIL_RTVFS, NIL_RTSEMRW,
if (RT_SUCCESS(rc))
{
pThis->fDecompress = true;
if (rc >= 0)
{
/*
* Read the gzip header from the input stream to check that it's
* a gzip stream.
*
* Note!. Since we've told zlib to check for the gzip header, we
* prebuffer what we read in the input buffer so it can
* be handed on to zlib later on.
*/
if (RT_SUCCESS(rc))
{
/* Validate the header and make a copy of it. */
else
{
/* Parse on if there are names or comments. */
{
/** @todo Can implement this when someone needs the
* name or comment for something useful. */
}
if (RT_SUCCESS(rc))
{
*phVfsIosOut = hVfsIos;
return VINF_SUCCESS;
}
}
}
}
else
}
return rc;
}