VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/zip/gzipvfs.cpp@ 39049

Last change on this file since 39049 was 39049, checked in by vboxsync, 13 years ago

IPRT: Implemented / adjusted zlib status code conversion.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.9 KB
Line 
1/* $Id: gzipvfs.cpp 39049 2011-10-20 09:39:22Z vboxsync $ */
2/** @file
3 * IPRT - GZIP Compressor and Decompressor I/O Stream.
4 */
5
6/*
7 * Copyright (C) 2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*******************************************************************************
29* Defined Constants And Macros *
30*******************************************************************************/
31#include "internal/iprt.h"
32#include <iprt/zip.h>
33
34#include <iprt/assert.h>
35#include <iprt/file.h>
36#include <iprt/err.h>
37#include <iprt/poll.h>
38#include <iprt/string.h>
39#include <iprt/vfslowlevel.h>
40
41#include <zlib.h>
42
43#if defined(RT_OS_OS2) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS)
44/**
45 * Drag in the missing zlib symbols.
46 */
47PFNRT g_apfnRTZlibDeps[] =
48{
49 (PFNRT)gzrewind,
50 (PFNRT)gzread,
51 (PFNRT)gzopen,
52 (PFNRT)gzwrite,
53 (PFNRT)gzclose,
54 (PFNRT)gzdopen,
55 NULL
56};
57#endif /* RT_OS_OS2 || RT_OS_SOLARIS || RT_OS_WINDOWS */
58
59/*******************************************************************************
60* Structures and Typedefs *
61*******************************************************************************/
62#pragma pack(1)
63typedef struct RTZIPGZIPHDR
64{
65 /** RTZIPGZIPHDR_ID1. */
66 uint8_t bId1;
67 /** RTZIPGZIPHDR_ID2. */
68 uint8_t bId2;
69 /** CM - The compression method. */
70 uint8_t bCompressionMethod;
71 /** FLG - Flags. */
72 uint8_t fFlags;
73 /** Modification time of the source file or the timestamp at the time the
74 * compression took place. Can also be zero. Is the number of seconds since
75 * unix epoch. */
76 uint32_t u32ModTime;
77 /** Flags specific to the compression method. */
78 uint8_t bXtraFlags;
79 /** An ID indicating which OS or FS gzip ran on. */
80 uint8_t bOS;
81} RTZIPGZIPHDR;
82#pragma pack()
83AssertCompileSize(RTZIPGZIPHDR, 10);
84/** Pointer to a const gzip header. */
85typedef RTZIPGZIPHDR const *PCRTZIPGZIPHDR;
86
87/** gzip header identification no 1. */
88#define RTZIPGZIPHDR_ID1 0x1f
89/** gzip header identification no 2. */
90#define RTZIPGZIPHDR_ID2 0x8b
91/** gzip deflate compression method. */
92#define RTZIPGZIPHDR_CM_DEFLATE 8
93
94/** @name gzip header flags
95 * @{ */
96/** Probably a text file */
97#define RTZIPGZIPHDR_FLG_TEXT UINT8_C(0x01)
98/** Header CRC present (crc32 of header cast to uint16_t). */
99#define RTZIPGZIPHDR_FLG_HDR_CRC UINT8_C(0x02)
100/** Length prefixed xtra field is present. */
101#define RTZIPGZIPHDR_FLG_EXTRA UINT8_C(0x04)
102/** A name field is present (latin-1). */
103#define RTZIPGZIPHDR_FLG_NAME UINT8_C(0x08)
104/** A comment field is present (latin-1). */
105#define RTZIPGZIPHDR_FLG_COMMENT UINT8_C(0x10)
106/** Mask of valid flags. */
107#define RTZIPGZIPHDR_FLG_VALID_MASK UINT8_C(0x1f)
108/** @} */
109
110/** @name gzip default xtra flag values
111 * @{ */
112#define RTZIPGZIPHDR_XFL_DEFLATE_MAX UINT8_C(0x02)
113#define RTZIPGZIPHDR_XFL_DEFLATE_FASTEST UINT8_C(0x04)
114/** @} */
115
116/** @name Operating system / Filesystem IDs
117 * @{ */
118#define RTZIPGZIPHDR_OS_FAT UINT8_C(0x00)
119#define RTZIPGZIPHDR_OS_AMIGA UINT8_C(0x01)
120#define RTZIPGZIPHDR_OS_VMS UINT8_C(0x02)
121#define RTZIPGZIPHDR_OS_UNIX UINT8_C(0x03)
122#define RTZIPGZIPHDR_OS_VM_CMS UINT8_C(0x04)
123#define RTZIPGZIPHDR_OS_ATARIS_TOS UINT8_C(0x05)
124#define RTZIPGZIPHDR_OS_HPFS UINT8_C(0x06)
125#define RTZIPGZIPHDR_OS_MACINTOSH UINT8_C(0x07)
126#define RTZIPGZIPHDR_OS_Z_SYSTEM UINT8_C(0x08)
127#define RTZIPGZIPHDR_OS_CPM UINT8_C(0x09)
128#define RTZIPGZIPHDR_OS_TOPS_20 UINT8_C(0x0a)
129#define RTZIPGZIPHDR_OS_NTFS UINT8_C(0x0b)
130#define RTZIPGZIPHDR_OS_QDOS UINT8_C(0x0c)
131#define RTZIPGZIPHDR_OS_ACORN_RISCOS UINT8_C(0x0d)
132#define RTZIPGZIPHDR_OS_UNKNOWN UINT8_C(0xff)
133/** @} */
134
135
136/**
137 * The internal data of a GZIP I/O stream.
138 */
139typedef struct RTZIPGZIPSTREAM
140{
141 /** The stream we're reading or writing the compressed data from or to. */
142 RTVFSIOSTREAM hVfsIos;
143 /** Set if it's a decompressor, clear if it's a compressor. */
144 bool fDecompress;
145 /** Set if zlib reported a fatal error. */
146 bool fFatalError;
147 /** Set if we've reached the end of the zlib stream. */
148 bool fEndOfStream;
149 /** The stream offset for pfnTell. */
150 RTFOFF offStream;
151 /** The zlib stream. */
152 z_stream Zlib;
153 /** The data buffer. */
154 uint8_t abBuffer[_64K];
155 /** Scatter gather segment describing abBuffer. */
156 RTSGSEG SgSeg;
157 /** Scatter gather buffer describing abBuffer. */
158 RTSGBUF SgBuf;
159 /** The original file name (decompressor only). */
160 char *pszOrgName;
161 /** The comment (decompressor only). */
162 char *pszComment;
163 /** The gzip header. */
164 RTZIPGZIPHDR Hdr;
165} RTZIPGZIPSTREAM;
166/** Pointer to a the internal data of a GZIP I/O stream. */
167typedef RTZIPGZIPSTREAM *PRTZIPGZIPSTREAM;
168
169
170/**
171 * Convert from zlib to IPRT status codes.
172 *
173 * This will also set the fFatalError flag when appropriate.
174 *
175 * @returns IPRT status code.
176 * @param pThis The gzip I/O stream instance data.
177 * @param rc Zlib error code.
178 */
179static int rtZipGzipConvertErrFromZlib(PRTZIPGZIPSTREAM pThis, int rc)
180{
181 switch (rc)
182 {
183 case Z_OK:
184 return VINF_SUCCESS;
185
186 case Z_BUF_ERROR:
187 /* This isn't fatal. */
188 return VINF_SUCCESS; /** @todo The code in zip.cpp treats Z_BUF_ERROR as fatal... */
189
190 case Z_STREAM_ERROR:
191 pThis->fFatalError = true;
192 return VERR_ZIP_CORRUPTED;
193
194 case Z_DATA_ERROR:
195 pThis->fFatalError = true;
196 return pThis->fDecompress ? VERR_ZIP_CORRUPTED : VERR_ZIP_ERROR;
197
198 case Z_MEM_ERROR:
199 pThis->fFatalError = true;
200 return VERR_ZIP_NO_MEMORY;
201
202 case Z_VERSION_ERROR:
203 pThis->fFatalError = true;
204 return VERR_ZIP_UNSUPPORTED_VERSION;
205
206 case Z_ERRNO: /* We shouldn't see this status! */
207 default:
208 AssertMsgFailed(("%d\n", rc));
209 if (rc >= 0)
210 return VINF_SUCCESS;
211 pThis->fFatalError = true;
212 return VERR_ZIP_ERROR;
213 }
214}
215
216
217/**
218 * @interface_method_impl{RTVFSOBJOPS,pfnClose}
219 */
220static DECLCALLBACK(int) rtZipGzip_Close(void *pvThis)
221{
222 PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
223
224 int rc;
225 if (pThis->fDecompress)
226 rc = inflateEnd(&pThis->Zlib);
227 else
228 rc = deflateEnd(&pThis->Zlib);
229 if (rc != Z_OK)
230 rc = rtZipGzipConvertErrFromZlib(pThis, rc);
231
232 RTVfsIoStrmRelease(pThis->hVfsIos);
233 pThis->hVfsIos = NIL_RTVFSIOSTREAM;
234 RTStrFree(pThis->pszOrgName);
235 pThis->pszOrgName = NULL;
236 RTStrFree(pThis->pszComment);
237 pThis->pszComment = NULL;
238
239 return rc;
240}
241
242
243/**
244 * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
245 */
246static DECLCALLBACK(int) rtZipGzip_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
247{
248 PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
249 return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
250}
251
252
253/**
254 * Reads one segment.
255 *
256 * @returns IPRT status code.
257 * @param pThis The gzip I/O stream instance data.
258 * @param pvBuf Where to put the read bytes.
259 * @param cbToRead The number of bytes to read.
260 * @param fBlocking Whether to block or not.
261 * @param pcbRead Where to store the number of bytes actually read.
262 */
263static int rtZipGzip_ReadOneSeg(PRTZIPGZIPSTREAM pThis, void *pvBuf, size_t cbToRead, bool fBlocking, size_t *pcbRead)
264{
265 /*
266 * This simplifies life a wee bit below.
267 */
268 if (pThis->fEndOfStream)
269 return pcbRead ? VINF_EOF : VERR_EOF;
270
271 /*
272 * Set up the output buffer.
273 */
274 pThis->Zlib.next_out = (Bytef *)pvBuf;
275 pThis->Zlib.avail_out = (uInt)cbToRead;
276 AssertReturn(pThis->Zlib.avail_out == cbToRead, VERR_OUT_OF_RANGE);
277
278 /*
279 * Be greedy reading input, even if no output buffer is left. It's possible
280 * that it's just the end of stream marker which needs to be read. Happens
281 * for incompressible blocks just larger than the input buffer size.
282 */
283 int rc = VINF_SUCCESS;
284 while ( pThis->Zlib.avail_out > 0
285 || pThis->Zlib.avail_in == 0 /* greedy */)
286 {
287 /*
288 * Read more input?
289 *
290 * N.B. The assertions here validate the RTVfsIoStrmSgRead behavior
291 * since the API is new and untested. They could be removed later
292 * but, better leaving them in.
293 */
294 if (pThis->Zlib.avail_in == 0)
295 {
296 size_t cbReadIn = ~(size_t)0;
297 rc = RTVfsIoStrmSgRead(pThis->hVfsIos, &pThis->SgBuf, fBlocking, &cbReadIn);
298 if (rc != VINF_SUCCESS)
299 {
300 AssertMsg(RT_FAILURE(rc) || rc == VINF_TRY_AGAIN || rc == VINF_EOF, ("%Rrc\n", rc));
301 if (rc == VERR_INTERRUPTED)
302 {
303 Assert(cbReadIn == 0);
304 continue;
305 }
306 if (RT_FAILURE(rc) || rc == VINF_TRY_AGAIN || cbReadIn == 0)
307 {
308 Assert(cbReadIn == 0);
309 break;
310 }
311 AssertMsg(rc == VINF_EOF, ("%Rrc\n", rc));
312 }
313 AssertMsgBreakStmt(cbReadIn > 0 && cbReadIn <= sizeof(pThis->abBuffer), ("%zu %Rrc\n", cbReadIn, rc),
314 rc = VERR_INTERNAL_ERROR_4);
315
316 pThis->Zlib.avail_in = (uInt)cbReadIn;
317 pThis->Zlib.next_in = &pThis->abBuffer[0];
318 }
319
320 /*
321 * Pass it on to zlib.
322 */
323 rc = inflate(&pThis->Zlib, Z_NO_FLUSH);
324 if (rc != Z_OK && rc != Z_BUF_ERROR)
325 {
326 if (rc == Z_STREAM_END)
327 {
328 pThis->fEndOfStream = true;
329 if (pThis->Zlib.avail_out == 0)
330 rc = VINF_SUCCESS;
331 else
332 rc = pcbRead ? VINF_EOF : VERR_EOF;
333 }
334 else
335 rc = rtZipGzipConvertErrFromZlib(pThis, rc);
336 break;
337 }
338 rc = VINF_SUCCESS;
339 }
340
341 /*
342 * Update the read counters before returning.
343 */
344 size_t const cbRead = cbToRead - pThis->Zlib.avail_out;
345 pThis->offStream += cbRead;
346 if (pcbRead)
347 *pcbRead = cbRead;
348
349 return rc;
350}
351
352/**
353 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
354 */
355static DECLCALLBACK(int) rtZipGzip_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
356{
357 PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
358 int rc;
359
360 if (!pThis->fDecompress)
361 return VERR_ACCESS_DENIED;
362
363 if (pSgBuf->cSegs == 1)
364 rc = rtZipGzip_ReadOneSeg(pThis, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, fBlocking, pcbRead);
365 else
366 {
367 rc = VINF_SUCCESS;
368 size_t cbRead = 0;
369 size_t cbReadSeg;
370 size_t *pcbReadSeg = pcbRead ? &cbReadSeg : NULL;
371 for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
372 {
373 cbReadSeg = 0;
374 rc = rtZipGzip_ReadOneSeg(pThis, pSgBuf->paSegs[iSeg].pvSeg, pSgBuf->paSegs[iSeg].cbSeg, fBlocking, pcbReadSeg);
375 if (RT_FAILURE(rc))
376 break;
377 if (pcbRead)
378 {
379 cbRead += cbReadSeg;
380 if (cbReadSeg != pSgBuf->paSegs[iSeg].cbSeg)
381 break;
382 }
383 }
384 if (pcbRead)
385 *pcbRead = cbRead;
386 }
387
388 return rc;
389}
390
391
392/**
393 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
394 */
395static DECLCALLBACK(int) rtZipGzip_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
396{
397 PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
398 //int rc;
399
400 NOREF(fBlocking);
401 if (pThis->fDecompress)
402 return VERR_ACCESS_DENIED;
403
404 /** @todo implement compression. */
405 return VERR_NOT_IMPLEMENTED;
406}
407
408
409/**
410 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
411 */
412static DECLCALLBACK(int) rtZipGzip_Flush(void *pvThis)
413{
414 PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
415 return RTVfsIoStrmFlush(pThis->hVfsIos);
416}
417
418
419/**
420 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
421 */
422static DECLCALLBACK(int) rtZipGzip_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
423 uint32_t *pfRetEvents)
424{
425 PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
426
427 /*
428 * Collect our own events first and see if that satisfies the request. If
429 * not forward the call to the compressed stream.
430 */
431 uint32_t fRetEvents = 0;
432 if (pThis->fFatalError)
433 fRetEvents |= RTPOLL_EVT_ERROR;
434 if (pThis->fDecompress)
435 {
436 fEvents &= ~RTPOLL_EVT_WRITE;
437 if (pThis->Zlib.avail_in > 0)
438 fRetEvents = RTPOLL_EVT_READ;
439 }
440 else
441 {
442 fEvents &= ~RTPOLL_EVT_READ;
443 if (pThis->Zlib.avail_out > 0)
444 fRetEvents = RTPOLL_EVT_WRITE;
445 }
446
447 int rc = VINF_SUCCESS;
448 fRetEvents &= fEvents;
449 if (!fRetEvents)
450 rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
451 return rc;
452}
453
454
455/**
456 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
457 */
458static DECLCALLBACK(int) rtZipGzip_Tell(void *pvThis, PRTFOFF poffActual)
459{
460 PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
461 *poffActual = pThis->offStream;
462 return VINF_SUCCESS;
463}
464
465
466/**
467 * The GZIP I/O stream vtable.
468 */
469static RTVFSIOSTREAMOPS g_rtZipGzipOps =
470{
471 { /* Obj */
472 RTVFSOBJOPS_VERSION,
473 RTVFSOBJTYPE_IO_STREAM,
474 "gzip",
475 rtZipGzip_Close,
476 rtZipGzip_QueryInfo,
477 RTVFSOBJOPS_VERSION
478 },
479 RTVFSIOSTREAMOPS_VERSION,
480 0,
481 rtZipGzip_Read,
482 rtZipGzip_Write,
483 rtZipGzip_Flush,
484 rtZipGzip_PollOne,
485 rtZipGzip_Tell,
486 NULL /* Skip */,
487 NULL /*ZeroFill*/,
488 RTVFSIOSTREAMOPS_VERSION,
489};
490
491
492RTDECL(int) RTZipGzipDecompressIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSIOSTREAM phVfsIosOut)
493{
494 AssertPtrReturn(hVfsIosIn, VERR_INVALID_HANDLE);
495 AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
496 AssertPtrReturn(phVfsIosOut, VERR_INVALID_POINTER);
497
498 uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn);
499 AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
500
501 /*
502 * Create the decompression I/O stream.
503 */
504 RTVFSIOSTREAM hVfsIos;
505 PRTZIPGZIPSTREAM pThis;
506 int rc = RTVfsNewIoStream(&g_rtZipGzipOps, sizeof(RTZIPGZIPSTREAM), RTFILE_O_READ, NIL_RTVFS, NIL_RTVFSLOCK,
507 &hVfsIos, (void **)&pThis);
508 if (RT_SUCCESS(rc))
509 {
510 pThis->hVfsIos = hVfsIosIn;
511 pThis->offStream = 0;
512 pThis->fDecompress = true;
513 pThis->SgSeg.pvSeg = &pThis->abBuffer[0];
514 pThis->SgSeg.cbSeg = sizeof(pThis->abBuffer);
515 RTSgBufInit(&pThis->SgBuf, &pThis->SgSeg, 1);
516
517 memset(&pThis->Zlib, 0, sizeof(pThis->Zlib));
518 pThis->Zlib.opaque = pThis;
519 rc = inflateInit2(&pThis->Zlib, MAX_WBITS + 16 /* autodetect gzip header */);
520 if (rc >= 0)
521 {
522 /*
523 * Read the gzip header from the input stream to check that it's
524 * a gzip stream.
525 *
526 * Note!. Since we've told zlib to check for the gzip header, we
527 * prebuffer what we read in the input buffer so it can
528 * be handed on to zlib later on.
529 */
530 rc = RTVfsIoStrmRead(pThis->hVfsIos, pThis->abBuffer, sizeof(RTZIPGZIPHDR), true /*fBlocking*/, NULL /*pcbRead*/);
531 if (RT_SUCCESS(rc))
532 {
533 /* Validate the header and make a copy of it. */
534 PCRTZIPGZIPHDR pHdr = (PCRTZIPGZIPHDR)pThis->abBuffer;
535 if ( pHdr->bId1 != RTZIPGZIPHDR_ID1
536 || pHdr->bId2 != RTZIPGZIPHDR_ID2
537 || pHdr->fFlags & ~RTZIPGZIPHDR_FLG_VALID_MASK)
538 rc = VERR_ZIP_BAD_HEADER;
539 else if (pHdr->bCompressionMethod != RTZIPGZIPHDR_CM_DEFLATE)
540 rc = VERR_ZIP_UNSUPPORTED_METHOD;
541 else
542 {
543 pThis->Hdr = *pHdr;
544 pThis->Zlib.avail_in = sizeof(RTZIPGZIPHDR);
545 pThis->Zlib.next_in = &pThis->abBuffer[0];
546
547 /* Parse on if there are names or comments. */
548 if (pHdr->fFlags & (RTZIPGZIPHDR_FLG_NAME | RTZIPGZIPHDR_FLG_COMMENT))
549 {
550 /** @todo Can implement this when someone needs the
551 * name or comment for something useful. */
552 }
553 if (RT_SUCCESS(rc))
554 {
555 *phVfsIosOut = hVfsIos;
556 return VINF_SUCCESS;
557 }
558 }
559 }
560 }
561 else
562 rc = rtZipGzipConvertErrFromZlib(pThis, rc); /** @todo cleaning up in this situation is going to go wrong. */
563 RTVfsIoStrmRelease(hVfsIos);
564 }
565 else
566 RTVfsIoStrmRelease(hVfsIosIn);
567 return rc;
568}
569
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette