diff options
Diffstat (limited to 'src/VBox/Runtime/common/zip/gzipvfs.cpp')
-rw-r--r-- | src/VBox/Runtime/common/zip/gzipvfs.cpp | 348 |
1 files changed, 304 insertions, 44 deletions
diff --git a/src/VBox/Runtime/common/zip/gzipvfs.cpp b/src/VBox/Runtime/common/zip/gzipvfs.cpp index d6b952ca..87efeb1b 100644 --- a/src/VBox/Runtime/common/zip/gzipvfs.cpp +++ b/src/VBox/Runtime/common/zip/gzipvfs.cpp @@ -56,6 +56,7 @@ PFNRT g_apfnRTZlibDeps[] = }; #endif /* RT_OS_OS2 || RT_OS_SOLARIS || RT_OS_WINDOWS */ + /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ @@ -167,6 +168,12 @@ typedef struct RTZIPGZIPSTREAM typedef RTZIPGZIPSTREAM *PRTZIPGZIPSTREAM; +/******************************************************************************* +* Internal Functions * +*******************************************************************************/ +static int rtZipGzip_FlushIt(PRTZIPGZIPSTREAM pThis, uint8_t fFlushType); + + /** * Convert from zlib to IPRT status codes. * @@ -223,11 +230,22 @@ static DECLCALLBACK(int) rtZipGzip_Close(void *pvThis) int rc; if (pThis->fDecompress) + { rc = inflateEnd(&pThis->Zlib); + if (rc != Z_OK) + rc = rtZipGzipConvertErrFromZlib(pThis, rc); + } else - rc = deflateEnd(&pThis->Zlib); - if (rc != Z_OK) - rc = rtZipGzipConvertErrFromZlib(pThis, rc); + { + /* Flush the compression stream before terminating it. */ + rc = VINF_SUCCESS; + if (!pThis->fFatalError) + rc = rtZipGzip_FlushIt(pThis, Z_FINISH); + + int rc2 = deflateEnd(&pThis->Zlib); + if (RT_SUCCESS(rc) && rc2 != Z_OK) + rc = rtZipGzipConvertErrFromZlib(pThis, rc); + } RTVfsIoStrmRelease(pThis->hVfsIos); pThis->hVfsIos = NIL_RTVFSIOSTREAM; @@ -349,44 +367,138 @@ static int rtZipGzip_ReadOneSeg(PRTZIPGZIPSTREAM pThis, void *pvBuf, size_t cbTo return rc; } + /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} */ static DECLCALLBACK(int) rtZipGzip_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) { PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis; - int rc; + Assert(pSgBuf->cSegs == 1); AssertReturn(off == -1, VERR_INVALID_PARAMETER); if (!pThis->fDecompress) return VERR_ACCESS_DENIED; - if (pSgBuf->cSegs == 1) - rc = rtZipGzip_ReadOneSeg(pThis, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, fBlocking, pcbRead); - else + return rtZipGzip_ReadOneSeg(pThis, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, fBlocking, pcbRead); +} + + +/** + * Internal helper for rtZipGzip_Write, rtZipGzip_Flush and rtZipGzip_Close. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS + * @retval VINF_TRY_AGAIN - the only informational status. + * @retval VERR_INTERRUPTED - call again. + * + * @param pThis The gzip I/O stream instance data. + * @param fBlocking Whether to block or not. + */ +static int rtZipGzip_WriteOutputBuffer(PRTZIPGZIPSTREAM pThis, bool fBlocking) +{ + /* + * Anything to write? No, then just return immediately. + */ + size_t cbToWrite = sizeof(pThis->abBuffer) - pThis->Zlib.avail_out; + if (cbToWrite == 0) { - rc = VINF_SUCCESS; - size_t cbRead = 0; - size_t cbReadSeg; - size_t *pcbReadSeg = pcbRead ? &cbReadSeg : NULL; - for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++) + Assert(pThis->Zlib.next_out == &pThis->abBuffer[0]); + return VINF_SUCCESS; + } + Assert(cbToWrite <= sizeof(pThis->abBuffer)); + + /* + * Loop write on VERR_INTERRUPTED. + * + * Note! Asserting a bit extra here to make sure the + * RTVfsIoStrmSgWrite works correctly. + */ + int rc; + size_t cbWrittenOut; + for (;;) + { + /* Set up the buffer. */ + pThis->SgSeg.cbSeg = cbToWrite; + Assert(pThis->SgSeg.pvSeg == &pThis->abBuffer[0]); + RTSgBufReset(&pThis->SgBuf); + + cbWrittenOut = ~(size_t)0; + rc = RTVfsIoStrmSgWrite(pThis->hVfsIos, &pThis->SgBuf, fBlocking, &cbWrittenOut); + if (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) + AssertMsg(RT_FAILURE(rc) || rc == VINF_TRY_AGAIN, ("%Rrc\n", rc)); + if (rc == VERR_INTERRUPTED) { - cbRead += cbReadSeg; - if (cbReadSeg != pSgBuf->paSegs[iSeg].cbSeg) - break; + Assert(cbWrittenOut == 0); + continue; + } + if (RT_FAILURE(rc) || rc == VINF_TRY_AGAIN || cbWrittenOut == 0) + { + AssertReturn(cbWrittenOut == 0, VERR_INTERNAL_ERROR_3); + AssertReturn(rc != VINF_SUCCESS, VERR_IPE_UNEXPECTED_INFO_STATUS); + return rc; } } - if (pcbRead) - *pcbRead = cbRead; + break; } + AssertMsgReturn(cbWrittenOut > 0 && cbWrittenOut <= sizeof(pThis->abBuffer), + ("%zu %Rrc\n", cbWrittenOut, rc), + VERR_INTERNAL_ERROR_4); - return rc; + /* + * Adjust the Zlib output buffer members. + */ + if (cbWrittenOut == pThis->SgBuf.paSegs[0].cbSeg) + { + pThis->Zlib.avail_out = sizeof(pThis->abBuffer); + pThis->Zlib.next_out = &pThis->abBuffer[0]; + } + else + { + Assert(cbWrittenOut <= pThis->SgBuf.paSegs[0].cbSeg); + size_t cbLeft = pThis->SgBuf.paSegs[0].cbSeg - cbWrittenOut; + memmove(&pThis->abBuffer[0], &pThis->abBuffer[cbWrittenOut], cbLeft); + pThis->Zlib.avail_out += (uInt)cbWrittenOut; + pThis->Zlib.next_out = &pThis->abBuffer[cbWrittenOut]; + } + + return VINF_SUCCESS; +} + + +/** + * Processes all available input. + * + * @returns IPRT status code. + * + * @param pThis The gzip I/O stream instance data. + * @param fBlocking Whether to block or not. + */ +static int rtZipGzip_CompressIt(PRTZIPGZIPSTREAM pThis, bool fBlocking) +{ + /* + * Processes all the intput currently lined up for us. + */ + while (pThis->Zlib.avail_in > 0) + { + /* Make sure there is some space in the output buffer before calling + deflate() so we don't waste time filling up the corners. */ + static const size_t s_cbFlushThreshold = 4096; + AssertCompile(sizeof(pThis->abBuffer) >= s_cbFlushThreshold * 4); + if (pThis->Zlib.avail_out < s_cbFlushThreshold) + { + int rc = rtZipGzip_WriteOutputBuffer(pThis, fBlocking); + if (rc != VINF_SUCCESS) + return rc; + Assert(pThis->Zlib.avail_out >= s_cbFlushThreshold); + } + + int rcZlib = deflate(&pThis->Zlib, Z_NO_FLUSH); + if (rcZlib != Z_OK) + return rtZipGzipConvertErrFromZlib(pThis, rcZlib); + } + return VINF_SUCCESS; } @@ -396,16 +508,87 @@ static DECLCALLBACK(int) rtZipGzip_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgB static DECLCALLBACK(int) rtZipGzip_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) { PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis; - //int rc; AssertReturn(off == -1, VERR_INVALID_PARAMETER); - NOREF(fBlocking); + Assert(pSgBuf->cSegs == 1); NOREF(fBlocking); + if (pThis->fDecompress) return VERR_ACCESS_DENIED; - /** @todo implement compression. */ - NOREF(pSgBuf); NOREF(pcbWritten); - return VERR_NOT_IMPLEMENTED; + /* + * Write out the intput buffer. Using a loop here because of potential + * integer type overflow since avail_in is uInt and cbSeg is size_t. + */ + int rc = VINF_SUCCESS; + size_t cbWritten = 0; + uint8_t const *pbSrc = (uint8_t const *)pSgBuf->paSegs[0].pvSeg; + size_t cbLeft = pSgBuf->paSegs[0].cbSeg; + if (cbLeft > 0) + for (;;) + { + size_t cbThis = cbLeft < ~(uInt)0 ? cbLeft : ~(uInt)0 / 2; + pThis->Zlib.next_in = (Bytef * )pbSrc; + pThis->Zlib.avail_in = (uInt)cbThis; + rc = rtZipGzip_CompressIt(pThis, fBlocking); + + Assert(cbThis >= pThis->Zlib.avail_in); + cbThis -= pThis->Zlib.avail_in; + cbWritten += cbThis; + if (cbLeft == cbThis || rc != VINF_SUCCESS) + break; + pbSrc += cbThis; + cbLeft -= cbThis; + } + + if (pcbWritten) + *pcbWritten = cbWritten; + return rc; +} + + +/** + * Processes all available input. + * + * @returns IPRT status code. + * + * @param pThis The gzip I/O stream instance data. + * @param fFlushType The flush type to pass to deflate(). + */ +static int rtZipGzip_FlushIt(PRTZIPGZIPSTREAM pThis, uint8_t fFlushType) +{ + /* + * Tell Zlib to flush until it stops producing more output. + */ + int rc; + bool fMaybeMore = true; + for (;;) + { + /* Write the entire output buffer. */ + do + { + rc = rtZipGzip_WriteOutputBuffer(pThis, true /*fBlocking*/); + if (RT_FAILURE(rc)) + return rc; + Assert(rc == VINF_SUCCESS); + } while (pThis->Zlib.avail_out < sizeof(pThis->abBuffer)); + + if (!fMaybeMore) + return VINF_SUCCESS; + + /* Do the flushing. */ + pThis->Zlib.next_in = NULL; + pThis->Zlib.avail_in = 0; + int rcZlib = deflate(&pThis->Zlib, fFlushType); + if (rcZlib == Z_OK) + fMaybeMore = pThis->Zlib.avail_out < 64 || fFlushType == Z_FINISH; + else if (rcZlib == Z_STREAM_END) + fMaybeMore = false; + else + { + rtZipGzip_WriteOutputBuffer(pThis, true /*fBlocking*/); + return rtZipGzipConvertErrFromZlib(pThis, rcZlib); + } + } } @@ -415,6 +598,13 @@ static DECLCALLBACK(int) rtZipGzip_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSg static DECLCALLBACK(int) rtZipGzip_Flush(void *pvThis) { PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis; + if (!pThis->fDecompress) + { + int rc = rtZipGzip_FlushIt(pThis, Z_SYNC_FLUSH); + if (RT_FAILURE(rc)) + return rc; + } + return RTVfsIoStrmFlush(pThis->hVfsIos); } @@ -480,7 +670,7 @@ static RTVFSIOSTREAMOPS g_rtZipGzipOps = RTVFSOBJOPS_VERSION }, RTVFSIOSTREAMOPS_VERSION, - 0, + RTVFSIOSTREAMOPS_FEAT_NO_SG, rtZipGzip_Read, rtZipGzip_Write, rtZipGzip_Flush, @@ -495,7 +685,7 @@ static RTVFSIOSTREAMOPS g_rtZipGzipOps = RTDECL(int) RTZipGzipDecompressIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSIOSTREAM phVfsIosOut) { AssertPtrReturn(hVfsIosIn, VERR_INVALID_HANDLE); - AssertReturn(!fFlags, VERR_INVALID_PARAMETER); + AssertReturn(!(fFlags & ~RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR), VERR_INVALID_PARAMETER); AssertPtrReturn(phVfsIosOut, VERR_INVALID_POINTER); uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn); @@ -519,12 +709,15 @@ RTDECL(int) RTZipGzipDecompressIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags memset(&pThis->Zlib, 0, sizeof(pThis->Zlib)); pThis->Zlib.opaque = pThis; - rc = inflateInit2(&pThis->Zlib, MAX_WBITS + 16 /* autodetect gzip header */); + rc = inflateInit2(&pThis->Zlib, + fFlags & RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR + ? MAX_WBITS + : MAX_WBITS + 16 /* autodetect gzip header */); if (rc >= 0) { /* * Read the gzip header from the input stream to check that it's - * a gzip stream. + * a gzip stream as specified by the user. * * Note!. Since we've told zlib to check for the gzip header, we * prebuffer what we read in the input buffer so it can @@ -535,23 +728,37 @@ RTDECL(int) RTZipGzipDecompressIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags { /* Validate the header and make a copy of it. */ PCRTZIPGZIPHDR pHdr = (PCRTZIPGZIPHDR)pThis->abBuffer; - if ( pHdr->bId1 != RTZIPGZIPHDR_ID1 - || pHdr->bId2 != RTZIPGZIPHDR_ID2 - || pHdr->fFlags & ~RTZIPGZIPHDR_FLG_VALID_MASK) - rc = VERR_ZIP_BAD_HEADER; - else if (pHdr->bCompressionMethod != RTZIPGZIPHDR_CM_DEFLATE) - rc = VERR_ZIP_UNSUPPORTED_METHOD; + if ( pHdr->bId1 == RTZIPGZIPHDR_ID1 + && pHdr->bId2 == RTZIPGZIPHDR_ID2 + && !(pHdr->fFlags & ~RTZIPGZIPHDR_FLG_VALID_MASK)) + { + if (pHdr->bCompressionMethod == RTZIPGZIPHDR_CM_DEFLATE) + rc = VINF_SUCCESS; + else + rc = VERR_ZIP_UNSUPPORTED_METHOD; + } + else if ( (fFlags & RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR) + && (RT_MAKE_U16(pHdr->bId2, pHdr->bId1) % 31) == 0 + && (pHdr->bId1 & 0xf) == RTZIPGZIPHDR_CM_DEFLATE ) + { + pHdr = NULL; + rc = VINF_SUCCESS; + } else + rc = VERR_ZIP_BAD_HEADER; + if (RT_SUCCESS(rc)) { - pThis->Hdr = *pHdr; pThis->Zlib.avail_in = sizeof(RTZIPGZIPHDR); pThis->Zlib.next_in = &pThis->abBuffer[0]; - - /* Parse on if there are names or comments. */ - if (pHdr->fFlags & (RTZIPGZIPHDR_FLG_NAME | RTZIPGZIPHDR_FLG_COMMENT)) + if (pHdr) { - /** @todo Can implement this when someone needs the - * name or comment for something useful. */ + pThis->Hdr = *pHdr; + /* Parse on if there are names or comments. */ + if (pHdr->fFlags & (RTZIPGZIPHDR_FLG_NAME | RTZIPGZIPHDR_FLG_COMMENT)) + { + /** @todo Can implement this when someone needs the + * name or comment for something useful. */ + } } if (RT_SUCCESS(rc)) { @@ -570,3 +777,56 @@ RTDECL(int) RTZipGzipDecompressIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags return rc; } + +RTDECL(int) RTZipGzipCompressIoStream(RTVFSIOSTREAM hVfsIosDst, uint32_t fFlags, uint8_t uLevel, PRTVFSIOSTREAM phVfsIosZip) +{ + AssertPtrReturn(hVfsIosDst, VERR_INVALID_HANDLE); + AssertReturn(!fFlags, VERR_INVALID_PARAMETER); + AssertPtrReturn(phVfsIosZip, VERR_INVALID_POINTER); + AssertReturn(uLevel > 0 && uLevel <= 9, VERR_INVALID_PARAMETER); + + uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosDst); + AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); + + /* + * Create the compression I/O stream. + */ + RTVFSIOSTREAM hVfsIos; + PRTZIPGZIPSTREAM pThis; + int rc = RTVfsNewIoStream(&g_rtZipGzipOps, sizeof(RTZIPGZIPSTREAM), RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK, + &hVfsIos, (void **)&pThis); + if (RT_SUCCESS(rc)) + { + pThis->hVfsIos = hVfsIosDst; + pThis->offStream = 0; + pThis->fDecompress = false; + pThis->SgSeg.pvSeg = &pThis->abBuffer[0]; + pThis->SgSeg.cbSeg = sizeof(pThis->abBuffer); + RTSgBufInit(&pThis->SgBuf, &pThis->SgSeg, 1); + + RT_ZERO(pThis->Zlib); + pThis->Zlib.opaque = pThis; + pThis->Zlib.next_out = &pThis->abBuffer[0]; + pThis->Zlib.avail_out = sizeof(pThis->abBuffer); + + rc = deflateInit2(&pThis->Zlib, + uLevel, + Z_DEFLATED, + 15 /* Windows Size */ + 16 /* GZIP header */, + 9 /* Max memory level for optimal speed */, + Z_DEFAULT_STRATEGY); + + if (rc >= 0) + { + *phVfsIosZip = hVfsIos; + return VINF_SUCCESS; + } + + rc = rtZipGzipConvertErrFromZlib(pThis, rc); /** @todo cleaning up in this situation is going to go wrong. */ + RTVfsIoStrmRelease(hVfsIos); + } + else + RTVfsIoStrmRelease(hVfsIosDst); + return rc; +} + |