/* * mkZiplib 1.0 * ------------ * * Please see the web pages for releases and documentation. * * Author: Michael Kraus * mailto:mmg_kraus@compuserve.com * http://ourworld.compuserve.com/homepages/mmg_kraus * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted. * The author makes no representations about the suitability of this * software for any purpose. It is provided "as is" without express * or implied warranty. By use of this software the user agrees to * indemnify and hold harmless the author from any claims or * liability for loss arising out of such use. * */ /* required to build a dll using stubs. should be a compiler option */ /* #define USE_TCL_STUBS */ #include #include #include #include #include #include #include "zip.h" #include "unzip.h" #ifndef TRUE # define TRUE 1 # define FALSE 0 #endif /* copied from sun's example.c */ #ifdef __WIN32__ #if defined(__WIN32__) # define WIN32_LEAN_AND_MEAN # include # undef WIN32_LEAN_AND_MEAN # if defined(_MSC_VER) # define EXPORT(a,b) __declspec(dllexport) a b # define DllEntryPoint DllMain # else # if defined(__BORLANDC__) # define EXPORT(a,b) a _export b # else # define EXPORT(a,b) a b # endif # endif #else # define EXPORT(a,b) a b #endif EXTERN EXPORT(int,Mkziplib_Init) _ANSI_ARGS_((Tcl_Interp *interp)); EXTERN EXPORT(int,Mkziplib_SafeInit) _ANSI_ARGS_((Tcl_Interp *interp)); BOOL APIENTRY DllEntryPoint(HINSTANCE hInst, DWORD reason, LPVOID reserved) { return TRUE; } #endif /* mkZiplib version number */ #define _VERSION "1.0" /* initial buffer size for dynamic inflation */ #define _BUFLEN 32768 /* some acronyms for popular Tcl_xxx functions */ #define _NSO(pcText) Tcl_NewStringObj( pcText, -1 ) #define _SSO(pO,pcText) Tcl_SetStringObj( pO, pcText, -1 ) #define _GSO(pO) Tcl_GetStringFromObj( pO, NULL ) #define _SOL(pO,iLen) Tcl_SetObjLength( pO, iLen ) #define _NIO(iVal) Tcl_NewIntObj( iVal ) #define _SIO(pO,iVal) Tcl_SetIntObj( pO, iVal ) #define _GIO(pO,piVal) Tcl_GetIntFromObj( pI, pO, piVal ) #define _NBO(bVal) Tcl_NewBooleanObj( bVal ) #define _SBO(pO,bVal) Tcl_SetBooleanObj( pO, bVal ) #define _GBO(pO,pbVal) Tcl_GetBooleanFromObj( pI, pO, pbVal ) #define _NDO(fVal) Tcl_NewDoubleObj( fVal ) #define _SDO(pO,fVal) Tcl_SetDoubleObj( pO, fVal ) #define _GDO(pO,pfVal) Tcl_GetDoubleFromObj( pI, pO, pfVal ) #define _NAO(pcDt,iLen) Tcl_NewByteArrayObj( pcDt, iLen ) #define _SAO(pO,pcDt,iLen) Tcl_SetByteArrayObj( pO, pcDt, iLen ) #define _GAO(pO,piLen) Tcl_GetByteArrayFromObj( pO, piLen ) #define _SAL(pO,iLen) Tcl_SetByteArrayLength( pO, iLen ) #define _LOAL(pO,pNewO) Tcl_ListObjAppendList( pI, pO, pNewO ) #define _LOAE(pO,pNewO) Tcl_ListObjAppendElement( pI, pO, pNewO ) #define _LOGL(pO,piLen) Tcl_ListObjLength( pI, pO, piLen ) #define _LOGI(pO,iI,poE) Tcl_ListObjIndex( pI, pO, iI, poE ) #define _LOGE(pO,piC,ppV) Tcl_ListObjGetElements( pI, pO, piC, ppV ) #define _LORE(pO,iPos,poE) Tcl_ListObjReplace( pI, pO, iPos, 1, 1, poE ) #define _LODE(pO,iPos) Tcl_ListObjReplace( pI, pO, iPos, 1, 0, NULL ) #define _OSV(po1,po2,poV) Tcl_ObjSetVar2( pI, po1, po2, poV, TCL_LEAVE_ERR_MSG ) #define _OGV(po1,po2) Tcl_ObjGetVar2( pI, po1, po2, TCL_LEAVE_ERR_MSG ) #define _OUV(po1,po2) Tcl_UnsetVar2( pI, _GSO(po1), (po2==NULL)?NULL:_GSO(po2), TCL_LEAVE_ERR_MSG ) #define _OSVG(po1,po2,poV) Tcl_ObjSetVar2( pI, po1, po2, poV, TCL_GLOBAL_ONLY ) #define _OGVG(po1,po2) Tcl_ObjGetVar2( pI, po1, po2, TCL_GLOBAL_ONLY ) #define _OUVG(po1,po2) Tcl_UnsetVar2( pI, _GSO(po1), (po2==NULL)?NULL:_GSO(po2), TCL_GLOBAL_ONLY ) #define _NOB Tcl_NewObj() #define _DOB Tcl_DuplicateObj #define _SOB(pO) Tcl_IsShared( pO )? Tcl_DuplicateObj( pO ):pO #define _ASO Tcl_AppendStringsToObj #define _ATO Tcl_AppendToObj #define _DRC Tcl_DecrRefCount #define _IRC Tcl_IncrRefCount #define _GOR Tcl_GetObjResult( pI ) #define _SOR(pO) Tcl_SetObjResult( pI, pO ) #define _ROR Tcl_ResetResult( pI ) #define _HACE Tcl_CreateHashEntry #define _HADE Tcl_DeleteHashEntry #define _HASV Tcl_SetHashValue #define _HAFE Tcl_FindHashEntry #define _HAGV Tcl_GetHashValue #define _HAGK Tcl_GetHashKey #define _HAFESV(phH,pcK,psD,piN) _HASV( _HACE( phH, pcK, piN ), psD ) #define _HASCAN(phH,peE,psS) peE = Tcl_FirstHashEntry( phH, psS ); peE != NULL; peE = Tcl_NextHashEntry( psS ) #define _GIFO(pO,pA,pcTxt,piRes) Tcl_GetIndexFromObj( pI, pO, pA, pcTxt, 0, piRes ) #define _WNA(objc,pcText) ( Tcl_WrongNumArgs( pI, objc, objv, pcText ), TCL_ERROR ) /* my very own exception handling */ #define try( Expr, Excep ) { if( Expr != TCL_OK ) throw Excep; } #define throw goto #define catch /* _MkzInfo struct for each created interpreter. the key is always the handle of the gz or zip file. the value for tGzHandles and tZipHandles is the access mode (r,w,a). For tZipCurrent it is the "current file", for tZipComment any comment that was set for the zip file. */ typedef struct _MkzInfo { Tcl_HashTable tGzHandles; /* access mode of gz file */ Tcl_HashTable tZipHandles; /* access mode of zip file */ Tcl_HashTable tZipCurrent; /* the "current file" within a zip file */ Tcl_HashTable tZipComment; /* any comment for a zip file */ } _MkzInfo; /* the function prototypes. */ static int _MkzError( Tcl_Interp *, char *, ... ); static int _MkzZlibError( Tcl_Interp *, char *, int ); static int _MkzGzError( Tcl_Interp *, char *, gzFile ); static int _MkzZipError( Tcl_Interp *, char *, int ); static int _MkzGetOptions( Tcl_Interp *, int, Tcl_Obj *CONST[], char *[], Tcl_Obj ** ); static void *_MkzFindHandle( Tcl_Interp *, Tcl_HashTable *, Tcl_Obj *, char, char * ); static int _MkzCloseCurrent( Tcl_Interp *, _MkzInfo *, void *, char ); int Mkziplib_Init( Tcl_Interp * ); int Mkziplib_SafeInit( Tcl_Interp * ); void Mkziplib_Exit( ClientData, Tcl_Interp * ); int Mkz_DeflateCmd( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] ); int Mkz_InflateCmd( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] ); int Mkz_GzCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] ); int Mkz_ZipCmd ( ClientData, Tcl_Interp *, int, Tcl_Obj *CONST[] ); /* _MkzError creates a formatted result string and always returns TCL_ERROR. like with printf(), at least a format string must be provided. in addition, the format string may contain "%O" for Tcl_Obj* types. */ static int _MkzError( Tcl_Interp *pI, char *pcFormat, ... ) { int i; char **args, *pcRun, pcMsg[2000], pcFmt[2000]; va_list marker; if( pI == NULL || pcFormat == NULL ) return TCL_ERROR; va_start( marker, pcFormat ); args = (char**)marker; strcpy( pcFmt, pcFormat ); for( i = 0, pcRun = pcFmt; *pcRun; pcRun++ ) { if( *pcRun != '%' ) continue; pcRun++; if( *pcRun == 'O' ) { *pcRun = 's'; args[i] = _GSO( (Tcl_Obj*)args[i] ); i++; } } vsprintf( pcMsg, pcFmt, marker ); _SSO( _GOR, pcMsg ); return TCL_ERROR; } /* _MkzZlibError called if a zlib function returns an error. this formats an error string and returns TCL_ERROR through _MkzError. pcInfo should be a meaningful text, iError is the zlib error code. */ static int _MkzZlibError( Tcl_Interp *pI, char *pcInfo, int iError ) { char *pcWhat; switch( iError ) { case Z_OK : pcWhat = "no error" ; break; case Z_STREAM_END : pcWhat = "stream end" ; break; case Z_NEED_DICT : pcWhat = "need dictionary"; break; case Z_ERRNO : pcWhat = "see errno" ; break; case Z_STREAM_ERROR : pcWhat = "stream error" ; break; case Z_DATA_ERROR : pcWhat = "data error" ; break; case Z_MEM_ERROR : pcWhat = "memory error" ; break; case Z_BUF_ERROR : pcWhat = "buffer error" ; break; case Z_VERSION_ERROR: pcWhat = "version error" ; break; default : pcWhat = "unknown"; } return _MkzError( pI, "%s (zlib error %d, %s)", pcInfo, iError, pcWhat ); } /* _MkzGzError called if an operation on a gz file failed. formats an error string and returns TCL_ERROR through _MkzError. pcInfo should be a meaningful text, pvFile is the handle of the gz file. */ static int _MkzGzError( Tcl_Interp *pI, char *pcInfo, gzFile pvFile ) { int iError; return _MkzError( pI, "%s (gz error '%s')", pcInfo, gzerror( pvFile, &iError ) ); } /* _MkzZipError called if a minizip function returns an error. this formats an error string and returns TCL_ERROR through _MkzError. though there are different #defines for UNZ* and ZIP*, the values behind them are the same (in that version). the UNZ* #defines is the superset, so it is used here. pcInfo should be a meaningful text, iError is the minizp error code. */ static int _MkzZipError( Tcl_Interp *pI, char *pcInfo, int iError ) { char *pcWhat; switch( iError ) { case UNZ_OK : pcWhat = "no error" ; break; case UNZ_ERRNO : pcWhat = "see errno" ; break; case UNZ_PARAMERROR : pcWhat = "parameter error"; break; case UNZ_BADZIPFILE : pcWhat = "bad zip file" ; break; case UNZ_INTERNALERROR: pcWhat = "internal error" ; break; case UNZ_CRCERROR : pcWhat = "checksum error" ; break; default : pcWhat = "unknown"; } return _MkzError( pI, "%s (zip error %d, %s)", pcInfo, iError, pcWhat ); } /* _MkzGetOptions helper function to analyze option-value pairs in an argument list. objv is expected to contain objc/2 option-value pairs. ppcOptions must point to a string array with the allowed options. the function will return TCL_ERROR if an illegal option was found. if not, the elements in ppoValues will either point to NULL (if the option was not found in objv), or to the option's value (if found in objv). the indexes in ppoValues correspond to those in ppcValues. */ static int _MkzGetOptions( Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[], char *ppcOptions[], Tcl_Obj **ppoValues ) { int i, iMatch; for( i = 0; ppcOptions[i]; i++ ) ppoValues[i] = NULL; for( i = 0; i < objc; i+= 2 ) { try( _GIFO( objv[i], ppcOptions, "option", &iMatch ), eError ); ppoValues[iMatch] = objv[i+1]; } return TCL_OK; catch eError: return TCL_ERROR; } /* _MkzFindHandle searches for a gz or zip handle and throws an exception if the handle could not be found. otherwise, returns the handle. in addition, if cReqMode is 0 and pcRealMode is not NULL, the mode with which the gz or zip file was opened is returned in pcRealMode ('r', 'w' or 'a'). If cReqMode is 'r' or 'w' instead, it is checked if the file was opened in that mode, and if not, an exception is thrown (cReqmode 'w' covers access modes 'w' and 'a'). */ static void *_MkzFindHandle( Tcl_Interp *pI, Tcl_HashTable *ptTable, Tcl_Obj *poHandle, char cReqMode, char *pcRealMode ) { char cRealMode; int iHandle; Tcl_HashEntry *psHashE; try( _GIO( poHandle, &iHandle ), eBadHandle ); if( ! ( psHashE = _HAFE( ptTable, (char*)iHandle ) ) ) throw eBadHandle; cRealMode = (char)_HAGV( psHashE ); if( cReqMode == 'r' && cRealMode != 'r' ) throw eWrongMode; else if( cReqMode == 'w' && cRealMode != 'w' && cRealMode != 'a' ) throw eWrongMode; if( pcRealMode ) *pcRealMode = cRealMode; return (void*)iHandle; catch eBadHandle: _MkzError( pI, "Cannot find ziphandle '%O'", poHandle ); return NULL; catch eWrongMode: _MkzError( pI, "ziphandle '%O' wasn't opened for %sing", poHandle, (cReqMode == 'r')? "read":"writ" ); return NULL; } /* _MkzCloseCurrent Closes the "current file" within a zip file. pvFile is the handle to the zip file, cMode the access mode from when it was opened. the current file is closed and the hash table that holds its name is cleaned up. */ static int _MkzCloseCurrent( Tcl_Interp *pI, _MkzInfo *psInfo, void *pvFile, char cMode ) { int iErr; Tcl_Obj *poCurrent; poCurrent = (Tcl_Obj*)_HAGV( _HAFE( &(psInfo->tZipCurrent), pvFile ) ); if( poCurrent ) { if( ( cMode == 'r' && ( iErr = unzCloseCurrentFile( pvFile ) ) != UNZ_OK ) || ( cMode == 'w' && ( iErr = zipCloseFileInZip ( pvFile ) ) != ZIP_OK ) ) throw eError; _DRC( poCurrent ); _HASV( _HAFE( &(psInfo->tZipCurrent), pvFile ), NULL ); } return 0; catch eError: return iErr; } /* Mkz_DeflateCmd implements the deflate command. Simply compresses a (binary) data string. The compression level can be set with the -level option. Memory for the deflated data is allocated (according to the zlib documentation, it must be at least 0.1% + 12 bytes larger than the uncompressed data. I made it 1% + 13 bytes, just for fun). Then the data is compressed with a single call to deflate(). Note that simply writing such compressed data to a file is *not* the same as creating a gz file with the gz command. */ int Mkz_DeflateCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] ) { int iSize, iLevel, iErr; char *pcData; z_stream sStream; if( ( objc != 2 && objc != 4 ) || ( objc == 4 && strcmp( _GSO( objv[1] ), "-level" ) ) ) return _WNA( 1, "?-level 0-9? data" ); /* detect the option and extract its value, or use default compression */ if( objc == 4 ) { try( _GIO( objv[2], &iLevel ), eBadLevel ); if( iLevel < 0 || iLevel > 9 ) throw eBadLevel; } else iLevel = Z_DEFAULT_COMPRESSION; /* uncompressed stream starts at first byte of the last argument */ sStream.next_in = _GAO( objv[objc-1], &(sStream.avail_in) ); /* blow up result object with sufficient bytes... */ iSize = (int)(sStream.avail_in * 1.01 + 13); if( ! ( pcData = _SAL( _GOR, iSize ) ) ) throw eOutOfMemory; /* ...and tell zlib where to find them. */ sStream.next_out = pcData; sStream.avail_out = iSize; sStream.zalloc = NULL; sStream.zfree = NULL; if( ( iErr = deflateInit( &sStream, iLevel ) ) != Z_OK ) throw eDeflate; /* deflate all in one pass. the result must be Z_STREAM_END!. if it just Z_OK, then we didn't compress all of it, because of insufficient memory */ if( ( iErr = deflate( &sStream, Z_FINISH ) ) != Z_STREAM_END ) { iErr = ( iErr == Z_OK )? Z_BUF_ERROR : iErr; throw eDeflate; } if( ( iErr = deflateEnd( &sStream ) ) != Z_OK ) throw eDeflate; /* set result object to actual length of compressed data */ _SAL( _GOR, sStream.total_out ); return TCL_OK; catch eBadLevel: return _MkzError( pI, "Bad compression level '%O': Must be between 0 and 9.", objv[2] ); catch eOutOfMemory: return _MkzError( pI, "Out of memory (%d bytes needed)", iSize ); catch eDeflate: return _MkzZlibError( pI, "Could not compress data", iErr ); } /* Mkz_InflateCmd implements the inflate command. Decompresses data that was compressed with the inflate command. since the size of the inflated(!) data is normally not known, the function uses an initial buffer of _BUFLEN bytes and increases it iteratively if neccessary. if the size of the inflated(!) data is known, it can be specified with the -size option, in which case the result buffer is allocated with that size and hence re-allocations are avoided. */ int Mkz_InflateCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] ) { int iSize, iDelta, iErr; char *pcData; z_stream sStream; if( ( objc != 2 && objc != 4 ) || ( objc == 4 && strcmp( _GSO( objv[1] ), "-size" ) ) ) return _WNA( 1, "?-size integer? data" ); /* detect the -size option and extract its value, or use default size */ if( objc == 4 ) { try( _GIO( objv[2], &iSize ), eBadSize ); if( iSize < 1 ) throw eBadSize; } else iSize = _BUFLEN; /* compressed stream starts at first byte of the last argument */ sStream.next_in = _GAO( objv[objc-1], &(sStream.avail_in) ); /* blow up result object with some bytes... */ if( ! ( pcData = _SAL( _GOR, iSize ) ) ) throw eOutOfMemory; /* ...and tell zlib where to find them. */ sStream.next_out = pcData; sStream.avail_out = iSize; sStream.zalloc = NULL; sStream.zfree = NULL; if( ( iErr = inflateInit( &sStream ) ) != Z_OK ) throw eInflate; iDelta = iSize; /* additional same as initial memory */ while( 1 ) { iErr = inflate( &sStream, Z_SYNC_FLUSH ); /* inflate what you can */ if( iErr == Z_STREAM_END ) /* true if all is inflated */ break; /* then we're done and can go */ else if( iErr != Z_OK ) /* Z_OK means there's data left */ throw eInflate; /* but anything else is bad */ if( sStream.avail_out == 0 ) /* if we run out of buffer space */ { if( ! ( pcData = _SAL( _GOR, iSize + iDelta ) ) ) /* just get more */ throw eOutOfMemory; sStream.next_out = pcData + iSize; /* tell zlib where the new... */ sStream.avail_out = iDelta; /* block is and how long it is */ iSize += iDelta; } } if( ( iErr = inflateEnd( &sStream ) ) != Z_OK ) throw eInflate; /* set result object to actual length of decompressed data */ _SAL( _GOR, sStream.total_out ); return TCL_OK; catch eBadSize: return _MkzError( pI, "Bad buffer size '%O': Must be a positive integer.", objv[2] ); catch eOutOfMemory: return _MkzError( pI, "Out of memory (%d bytes needed)", iSize ); catch eInflate: return _MkzZlibError( pI, "Could not decompress data", iErr ); } /* Mkz_GzCmd implements the gz command. this is rather lengthy, but the various cases within the switch command are like single and independent functions. the command allows to create, write and read gz files. a new Tcl channel type would have been the perfect solution here, but I was too lazy. */ int Mkz_GzCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] ) { _MkzInfo *psInfo = (_MkzInfo*)pC; int iMatch, iLevel, iZerr, bNew, iDataLen, iBytes; char pcMode[6], *pcData; Tcl_Obj *poFile; Tcl_HashEntry *psHashE; Tcl_HashSearch sHashS; gzFile pvFile; char *ppcOptions[] = { "open", "close", "read", "gets", "write", "flush", "eof", "handles", NULL }; enum options { _OPEN , _CLOSE , _READ , _GETS , _WRITE , _FLUSH , _EOF , _HANDLES }; if( objc < 2 ) return _WNA( 1, "option ?args ...?" ); try( _GIFO( objv[1], ppcOptions, "option", &iMatch ), eError ); switch( iMatch ) { /* gz open opens a gz file. the -level option can only be specified when access mode is 'w' or 'a'. the central piece is really just the call to gz_open(), the rest is checking and hash table maintenance. */ case _OPEN: if( objc < 3 || objc > 6 || objc == 5 || ( objc == 6 && strcmp( _GSO( objv[2] ), "-level" ) ) ) return _WNA( 2, "?-level 0-9? fileName ?access?" ); /* determine the access mode. the default is 'r'. */ if( objc == 3 || ( objc == 4 && ! strcmp( _GSO( objv[3] ), "r" ) ) ) strcpy( pcMode, "rb" ); else if( ! strcmp( _GSO( objv[objc-1] ), "w" ) ) strcpy( pcMode, "wb" ); else if( ! strcmp( _GSO( objv[objc-1] ), "a" ) ) strcpy( pcMode, "ab" ); else throw eBadMode; /* now extract the value of the -level option, if any */ if( objc == 6 ) { poFile = objv[4]; try( _GIO( objv[3], &iLevel ), eBadLevel ); if( iLevel < 0 || iLevel > 9 ) throw eBadLevel; strcat( pcMode, _GSO( objv[3] ) ); } else poFile = objv[2]; /* finally open the gz file */ if( ! ( pvFile = gzopen( _GSO( poFile ), pcMode ) ) ) throw eOpenFailed; /* create an entry in the hast table and store the handle */ psHashE = _HACE( &(psInfo->tGzHandles), (char*)pvFile, &bNew ); _HASV( psHashE, pcMode[0] ); /* then return the handle for further reference */ _SOR( _NIO( (int)pvFile ) ); break; /* gz close closes a gz file. also cleans up the hash table. */ case _CLOSE: if( objc != 3 ) return _WNA( 2, "gzHandle" ); if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tGzHandles), objv[2], 0, NULL ) ) ) throw eError; if( ( iZerr = gzclose( pvFile ) ) != Z_OK ) throw eCloseFailed; _HADE( _HAFE( &(psInfo->tGzHandles), (char*)pvFile ) ); break; /* gz read read and decompress data from a gz file. either numChars bytes are read or the entire file. */ case _READ: if( objc < 3 || objc > 4 ) return _WNA( 2, "gzHandle ?numChars?" ); /* get handle and make sure the file was opened for reading */ if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tGzHandles), objv[2], 'r', NULL ) ) ) throw eError; if( objc == 3 ) { /* read the entire file, in blocks of _BUFLEN bytes, until EOF */ for( iDataLen = 0; ! gzeof( pvFile ); iDataLen += iBytes ) { if( ! ( pcData = _SAL( _GOR, iDataLen + _BUFLEN ) ) ) throw eReallocFailed; /* blow up result object */ if( ( iBytes = gzread( pvFile, pcData + iDataLen, _BUFLEN ) ) < 0 ) throw eReadFailed; /* get some inflated data */ } _SAL( _GOR, iDataLen ); /* set actual number of bytes read */ } else { try( _GIO( objv[3], &iDataLen ), eError ); /* get numBytes */ if( ! ( pcData = _SAL( _GOR, iDataLen ) ) ) /* blow up result obj */ throw eReallocFailed; if( ( iBytes = gzread( pvFile, pcData, iDataLen ) ) < 0 ) throw eReadFailed; /* get the amount of bytes requested */ _SAL( _GOR, iBytes ); /* set actual number of bytes read */ } break; /* gz gets reads bytes from the gz file until a newline character is found or until the file is read entirely. the newline character is not returned in the result string. */ case _GETS: if( objc != 3 ) return _WNA( 2, "gzHandle" ); /* get handle and make sure the file was opened for reading */ if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tGzHandles), objv[2], 'r', NULL ) ) ) throw eError; if( ! ( pcData = Tcl_Alloc( _BUFLEN ) ) ) /* put bytes into this buffer */ throw eOutOfMemory; while( ! gzeof( pvFile ) ) { if( ! gzgets( pvFile, pcData, _BUFLEN ) ) /* get a line, or up to */ throw eGetsFailed; /* _BUFLEN bytes */ if( pcData[strlen(pcData)-1] == '\n' ) /* we read a complete line */ { _ATO( _GOR, pcData, strlen(pcData)-1 ); /* append it to result */ break; /* and exit loop, we're done */ } _ATO( _GOR, pcData, strlen(pcData) ); /* append block to result */ } /* then read next block */ Tcl_Free( pcData ); break; /* gz write writes data into a gz file. */ case _WRITE: if( objc != 4 ) return _WNA( 2, "gzHandle data" ); /* get handle and make sure the file was opened for writing */ if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tGzHandles), objv[2], 'w', NULL ) ) ) throw eError; pcData = _GAO( objv[3], &iDataLen ); /* assume data is binary */ if( ( iBytes = gzwrite( pvFile, pcData, iDataLen ) ) < 0 ) throw eWriteFailed; /* write the data */ _SOR( _NIO( iBytes ) ); /* return number of bytes written */ break; /* gz flush flush all pending data to the gz file. */ case _FLUSH: if( objc != 3 ) return _WNA( 2, "gzHandle" ); /* get handle and make sure the file was opened for writing */ if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tGzHandles), objv[2], 'w', NULL ) ) ) throw eError; if( ( iZerr = gzflush( pvFile, Z_FINISH ) ) != Z_OK ) /* flush all */ throw eFlushFailed; break; /* gz eof see if an eof condition exists on the gz file. if the file was opened for writing, gzeof() always returns false. */ case _EOF: if( objc != 3 ) return _WNA( 2, "gzHandle" ); if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tGzHandles), objv[2], 0, NULL ) ) ) throw eError; _SOR( _NIO( gzeof( pvFile ) ) ); break; /* gz handles returns a list of all open gz handles. simply scans through the hash table and appends the keys to the result object. */ case _HANDLES: for( _HASCAN( &(psInfo->tGzHandles), psHashE, &sHashS ) ) _LOAE( _GOR, _NIO( (int)_HAGK( &(psInfo->tGzHandles), psHashE ) ) ); break; } return TCL_OK; catch eError: return TCL_ERROR; catch eOpenFailed: return _MkzError( pI, "Could not open gzfile '%O'", objv[2] ); catch eBadMode: return _MkzError( pI, "Bad access mode '%O': Must be [rwa] or [wa][0-9].", objv[3] ); catch eCloseFailed: return _MkzGzError( pI, "Could not close gzfile", pvFile ); catch eFlushFailed: return _MkzGzError( pI, "Could not flush to gzfile", pvFile ); catch eReadFailed: return _MkzGzError( pI, "Could not read gzfile", pvFile ); catch eWriteFailed: return _MkzGzError( pI, "Could not write gzfile", pvFile ); catch eBadLevel: return _MkzError( pI, "Bad compression level '%O': Must be between 0 and 9.", objv[3] ); catch eReallocFailed: return _MkzError( pI, "Out of memory" ); catch eOutOfMemory: return _MkzError( pI, "Out of memory" ); catch eGetsFailed: Tcl_Free( pcData ); return _MkzError( pI, "Gets failed for gzfile" ); } /* Mkz_ZipCmd implements the zip command. this is rather lengthy, but the various cases within the switch command are like single and independent functions. the command allows to create, write and read zip files. a zip archive can contain several files, and zip/unzip operations are always related to one file within the archive. this file is called the "current file" herein. this is based on the minizip work from Gilles Vollant. minizip is included in the zlib distribution. */ int Mkz_ZipCmd( ClientData pC, Tcl_Interp *pI, int objc, Tcl_Obj *CONST objv[] ) { _MkzInfo *psInfo = (_MkzInfo*)pC; int iMatch, iZerr, bNew, iDataLen, iBytes, iLevel; char *pcMode, *pcFile, *pcData, *pcComment, cMode, pcBuf[_BUFLEN], pcScript[40]; Tcl_HashEntry *psHashE; Tcl_HashSearch sHashS; Tcl_Obj *poCurrent, *poComment, *ppoValues[4]; void *pvFile; unz_file_info sUnzFileInfo; zip_fileinfo sZipFileInfo; char *ppcOptions[] = { "open", "close", "comment", "set", "write", "read", "files", "info", "eof", "handles", NULL }; enum options { _OPEN , _CLOSE , _COMMENT , _SET , _WRITE , _READ , _FILES , _INFO , _EOF , _HANDLES }; char *ppcSetOptions[] = { "-level", "-comment", "-time", "-attributes", NULL }; if( objc < 2 ) return _WNA( 1, "option ?args ...?" ); try( _GIFO( objv[1], ppcOptions, "option", &iMatch ), eError ); switch( iMatch ) { /* zip open opens a zip archive. the default access mode is 'r' for reading. mode 'a' (append) does unfortunately *not* mean that files can be appended to a zip archive. it just means that the archive is created at the end of the (existing) file. */ case _OPEN: if( objc < 3 || objc > 4 ) return _WNA( 2, "fileName ?access?" ); pcFile = _GSO( objv[2] ); pcMode = (objc == 3)? "r" : _GSO( objv[3] ); /* check access mode is r, w or a */ if( strlen( pcMode ) != 1 || ! strchr( "rwa", *pcMode ) ) throw eBadMode; /* different functions, depending on the access mode */ if( *pcMode == 'r' ) pvFile = (void*)unzOpen( pcFile ); else pvFile = (void*)zipOpen( pcFile, ( *pcMode == 'a' ) ); if( ! pvFile ) throw eOpenFailed; /* create a record in the hash tables for that handle. tZipCurrent will later hold the "current file", tZipComment any comment to be set in the archive (if opened for writing */ _HASV( _HACE( &(psInfo->tZipHandles), pvFile, &bNew ), *pcMode ); _HASV( _HACE( &(psInfo->tZipCurrent), pvFile, &bNew ), NULL ); _HASV( _HACE( &(psInfo->tZipComment), pvFile, &bNew ), NULL ); _SOR( _NIO( (int)pvFile ) ); break; /* zip close closes a zip archive. the tables are cleaned up, too. */ case _CLOSE: if( objc != 3 ) return _WNA( 2, "zipHandle" ); /* get the zip handle */ if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tZipHandles), objv[2], 0, &cMode ) ) ) throw eError; /* close any open "current file" */ _MkzCloseCurrent( pI, psInfo, pvFile, cMode ); /* just close the archive. if it was opened for writing, also get the comment from the hash table, and use it in zipClose function */ if( cMode == 'r' ) { if( ( iZerr = unzClose( pvFile ) ) != UNZ_OK ) throw eCloseFailed; } else { poComment = (Tcl_Obj*)_HAGV( _HAFE( &(psInfo->tZipComment), pvFile ) ); if( ( iZerr = zipClose( pvFile, (poComment)? _GSO( poComment ):NULL ) ) != ZIP_OK ) throw eCloseFailed; if( poComment ) _DRC( poComment ); } _HADE( _HAFE( &(psInfo->tZipHandles), pvFile ) ); _HADE( _HAFE( &(psInfo->tZipCurrent), pvFile ) ); _HADE( _HAFE( &(psInfo->tZipComment), pvFile ) ); break; /* zip comment gets or sets the comment on a zip archive. if the archive is in read mode, simple get the comment from the API function and return it. otherwise, either get the comment from the hash table or set it there. it will be written into the archive when it is closed. */ case _COMMENT: if( objc < 3 || objc > 4 ) return _WNA( 2, "zipHandle ?comment?" ); if( objc == 3 ) { if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tZipHandles), objv[2], 0, &cMode ) ) ) throw eError; if( cMode == 'r' ) /* for read mode: take it from archive */ { _SOL( _GOR, _BUFLEN ); /* bump up result object */ if( ( iZerr = unzGetGlobalComment( pvFile, _GSO( _GOR ), _BUFLEN ) ) < 0 ) throw eCommentFailed; /* and read what's available */ _SOL( _GOR, iZerr ); /* set real length of comment */ } else /* for write mode: take it from hash table, if available */ { poComment = (Tcl_Obj*)_HAGV( _HAFE( &(psInfo->tZipComment), pvFile ) ); if( poComment ) _SOR( (Tcl_Obj*)poComment ); } } else { if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tZipHandles), objv[2], 'w', NULL ) ) ) throw eError; poComment = (Tcl_Obj*)_HAGV( _HAFE( &(psInfo->tZipComment), pvFile ) ); if( poComment ) /* find a dereference any existing comment */ _DRC( poComment ); _HASV( _HACE( &(psInfo->tZipComment), pvFile, &bNew ), objv[3] ); _IRC( objv[3] ); /* set new comment and don't forget it */ } break; /* zip set sets or retrieves the "current file". the current file is stored in a hash table. if no fileName is given, the record in the hash table is returned. otherwise, the hash table is updated and the current file is "activated" within the archive for subsequent read/write operations. for writing, several options can be provided, which are passed to the minizip API function through a struct. */ case _SET: if( objc < 3 ) return _WNA( 2, "zipHandle ?fileName? ?options?" ); if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tZipHandles), objv[2], 0, &cMode ) ) ) throw eError; if( objc == 3 ) /* if no fileName specified */ { poCurrent = (Tcl_Obj*)_HAGV( _HAFE( &(psInfo->tZipCurrent), pvFile ) ); if( poCurrent ) _SOR( poCurrent ); /* simple return what's in the table */ } else /* fileName specified */ { _MkzCloseCurrent( pI, psInfo, pvFile, cMode ); /* close open file */ if( cMode == 'r' ) /* for read mode */ { if( objc > 4 ) /* don't allow any options */ throw eNoOptions; if( ( iZerr = unzLocateFile( pvFile, _GSO( objv[3] ), 0 ) ) != UNZ_OK ) throw eLocateFailed; /* now find the file in the archive */ if( ( iZerr = unzOpenCurrentFile( pvFile ) ) != UNZ_OK ) throw eSetFailed; /* ...and open it */ } else /* for write mode it's more complicated */ { try( _MkzGetOptions( pI, objc-4, objv+4, ppcSetOptions, ppoValues ), eError ); memset( &sZipFileInfo, 0, sizeof( sZipFileInfo ) ); /* reset */ if( ppoValues[0] ) /* found -level option */ { try( _GIO( ppoValues[0], &iLevel ), eBadLevel ); if( iLevel < 0 || iLevel > 9 ) /* get compression level */ throw eBadLevel; } else iLevel = Z_DEFAULT_COMPRESSION; if( ppoValues[1] ) /* found -comment option */ pcComment = _GSO( ppoValues[1] ); /* extract comment */ else pcComment = NULL; if( ppoValues[2] ) /* found -time option */ { time_t iTime; struct tm *psTime; try( _GIO( ppoValues[2], &iTime ), eBadTime ); /* int expected */ if( ! ( psTime = localtime( &iTime ) ) ) /* can't parse it */ throw eBadTime; /* tm is actually identical with tm_zip from minizip */ memcpy( &sZipFileInfo.tmz_date, psTime, sizeof( sZipFileInfo.tmz_date ) ); } if( ppoValues[3] ) /* found -attributes option */ try( _GIO( ppoValues[3], &sZipFileInfo.external_fa ), eError ); /* finally open the "current file" in the archive */ if( ( iZerr = zipOpenNewFileInZip( pvFile, _GSO( objv[3] ), &sZipFileInfo, NULL, 0, NULL, 0, pcComment, (iLevel)? Z_DEFLATED:0, iLevel ) ) != ZIP_OK ) throw eSetFailed; } /* set "current file" in hash table */ _HASV( _HAFE( &(psInfo->tZipCurrent), pvFile ), objv[3] ); _IRC( objv[3] ); } break; /* zip write writes data for the current file into the archive. */ case _WRITE: if( objc != 4 ) return _WNA( 2, "zipHandle data" ); if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tZipHandles), objv[2], 'w', NULL ) ) ) throw eError; /* get handle and check if okay to write */ if( ! _HAGV( _HAFE( &(psInfo->tZipCurrent), pvFile ) ) ) throw eCurrentUndefined; /* can't write without "current file" */ pcData = _GAO( objv[3], &iDataLen ); /* extract data and write it */ if( ( iZerr = zipWriteInFileInZip( pvFile, pcData, iDataLen ) ) != ZIP_OK ) throw eWriteFailed; break; /* zip read read and decompress data from an archive. either numChars bytes are read or the entire file. */ case _READ: if( objc < 3 || objc > 4 ) return _WNA( 2, "zipHandle ?numChars?" ); if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tZipHandles), objv[2], 'r', NULL ) ) ) throw eError; /* get handle and check if okay to read*/ if( ! _HAGV( _HAFE( &(psInfo->tZipCurrent), pvFile ) ) ) throw eCurrentUndefined; /* can't write without "current file" */ /* read the entire file, in blocks of _BUFLEN bytes, until EOF */ if( objc == 3 ) { for( iDataLen = 0; ! unzeof( pvFile ); iDataLen += iBytes ) { if( ! ( pcData = _SAL( _GOR, iDataLen + _BUFLEN ) ) ) throw eOutOfMemory; /* blow up result object */ if( ( iBytes = unzReadCurrentFile( pvFile, pcData + iDataLen, _BUFLEN ) ) < 0 ) throw eReadFailed; /* get some inflated data */ } _SAL( _GOR, iDataLen ); /* set actual number of bytes read */ } else { try( _GIO( objv[3], &iDataLen ), eError ); /* get numBytes */ if( ! ( pcData = _SAL( _GOR, iDataLen ) ) ) /* blow up result obj */ throw eOutOfMemory; if( ( iBytes = unzReadCurrentFile( pvFile, pcData, iDataLen ) ) < 0 ) throw eReadFailed; /* get the amount of bytes requested */ _SAL( _GOR, iBytes ); /* set actual number of bytes read */ } break; /* zip files returns all the files in a zip archive. loops through the files in the archive, retrieves the file name and appends it to the result. */ case _FILES: if( objc != 3 ) return _WNA( 2, "zipHandle" ); if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tZipHandles), objv[2], 'r', NULL ) ) ) throw eError; _MkzCloseCurrent( pI, psInfo, pvFile, 'r' ); for( iZerr = unzGoToFirstFile( pvFile ); iZerr == UNZ_OK; iZerr = unzGoToNextFile( pvFile ) ) if( unzGetCurrentFileInfo( pvFile, NULL, pcBuf, _BUFLEN, NULL, 0, NULL, 0 ) == UNZ_OK ) _LOAE( _GOR, _NSO( pcBuf ) ); break; /* zip info returns information on a file within the archive. the result is a list with 5 arguments: the timestamp of the file (converted back from the tm struct to an int), the compressed size, uncompressed size, the file attributes and the file comment. */ case _INFO: if( objc != 4 ) return _WNA( 2, "zipHandle fileName" ); if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tZipHandles), objv[2], 'r', NULL ) ) ) throw eError; _MkzCloseCurrent( pI, psInfo, pvFile, 'r' ); if( unzLocateFile( pvFile, _GSO( objv[3] ), 0 ) != UNZ_OK ) throw eInfoFailed; /* try to find the file in the archive */ if( unzGetCurrentFileInfo( pvFile, &sUnzFileInfo, NULL, 0, NULL, 0, pcBuf, _BUFLEN ) != UNZ_OK ) throw eInfoFailed; /* get the info struct and the comment */ /* let tcl do the dirty work of converting the time into an int */ sprintf(pcScript, "clock scan {%d/%d/%d %d:%d:%d}", sUnzFileInfo.tmu_date.tm_mon+1, sUnzFileInfo.tmu_date.tm_mday, sUnzFileInfo.tmu_date.tm_year, sUnzFileInfo.tmu_date.tm_hour , sUnzFileInfo.tmu_date.tm_min , sUnzFileInfo.tmu_date.tm_sec ); /* if this succeeds, the time is now in the result object as an int */ if( Tcl_Eval( pI, pcScript ) != TCL_OK ) _SOR( _NIO( -1 ) ); /* if it fails, put in -1 instead */ /* append all the rest. pcBuf contains the file comment */ _LOAE( _GOR, _NIO( sUnzFileInfo.compressed_size ) ); _LOAE( _GOR, _NIO( sUnzFileInfo.uncompressed_size ) ); _LOAE( _GOR, _NIO( sUnzFileInfo.external_fa ) ); _LOAE( _GOR, _NSO( pcBuf ) ); break; /* zip eof see if an eof condition exists on the archive. if the file was opened for writing, it always returns false. */ case _EOF: if( objc != 3 ) return _WNA( 2, "zipHandle" ); if( ! ( pvFile = _MkzFindHandle( pI, &(psInfo->tZipHandles), objv[2], 0, &cMode ) ) ) throw eError; _SOR( _NIO( (cMode == 'r')? unzeof( (unzFile)pvFile ) : 0 ) ); break; /* zip handles returns a list of all open zip handles. simply scans through the hash table and appends the keys to the result object. */ case _HANDLES: for( _HASCAN( &(psInfo->tZipHandles), psHashE, &sHashS ) ) _LOAE( _GOR, _NIO( (int)_HAGK( &(psInfo->tZipHandles), psHashE ) ) ); break; } return TCL_OK; catch eError: return TCL_ERROR; catch eOpenFailed: return _MkzError( pI, "Could not open zip archive '%O'", objv[2] ); catch eBadMode: return _MkzError( pI, "Bad access mode '%O': Must be 'r' or 'w' or 'a'.", objv[3] ); catch eCloseFailed: return _MkzZipError( pI, "Could not close zip archive", iZerr ); catch eCommentFailed: return _MkzZipError( pI, "Could not retrieve comment from archive", iZerr ); catch eLocateFailed: return _MkzZipError( pI, "Could not locate file in zip archive", iZerr ); catch eSetFailed: return _MkzZipError( pI, "Could not set file in zip archive", iZerr ); catch eWriteFailed: return _MkzZipError( pI, "Could not write file into zip archive", iZerr ); catch eReadFailed: return _MkzZipError( pI, "Could not read file in zip archive", iBytes ); catch eInfoFailed: return _MkzError( pI, "Could not retrieve info for file '%O'.", objv[3] ); catch eCurrentUndefined: return _MkzError( pI, "No current file defined in zip archive" ); catch eBadLevel: return _MkzError( pI, "Bad compression level '%O': Must be between 0 and 9.", ppoValues[0] ); catch eBadTime: return _MkzError( pI, "Bad time value '%O'. Expected integer.", ppoValues[2] ); catch eNoOptions: return _MkzError( pI, "Options not allowed in read mode." ); catch eOutOfMemory: Tcl_Free( pcData ); return _MkzError( pI, "Out of memory (%d bytes needed)", iDataLen ); } /* Mkziplib_Init package initialization. creates all new commands and registers the package. also initializes hash tables. */ int Mkziplib_Init( Tcl_Interp *pI ) { _MkzInfo *psInfo; ClientData pC; /* check for version >= 8.2, because of byte arrays being used */ if( TCL_MAJOR_VERSION < 8 || ( TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 2 ) ) throw eWrongVersion; #ifdef USE_TCL_STUBS if( Tcl_InitStubs( pI, "8.3", 0) == NULL ) throw eError; #endif /* one info struct for each interp! */ psInfo = (_MkzInfo*)ckalloc( sizeof(_MkzInfo) ); Tcl_InitHashTable( &(psInfo->tGzHandles ), TCL_ONE_WORD_KEYS ); Tcl_InitHashTable( &(psInfo->tZipHandles), TCL_ONE_WORD_KEYS ); Tcl_InitHashTable( &(psInfo->tZipCurrent), TCL_ONE_WORD_KEYS ); Tcl_InitHashTable( &(psInfo->tZipComment), TCL_ONE_WORD_KEYS ); pC = (ClientData)psInfo; Tcl_CallWhenDeleted( pI, Mkziplib_Exit, pC ); Tcl_CreateObjCommand( pI, "deflate", Mkz_DeflateCmd, pC, NULL ); Tcl_CreateObjCommand( pI, "inflate", Mkz_InflateCmd, pC, NULL ); Tcl_CreateObjCommand( pI, "gz" , Mkz_GzCmd , pC, NULL ); Tcl_CreateObjCommand( pI, "zip" , Mkz_ZipCmd , pC, NULL ); try( Tcl_PkgProvide( pI, "mkZiplib", _VERSION ), eError ); return TCL_OK; catch eError: return TCL_ERROR; catch eWrongVersion: return _MkzError( pI, "Package mkZiplib requires Tcl Version 8.2" ); } int Mkziplib_SafeInit( Tcl_Interp *pI ) { return Mkziplib_Init( pI ); } /* Mkziplib_Exit termination procedure. called when the interpreter is deleted. closes all open files, cleans up the hash table objects, then deletes the hash tables. */ void Mkziplib_Exit( ClientData pC, Tcl_Interp *pI ) { _MkzInfo *psInfo = (_MkzInfo*)pC; Tcl_HashEntry *psHashE; Tcl_HashSearch sHashS; /* close any open gz files */ for( _HASCAN( &(psInfo->tGzHandles), psHashE, &sHashS ) ) gzclose( _HAGK( &(psInfo->tGzHandles), psHashE ) ); /* close any open zip files */ for( _HASCAN( &(psInfo->tZipHandles), psHashE, &sHashS ) ) ( (char)_HAGV( psHashE ) == 'r' )? unzClose( _HAGK( &(psInfo->tGzHandles), psHashE ) ) : zipClose( _HAGK( &(psInfo->tGzHandles), psHashE ), NULL ); /* delete objects that hold current file in zip-file, if any */ for( _HASCAN( &(psInfo->tZipCurrent), psHashE, &sHashS ) ) if( _HAGV( psHashE ) ) _DRC( (Tcl_Obj*)_HAGV( psHashE ) ); /* delete objects that hold zip-comments, if any */ for( _HASCAN( &(psInfo->tZipComment), psHashE, &sHashS ) ) if( _HAGV( psHashE ) ) _DRC( (Tcl_Obj*)_HAGV( psHashE ) ); /* delete the hash tables now, they are clean */ Tcl_DeleteHashTable( &(psInfo->tGzHandles) ); Tcl_DeleteHashTable( &(psInfo->tZipHandles) ); Tcl_DeleteHashTable( &(psInfo->tZipCurrent) ); Tcl_DeleteHashTable( &(psInfo->tZipComment) ); /* finally, delete the info struct for this interp */ ckfree( (char*)psInfo ); } /* static linking. uncomment the following two functions if you want to create a stand-alone shell instead of a dynamic library. */ #ifndef USE_TCL_STUBS int main( int argc, char *argv[] ) { Tcl_Main( argc, argv, Tcl_AppInit ); return 0; } int Tcl_AppInit( Tcl_Interp *pI ) { try( Tcl_Init( pI ), eError ); try( Mkziplib_Init( pI ), eError ); return TCL_OK; catch eError: return TCL_ERROR; } #endif /* * mkZiplib 1.0 * ------------ */ /* case _COMPRESS: if( ( objc != 4 && objc != 6 ) || ( objc == 6 && strcmp( _GSO( objv[2] ), "-level" ) ) ) return _WNA( 2, "?-level 0-9? fileName gzFileName" ); if( objc == 4 ) iLevel = Z_DEFAULT_COMPRESSION; else if( strlen( _GSO( objv[3] ) ) != 1 ) throw eBadLevel; else try( _GIO( objv[3], &iLevel ), eBadLevel ); sprintf( pcMode, "wb%1d", iLevel ); if( ! ( psIn = fopen( _GSO( objv[2] ), "rb" ) ) ) throw eError; else if( ! ( pvFile = gzopen( _GSO( objv[3] ), pcMode ) ) ) throw eError; while( ! feof( psIn ) ) { iBytes = fread( pcBuf, 1, _BUFLEN, psIn ); if( ferror( psIn ) ) throw eError; if( gzwrite( pvFile, pcBuf, iBytes ) < 0 ) throw eError; } fclose( psIn ); if( gzclose( pvFile ) != Z_OK ) throw eError; break; case _UNCOMPRESS: if( objc != 4 ) return _WNA( 2, "gzFileName fileName" ); if( ! ( pvFile = gzopen( _GSO( objv[2] ), "rb" ) ) ) throw eError; if( ! ( psIn = fopen( _GSO( objv[3] ), "wb" ) ) ) throw eError; while( ! gzeof( pvFile ) ) { if( ( iBytes = gzread( pvFile, pcBuf, _BUFLEN ) ) < 0 ) throw eError; if( (int)fwrite( pcBuf, 1, (unsigned)iBytes, psIn ) != iBytes ) throw eError; } fclose( psIn ); if( gzclose( pvFile ) != Z_OK ) throw eError; break; */