summaryrefslogtreecommitdiffstats
path: root/tclzvfs/zvfs.c
diff options
context:
space:
mode:
Diffstat (limited to 'tclzvfs/zvfs.c')
-rwxr-xr-xtclzvfs/zvfs.c1438
1 files changed, 1438 insertions, 0 deletions
diff --git a/tclzvfs/zvfs.c b/tclzvfs/zvfs.c
new file mode 100755
index 0000000..4f1f48f
--- /dev/null
+++ b/tclzvfs/zvfs.c
@@ -0,0 +1,1438 @@
+/*
+ * Smithsonian Astrophysical Observatory, Cambridge, MA, USA
+ * This code has been modified under the terms listed below and is made
+ * available under the same terms.
+ */
+
+/*
+** Copyright (c) 2000 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the GNU General Public
+** License as published by the Free Software Foundation; either
+** version 2 of the License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+** General Public License for more details.
+**
+** You should have received a copy of the GNU General Public
+** License along with this library; if not, write to the
+** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+** Boston, MA 02111-1307, USA.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*************************************************************************
+** A ZIP archive virtual filesystem for Tcl.
+**
+** This package of routines enables Tcl to use a Zip file as
+** a virtual file system. Each of the content files of the Zip
+** archive appears as a real file to Tcl.
+**
+** Modified to use Tcl VFS hooks by Peter MacDonald
+** peter@pdqi.com
+** http://pdqi.com
+**
+** @(#) $Id$
+**
+** Revison Date Author Description
+** ------- ------------- ----------------- ----------------------------------------------
+** Jan 8, 2006 Dennis R. LaBelle Modified to support encrypted files
+**
+** Dec 16, 2009 Dennis R. LaBelle Corrected Tobe_FSMatchInDirectoryProc() for
+** proper operation of glob command on ZVFS files
+** under TCL 8.5.
+*/
+
+#include <ctype.h>
+#include <zlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <stdlib.h>
+/*#include <malloc.h>*/
+#include "tcl.h"
+
+/*
+** Size of the decompression input buffer
+*/
+#define COMPR_BUF_SIZE 32768
+
+/*
+** All static variables are collected into a structure named "local".
+** That way, it is clear in the code when we are using a static
+** variable because its name begins with "local.".
+*/
+static struct {
+ Tcl_HashTable fileHash; /* One entry for each file in the ZVFS. The
+ ** The key is the virtual filename. The data
+ ** an an instance of the ZvfsFile structure. */
+ Tcl_HashTable archiveHash; /* One entry for each archive. Key is the name.
+ ** data is the ZvfsArchive structure */
+ int isInit; /* True after initialization */
+ char *firstMount; /* The path to to the first mounted file. */
+} local;
+
+/*
+** Each ZIP archive file that is mounted is recorded as an instance
+** of this structure
+*/
+typedef struct ZvfsArchive {
+ char *zName; /* Name of the archive */
+ char *zMountPoint; /* Where this archive is mounted */
+ struct ZvfsFile *pFiles; /* List of files in that archive */
+} ZvfsArchive;
+
+/*
+** Particulars about each virtual file are recorded in an instance
+** of the following structure.
+*/
+typedef struct ZvfsFile {
+ char *zName; /* The full pathname of the virtual file */
+ ZvfsArchive *pArchive; /* The ZIP archive holding this file data */
+ int iOffset; /* Offset into the ZIP archive of the data */
+ int nByte; /* Uncompressed size of the virtual file */
+ int nByteCompr; /* Compressed size of the virtual file */
+ int isdir; /* Set to 1 if directory */
+ int depth; /* Number of slashes in path. */
+ int timestamp; /* Modification time */
+ struct ZvfsFile *pNext; /* Next file in the same archive */
+ struct ZvfsFile *pNextName; /* A doubly-linked list of files with the same */
+ struct ZvfsFile *pPrevName; /* name. Only the first is in local.fileHash */
+ /* The following would be used for writable zips. */
+ int nExtra; /* Extra space in the TOC header */
+ int isSpecial; /* Not really a file in the ZIP archive */
+ int dosTime; /* Modification time (DOS format) */
+ int dosDate; /* Modification date (DOS format) */
+ int iCRC; /* Cyclic Redundancy Check of the data */
+} ZvfsFile;
+
+/*
+** Macros to read 16-bit and 32-bit big-endian integers into the
+** native format of this local processor. B is an array of
+** characters and the integer begins at the N-th character of
+** the array.
+*/
+#define INT16(B, N) (B[N] + (B[N+1]<<8))
+#define INT32(B, N) (INT16(B,N) + (B[N+2]<<16) + (B[N+3]<<24))
+
+/* Convert DOS time to unix time. */
+static time_t DosTimeDate(int dosDate, int dosTime)
+{
+ time_t now;
+ struct tm *tm;
+ now=time(NULL);
+ tm = localtime(&now);
+ tm->tm_year=(((dosDate&0xfe00)>>9) + 80);
+ tm->tm_mon=((dosDate&0x1e0)>>5)-1;
+ tm->tm_mday=(dosDate & 0x1f);
+ tm->tm_hour=(dosTime&0xf800)>>11;
+ tm->tm_min=(dosTime&0x7e)>>5;
+ tm->tm_sec=(dosTime&0x1f);
+ return mktime(tm);
+}
+
+/* Return count of char ch in str */
+int strchrcnt(char *str, char ch)
+{
+ int cnt=0;
+ char *cp=str;
+ while ((cp=strchr(cp,ch))) { cp++; cnt++; }
+ return cnt;
+}
+
+/*
+** Concatenate zTail onto zRoot to form a pathname. zRoot will begin
+** with "/". After concatenation, simplify the pathname be removing
+** unnecessary ".." and "." directories. Under windows, make all
+** characters lower case.
+**
+** Resulting pathname is returned. Space to hold the returned path is
+** obtained from Tcl_Alloc() and should be freed by the calling function.
+*/
+static char *CanonicalPath(const char *zRoot, const char *zTail)
+{
+ char *zPath;
+ int i, j, c;
+
+#ifdef __WIN32__
+ if( isalpha(zTail[0]) && zTail[1]==':' ){ zTail += 2; }
+ if( zTail[0]=='\\' ){ zRoot = ""; zTail++; }
+ if( zTail[0]=='\\' ){ zRoot = "/"; zTail++; } // account for UNC style path
+#endif
+ if( zTail[0]=='/' ){ zRoot = ""; zTail++; }
+ if( zTail[0]=='/' ){ zRoot = "/"; zTail++; } // account for UNC style path
+ zPath = Tcl_Alloc( strlen(zRoot) + strlen(zTail) + 2 );
+ if( zPath==0 ) return 0;
+ sprintf(zPath, "%s/%s", zRoot, zTail);
+ for(i=j=0; (c = zPath[i])!=0; i++){
+#ifdef __WIN32__
+ if( isupper(c) ) { if (maptolower) c = tolower(c); }
+ else if( c=='\\' ) c = '/';
+#endif
+ if( c=='/' ){
+ int c2 = zPath[i+1];
+ if( c2=='/' ) continue;
+ if( c2=='.' ){
+ int c3 = zPath[i+2];
+ if( c3=='/' || c3==0 ){
+ i++;
+ continue;
+ }
+ if( c3=='.' && (zPath[i+3]=='.' || zPath[i+3]==0) ){
+ i += 2;
+ while( j>0 && zPath[j-1]!='/' ){ j--; }
+ continue;
+ }
+ }
+ }
+ zPath[j++] = c;
+ }
+ if( j==0 ){ zPath[j++] = '/'; }
+ zPath[j] = 0;
+ return zPath;
+}
+
+/*
+** Construct an absolute pathname in memory obtained from Tcl_Alloc
+** that means the same file as the pathname given.
+**
+** Under windows, all backslash (\) charaters are converted to foward
+** slash (/) and all upper case letters are converted to lower case.
+** The drive letter (if present) is preserved.
+*/
+static char *AbsolutePath(const char *z)
+{
+ Tcl_DString pwd;
+ char *zResult;
+ Tcl_DStringInit(&pwd);
+ if( *z!='/'
+#ifdef __WIN32__
+ && *z!='\\' && (!isalpha(*z) || z[1]!=':')
+#endif
+ ){
+ /* Case 1: "z" is a relative path. So prepend the current working
+ ** directory in order to generate an absolute path. Note that the
+ ** CanonicalPath() function takes care of converting upper to lower
+ ** case and (\) to (/) under windows.
+ */
+ Tcl_GetCwd(0, &pwd);
+ zResult = CanonicalPath( Tcl_DStringValue(&pwd), z);
+ Tcl_DStringFree(&pwd);
+ } else {
+ /* Case 2: "z" is an absolute path already. We just need to make
+ ** a copy of it. Under windows, we need to convert upper to lower
+ ** case and (\) into (/) on the copy.
+ */
+ zResult = Tcl_Alloc( strlen(z) + 1 );
+ if( zResult==0 ) return 0;
+ strcpy(zResult, z);
+#ifdef __WIN32__
+ {
+ int i, c;
+ for(i=0; (c=zResult[i])!=0; i++){
+ if( isupper(c) ) {
+ // zResult[i] = tolower(c);
+ }
+ else if( c=='\\' ) zResult[i] = '/';
+ }
+ }
+#endif
+ }
+ return zResult;
+}
+
+/*
+** Read a ZIP archive and make entries in the virutal file hash table for all
+** content files of that ZIP archive. Also initialize the ZVFS if this
+** routine has not been previously called.
+*/
+int Zvfs_Mount(
+ Tcl_Interp *interp,/* Leave error messages in this interpreter */
+ CONST char *zArchive,/* The ZIP archive file */
+ CONST char *zMountPoint /* Mount contents at this directory */
+ )
+{
+ Tcl_Channel chan; /* Used for reading the ZIP archive file */
+ char *zArchiveName = 0; /* A copy of zArchive */
+ int nFile; /* Number of files in the archive */
+ long iPos; /* Current position in the archive file */
+ ZvfsArchive *pArchive; /* The ZIP archive being mounted */
+ Tcl_HashEntry *pEntry; /* Hash table entry */
+ int isNew; /* Flag to tell use when a hash entry is new */
+ unsigned char zBuf[100]; /* Space into which to read from the ZIP archive */
+ Tcl_HashSearch zSearch; /* Search all mount points */
+
+ if( !local.isInit ) return TCL_ERROR;
+ if (!zArchive) {
+ pEntry=Tcl_FirstHashEntry(&local.archiveHash,&zSearch);
+ while (pEntry) {
+ if ((pArchive = Tcl_GetHashValue(pEntry))) {
+ Tcl_AppendResult(interp,pArchive->zMountPoint," ",pArchive->zName," ",0);
+ }
+ pEntry=Tcl_NextHashEntry(&zSearch);
+ }
+ return TCL_OK;
+ }
+ if (!zMountPoint) {
+ pEntry = Tcl_FindHashEntry(&local.archiveHash,AbsolutePath(zArchive));
+ if (pEntry) {
+ if ((pArchive = Tcl_GetHashValue(pEntry))) {
+ Tcl_AppendResult(interp, pArchive->zMountPoint, 0);
+ }
+ }
+ return TCL_OK;
+ }
+
+ chan = Tcl_OpenFileChannel(interp, zArchive, "r", 0);
+ if (!chan) {
+ return TCL_ERROR;
+ }
+ if (Tcl_SetChannelOption(interp, chan, "-translation", "binary") != TCL_OK){
+ return TCL_ERROR;
+ }
+ if (Tcl_SetChannelOption(interp, chan, "-encoding", "binary") != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ /* Read the "End Of Central Directory" record from the end of the
+ ** ZIP archive.
+ */
+ iPos = Tcl_Seek(chan, -22, SEEK_END);
+ Tcl_Read(chan, (char*)zBuf, 22);
+ if (memcmp(zBuf, "\120\113\05\06", 4)) {
+ Tcl_AppendResult(interp, "not a ZIP archive", NULL);
+ return TCL_ERROR;
+ }
+
+ /* Construct the archive record
+ */
+ zArchiveName = AbsolutePath(zArchive);
+ pEntry = Tcl_CreateHashEntry(&local.archiveHash, zArchiveName, &isNew);
+ if( !isNew ){
+ pArchive = Tcl_GetHashValue(pEntry);
+ Tcl_AppendResult(interp, "already mounted at ", pArchive->zMountPoint, 0);
+ Tcl_Free(zArchiveName);
+ Tcl_Close(interp, chan);
+ return TCL_ERROR;
+ }
+ pArchive = (ZvfsArchive*)Tcl_Alloc(sizeof(*pArchive) + strlen(zMountPoint)+1);
+ pArchive->zName = zArchiveName;
+ pArchive->zMountPoint = (char*)&pArchive[1];
+ strcpy(pArchive->zMountPoint, zMountPoint);
+ pArchive->pFiles = 0;
+ Tcl_SetHashValue(pEntry, pArchive);
+
+ /* Compute the starting location of the directory for the ZIP archive
+ ** in iPos then seek to that location.
+ */
+ nFile = INT16(zBuf,8);
+ iPos -= INT32(zBuf,12);
+ Tcl_Seek(chan, iPos, SEEK_SET);
+
+ while( nFile-- > 0 ){
+ int lenName; /* Length of the next filename */
+ int lenExtra; /* Length of "extra" data for next file */
+ int iData; /* Offset to start of file data */
+ int dosTime;
+ int dosDate;
+ int isdir;
+ ZvfsFile *pZvfs; /* A new virtual file */
+ char *zFullPath; /* Full pathname of the virtual file */
+ char zName[1024]; /* Space to hold the filename */
+
+ /* Read the next directory entry. Extract the size of the filename,
+ ** the size of the "extra" information, and the offset into the archive
+ ** file of the file data.
+ */
+ Tcl_Read(chan, (char*)zBuf, 46);
+ if (memcmp(zBuf, "\120\113\01\02", 4)) {
+ Tcl_AppendResult(interp, "ill-formed central directory entry", NULL);
+ return TCL_ERROR;
+ }
+ lenName = INT16(zBuf,28);
+ lenExtra = INT16(zBuf,30) + INT16(zBuf,32);
+ iData = INT32(zBuf,42);
+
+
+ /* If the virtual filename is too big to fit in zName[], then skip
+ ** this file
+ */
+ if( lenName >= sizeof(zName) ){
+ Tcl_Seek(chan, lenName + lenExtra, SEEK_CUR);
+ continue;
+ }
+
+ /* Construct an entry in local.fileHash for this virtual file.
+ */
+ Tcl_Read(chan, zName, lenName);
+ isdir=0;
+ if (lenName>0 && zName[lenName-1] == '/') {
+ lenName--;
+ isdir=1;
+ }
+ zName[lenName] = 0;
+ zFullPath = CanonicalPath(zMountPoint, zName);
+ pZvfs = (ZvfsFile*)Tcl_Alloc( sizeof(*pZvfs) );
+ pZvfs->zName = zFullPath;
+ pZvfs->pArchive = pArchive;
+ pZvfs->isdir = isdir;
+ pZvfs->depth=strchrcnt(zFullPath,'/');
+ pZvfs->iOffset = iData;
+ dosDate = INT16(zBuf, 14);
+ dosTime = INT16(zBuf, 12);
+ pZvfs->timestamp = DosTimeDate(dosDate, dosTime);
+ pZvfs->nByte = INT32(zBuf, 24);
+ pZvfs->nByteCompr = INT32(zBuf, 20);
+ pZvfs->pNext = pArchive->pFiles;
+ pArchive->pFiles = pZvfs;
+ pEntry = Tcl_CreateHashEntry(&local.fileHash, zFullPath, &isNew);
+ if( isNew ){
+ pZvfs->pNextName = 0;
+ }else{
+ ZvfsFile *pOld = (ZvfsFile*)Tcl_GetHashValue(pEntry);
+ pOld->pPrevName = pZvfs;
+ pZvfs->pNextName = pOld;
+ }
+ pZvfs->pPrevName = 0;
+ Tcl_SetHashValue(pEntry, (ClientData) pZvfs);
+
+ /* Skip over the extra information so that the next read will be from
+ ** the beginning of the next directory entry.
+ */
+ Tcl_Seek(chan, lenExtra, SEEK_CUR);
+ }
+ Tcl_Close(interp, chan);
+ return TCL_OK;
+}
+
+/*
+** Locate the ZvfsFile structure that corresponds to the file named.
+** Return NULL if there is no such ZvfsFile.
+*/
+static ZvfsFile *ZvfsLookup(char *zFilename)
+{
+ char *zTrueName;
+ Tcl_HashEntry *pEntry;
+ ZvfsFile *pFile;
+
+ if( local.isInit==0 ) return 0;
+ zTrueName = AbsolutePath(zFilename);
+ pEntry = Tcl_FindHashEntry(&local.fileHash, zTrueName);
+ pFile = pEntry ? Tcl_GetHashValue(pEntry) : 0;
+ Tcl_Free(zTrueName);
+ return pFile;
+}
+
+static int ZvfsLookupMount(char *zFilename)
+{
+ char *zTrueName;
+ Tcl_HashEntry *pEntry; /* Hash table entry */
+ Tcl_HashSearch zSearch; /* Search all mount points */
+ ZvfsArchive *pArchive; /* The ZIP archive being mounted */
+ int match=0;
+ if( local.isInit==0 ) return 0;
+ zTrueName = AbsolutePath(zFilename);
+ pEntry=Tcl_FirstHashEntry(&local.archiveHash,&zSearch);
+ while (pEntry) {
+ if ((pArchive = Tcl_GetHashValue(pEntry))) {
+ if (!strcmp(pArchive->zMountPoint,zTrueName)) {
+ match=1;
+ break;
+ }
+ }
+ pEntry=Tcl_NextHashEntry(&zSearch);
+ }
+ Tcl_Free(zTrueName);
+ return match;
+}
+
+
+/*
+** Unmount all the files in the given ZIP archive.
+*/
+static void Zvfs_Unmount(CONST char *zArchive)
+{
+ char *zArchiveName;
+ ZvfsArchive *pArchive;
+ ZvfsFile *pFile, *pNextFile;
+ Tcl_HashEntry *pEntry;
+
+ zArchiveName = AbsolutePath(zArchive);
+ pEntry = Tcl_FindHashEntry(&local.archiveHash, zArchiveName);
+ Tcl_Free(zArchiveName);
+ if( pEntry==0 ) return;
+ pArchive = Tcl_GetHashValue(pEntry);
+ Tcl_DeleteHashEntry(pEntry);
+ Tcl_Free(pArchive->zName);
+ for(pFile=pArchive->pFiles; pFile; pFile=pNextFile){
+ pNextFile = pFile->pNext;
+ if( pFile->pNextName ){
+ pFile->pNextName->pPrevName = pFile->pPrevName;
+ }
+ if( pFile->pPrevName ){
+ pFile->pPrevName->pNextName = pFile->pNextName;
+ }else{
+ pEntry = Tcl_FindHashEntry(&local.fileHash, pFile->zName);
+ if( pEntry==0 ){
+ /* This should never happen */
+ }else if( pFile->pNextName ){
+ Tcl_SetHashValue(pEntry, pFile->pNextName);
+ }else{
+ Tcl_DeleteHashEntry(pEntry);
+ }
+ }
+ Tcl_Free(pFile->zName);
+ Tcl_Free((char*)pFile);
+ }
+}
+
+/*
+** zvfs::mount Zip-archive-name mount-point
+**
+** Create a new mount point on the given ZIP archive. After this
+** command executes, files contained in the ZIP archive will appear
+** to Tcl to be regular files at the mount point.
+*/
+static int ZvfsMountCmd(ClientData clientData, Tcl_Interp *interp,
+ int argc,CONST char *argv[])
+{
+ if( argc>3 ){
+ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+ " ? ZIP-FILE ? MOUNT-POINT ? ?\"", 0);
+ return TCL_ERROR;
+ }
+ return Zvfs_Mount(interp, argc>1?argv[1]:0, argc>2?argv[2]:0);
+}
+
+/*
+** zvfs::unmount Zip-archive-name
+**
+** Undo the effects of zvfs::mount.
+*/
+static int ZvfsUnmountCmd(ClientData clientData, Tcl_Interp *interp,
+ int argc, CONST char *argv[])
+{
+ if( argc!=2 ){
+ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+ " ZIP-FILE\"", 0);
+ return TCL_ERROR;
+ }
+ Zvfs_Unmount(argv[1]);
+ return TCL_OK;
+}
+
+/*
+** zvfs::exists filename
+**
+** Return TRUE if the given filename exists in the ZVFS and FALSE if
+** it does not.
+*/
+static int ZvfsExistsObjCmd(void *NotUsed, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const* objv)
+{
+ char *zFilename;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+ return TCL_ERROR;
+ }
+ zFilename = Tcl_GetStringFromObj(objv[1], 0);
+ Tcl_SetBooleanObj( Tcl_GetObjResult(interp), ZvfsLookup(zFilename)!=0);
+ return TCL_OK;
+}
+
+
+/*
+** zvfs::info filename
+**
+** Return information about the given file in the ZVFS. The information
+** consists of (1) the name of the ZIP archive that contains the file,
+** (2) the size of the file after decompressions, (3) the compressed
+** size of the file, and (4) the offset of the compressed data in the archive.
+*/
+static int ZvfsInfoObjCmd(void *NotUsed, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const* objv)
+{
+ char *zFilename;
+ ZvfsFile *pFile;
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+ return TCL_ERROR;
+ }
+ zFilename = Tcl_GetStringFromObj(objv[1], 0);
+ pFile = ZvfsLookup(zFilename);
+ if( pFile ){
+ Tcl_Obj *pResult = Tcl_GetObjResult(interp);
+ Tcl_ListObjAppendElement(interp, pResult,
+ Tcl_NewStringObj(pFile->pArchive->zName, -1));
+ Tcl_ListObjAppendElement(interp, pResult, Tcl_NewIntObj(pFile->nByte));
+ Tcl_ListObjAppendElement(interp, pResult, Tcl_NewIntObj(pFile->nByteCompr));
+ Tcl_ListObjAppendElement(interp, pResult, Tcl_NewIntObj(pFile->iOffset));
+ }
+ return TCL_OK;
+}
+
+
+/*
+** zvfs::list
+**
+** Return a list of all files in the ZVFS. The order of the names
+** in the list is arbitrary.
+*/
+static int ZvfsListObjCmd(void *NotUsed, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const* objv)
+{
+ char *zPattern = 0;
+ Tcl_RegExp pRegexp = 0;
+ Tcl_HashEntry *pEntry;
+ Tcl_HashSearch sSearch;
+ Tcl_Obj *pResult = Tcl_GetObjResult(interp);
+
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "?(-glob|-regexp)? ?PATTERN?");
+ return TCL_ERROR;
+ }
+ if( local.isInit==0 ) return TCL_OK;
+ if( objc==3 ){
+ int n;
+ char *zSwitch = Tcl_GetStringFromObj(objv[1], &n);
+ if( n>=2 && strncmp(zSwitch,"-glob",n)==0 ){
+ zPattern = Tcl_GetString(objv[2]);
+ }else if( n>=2 && strncmp(zSwitch,"-regexp",n)==0 ){
+ pRegexp = Tcl_RegExpCompile(interp, Tcl_GetString(objv[2]));
+ if( pRegexp==0 ) return TCL_ERROR;
+ }else{
+ Tcl_AppendResult(interp, "unknown option: ", zSwitch, 0);
+ return TCL_ERROR;
+ }
+ }else if( objc==2 ){
+ zPattern = Tcl_GetStringFromObj(objv[1], 0);
+ }
+ if( zPattern ){
+ for(pEntry = Tcl_FirstHashEntry(&local.fileHash, &sSearch);
+ pEntry;
+ pEntry = Tcl_NextHashEntry(&sSearch)
+ ){
+ ZvfsFile *pFile = (ZvfsFile *)Tcl_GetHashValue(pEntry);
+ char *z = pFile->zName;
+ if( Tcl_StringMatch(z, zPattern) ){
+ Tcl_ListObjAppendElement(interp, pResult, Tcl_NewStringObj(z, -1));
+ }
+ }
+ }else if( pRegexp ){
+ for(pEntry = Tcl_FirstHashEntry(&local.fileHash, &sSearch);
+ pEntry;
+ pEntry = Tcl_NextHashEntry(&sSearch)
+ ){
+ ZvfsFile *pFile = (ZvfsFile *)Tcl_GetHashValue(pEntry);
+ char *z = pFile->zName;
+ if( Tcl_RegExpExec(interp, pRegexp, z, z) ){
+ Tcl_ListObjAppendElement(interp, pResult, Tcl_NewStringObj(z, -1));
+ }
+ }
+ }else{
+ for(pEntry = Tcl_FirstHashEntry(&local.fileHash, &sSearch);
+ pEntry;
+ pEntry = Tcl_NextHashEntry(&sSearch)
+ ){
+ ZvfsFile *pFile = (ZvfsFile *)Tcl_GetHashValue(pEntry);
+ char *z = pFile->zName;
+ Tcl_ListObjAppendElement(interp, pResult, Tcl_NewStringObj(z, -1));
+ }
+ }
+ return TCL_OK;
+}
+
+/*
+** Whenever a ZVFS file is opened, an instance of this structure is
+** attached to the open channel where it will be available to the
+** ZVFS I/O routines below. All state information about an open
+** ZVFS file is held in this structure.
+*/
+typedef struct ZvfsChannelInfo {
+ unsigned long nByte; /* number of bytes of uncompressed data */
+ unsigned long nByteCompr; /* number of bytes of unread compressed data */
+ unsigned long nData; /* total number of bytes of compressed data */
+ unsigned long readSoFar; /* position of next byte to be read from the channel */
+ long startOfData; /* File position of start of data in ZIP archive */
+ Tcl_Channel chan; /* Open file handle to the archive file */
+ unsigned char *zBuf; /* buffer used by the decompressor */
+ unsigned char *uBuf; /* pointer to the uncompressed, unencrypted data */
+ z_stream stream; /* state of the decompressor */
+ int isEncrypted; /* file is encrypted */
+ int isCompressed; /* True data is compressed */
+} ZvfsChannelInfo;
+
+/*
+** This routine is called as an exit handler. If we do not set
+** ZvfsChannelInfo.chan to NULL, then Tcl_Close() will be called on
+** that channel a second time when Tcl_Exit runs. This will lead to a
+** core dump.
+*/
+static void vfsExit(void *pArg)
+{
+ ZvfsChannelInfo *pInfo = (ZvfsChannelInfo*)pArg;
+ pInfo->chan = 0;
+}
+
+/*
+** This routine is called when the ZVFS channel is closed
+*/
+static int vfsClose(ClientData instanceData, Tcl_Interp *interp)
+{
+ ZvfsChannelInfo* pInfo = (ZvfsChannelInfo*)instanceData;
+
+ if( pInfo->zBuf ){
+ Tcl_Free((char*)pInfo->zBuf);
+ Tcl_Free((char*)pInfo->uBuf);
+ inflateEnd(&pInfo->stream);
+ }
+ if( pInfo->chan ){
+ Tcl_Close(interp, pInfo->chan);
+ Tcl_DeleteExitHandler(vfsExit, pInfo);
+ }
+ Tcl_Free((char*)pInfo);
+ return TCL_OK;
+}
+
+static int vfsInput(ClientData instanceData, char *buf, int toRead,
+ int *pErrorCode)
+{
+ ZvfsChannelInfo* pInfo = (ZvfsChannelInfo*) instanceData;
+ unsigned long nextpos;
+
+ nextpos = pInfo->readSoFar + toRead;
+ if (nextpos > pInfo->nByte) {
+ toRead = pInfo->nByte - pInfo->readSoFar;
+ nextpos = pInfo->nByte;
+ }
+ if( toRead == 0 )
+ return 0;
+
+ memcpy(buf, pInfo->uBuf + pInfo->readSoFar, toRead);
+
+ pInfo->readSoFar = nextpos;
+ *pErrorCode = 0;
+
+ return toRead;
+}
+
+
+static int vfsRead (ClientData instanceData, unsigned char *buf, int toRead,
+ int *pErrorCode)
+{
+ ZvfsChannelInfo* pInfo = (ZvfsChannelInfo*) instanceData;
+ int len;
+
+ if( (unsigned long)toRead > pInfo->nByte ){
+ toRead = pInfo->nByte;
+ }
+ if( toRead == 0 ){
+ return 0;
+ }
+ if (pInfo->isEncrypted) {
+ /* Make preparations to decrypt the data. */
+
+ /* Read and decrypt the encryption header. */
+ /*
+ crc_32_tab = get_crc_table();
+ init_keys(getPwdKey(pwdbuf));
+ len = Tcl_Read(pInfo->chan, encryptHdr, sizeof(encryptHdr));
+ if (len == sizeof(encryptHdr)) {
+ for (i = 0; i < sizeof(encryptHdr); ++i) {
+ C = encryptHdr[i] ^ decrypt_byte();
+ update_keys(C);
+ }
+
+ }
+ */
+ }
+
+ if( pInfo->isCompressed ){
+ int err = Z_OK;
+ z_stream *stream = &pInfo->stream;
+ stream->next_out = buf;
+ stream->avail_out = toRead;
+ while (stream->avail_out) {
+ if (!stream->avail_in) {
+ len = pInfo->nByteCompr;
+ if (len > COMPR_BUF_SIZE) {
+ len = COMPR_BUF_SIZE;
+ }
+ len = Tcl_Read(pInfo->chan, (char*)pInfo->zBuf, len);
+
+ if (pInfo->isEncrypted) {
+ /* Decrypt the bytes we have just read. */
+ /*
+ for (i = 0; i < len; ++i) {
+ C = pInfo->zBuf[i];
+ temp = C ^ decrypt_byte();
+ update_keys(temp);
+ pInfo->zBuf[i] = temp;
+ }
+ */
+ }
+
+ pInfo->nByteCompr -= len;
+ stream->next_in = pInfo->zBuf;
+ stream->avail_in = len;
+ }
+ err = inflate(stream, Z_NO_FLUSH);
+ if (err) break;
+ }
+ if (err == Z_STREAM_END) {
+ if ((stream->avail_out != 0)) {
+ *pErrorCode = err; /* premature end */
+ return -1;
+ }
+ }else if( err ){
+ *pErrorCode = err; /* some other zlib error */
+ return -1;
+ }
+ }else{
+ toRead = Tcl_Read(pInfo->chan, (char*)buf, toRead);
+ if (pInfo->isEncrypted) {
+ /* Decrypt the bytes we have just read. */
+ /*
+ for (i = 0; i < toRead; ++i) {
+ C = buf[i];
+ temp = C ^ decrypt_byte();
+ update_keys(temp);
+ buf[i] = temp;
+ }
+ */
+ }
+ }
+ pInfo->nByte = toRead;
+ pInfo->readSoFar = 0;
+ *pErrorCode = 0;
+ return toRead;
+}
+
+/*
+** Write to a ZVFS file. ZVFS files are always read-only, so this routine
+** always returns an error.
+*/
+static int vfsOutput(ClientData instanceData, CONST char *buf, int toWrite,
+ int *pErrorCode)
+{
+ *pErrorCode = EINVAL;
+ return -1;
+}
+
+static int vfsSeek(ClientData instanceData, long offset, int mode,
+ int *pErrorCode)
+{
+ ZvfsChannelInfo* pInfo = (ZvfsChannelInfo*) instanceData;
+
+ switch( mode ){
+ case SEEK_CUR: {
+ offset += pInfo->readSoFar;
+ break;
+ }
+ case SEEK_END: {
+ offset += pInfo->nByte - 1;
+ break;
+ }
+ default: {
+ /* Do nothing */
+ break;
+ }
+ }
+ /* Don't seek past end of data */
+ if (pInfo->nByte < (unsigned long)offset)
+ return -1;
+
+ /* Don't seek before the start of data */
+ if (offset < 0)
+ return -1;
+
+ pInfo->readSoFar = (unsigned long)offset;
+ return pInfo->readSoFar;
+}
+
+/*
+** Handle events on the channel. ZVFS files do not generate events,
+** so this is a no-op.
+*/
+static void vfsWatchChannel(ClientData instanceData, int mask)
+{
+ return;
+}
+
+/*
+** Called to retrieve the underlying file handle for this ZVFS file.
+** As the ZVFS file has no underlying file handle, this is a no-op.
+*/
+static int vfsGetFile(ClientData instanceData, int direction,
+ ClientData* handlePtr)
+{
+ return TCL_ERROR;
+}
+
+/*
+** This structure describes the channel type structure for
+** access to the ZVFS.
+*/
+static Tcl_ChannelType vfsChannelType = {
+ "vfs", /* Type name. */
+ NULL, /* Set blocking/nonblocking behaviour. NULL'able */
+ vfsClose, /* Close channel, clean instance data */
+ vfsInput, /* Handle read request */
+ vfsOutput, /* Handle write request */
+ vfsSeek, /* Move location of access point. NULL'able */
+ NULL, /* Set options. NULL'able */
+ NULL, /* Get options. NULL'able */
+ vfsWatchChannel, /* Initialize notifier */
+ vfsGetFile /* Get OS handle from the channel. */
+};
+
+/*
+** This routine attempts to do an open of a file. Check to see
+** if the file is located in the ZVFS. If so, then open a channel
+** for reading the file. If not, return NULL.
+*/
+static Tcl_Channel ZvfsFileOpen(Tcl_Interp *interp, char *zFilename,
+ char *modeString, int permissions)
+{
+ ZvfsFile *pFile;
+ ZvfsChannelInfo *pInfo;
+ Tcl_Channel chan;
+ static int count = 1;
+ char zName[50];
+ unsigned char zBuf[50];
+ int errCode;
+
+ pFile = ZvfsLookup(zFilename);
+ if( pFile==0 ) return NULL;
+ chan = Tcl_OpenFileChannel(interp, pFile->pArchive->zName, "r", 0);
+ if (local.firstMount == NULL)
+ local.firstMount = pFile->pArchive->zName;
+ if( chan==0 ){
+ return 0;
+ }
+ if( Tcl_SetChannelOption(interp, chan, "-translation", "binary")
+ || Tcl_SetChannelOption(interp, chan, "-encoding", "binary")
+ ){
+ /* this should never happen */
+ Tcl_Close(0, chan);
+ return 0;
+ }
+ Tcl_Seek(chan, pFile->iOffset, SEEK_SET);
+ Tcl_Read(chan, (char*)zBuf, 30);
+ if( memcmp(zBuf, "\120\113\03\04", 4) ){
+ if( interp ){
+ Tcl_AppendResult(interp, "local header mismatch: ", NULL);
+ }
+ Tcl_Close(interp, chan);
+ return 0;
+ }
+ pInfo = (ZvfsChannelInfo*)Tcl_Alloc( sizeof(*pInfo) );
+ pInfo->chan = chan;
+ Tcl_CreateExitHandler(vfsExit, pInfo);
+ pInfo->isEncrypted = zBuf[6] & 1;
+ if (pFile->pArchive->zName == local.firstMount) {
+ /* FreeWrap specific.
+ We are opening a file from the executable.
+ All such files must be encrypted.
+ */
+ if (!pInfo->isEncrypted) {
+ /* The file is not encrypted.
+ Someone must have tampered with the application.
+ Let's exit the program.
+ */
+ /*
+ printf("This application has an unauthorized modification. Exiting immediately\n");
+ exit(-10);
+ */
+ }
+ }
+ pInfo->isCompressed = INT16(zBuf, 8);
+ if (pInfo->isCompressed ){
+ z_stream *stream = &pInfo->stream;
+ pInfo->zBuf = (unsigned char*)Tcl_Alloc(COMPR_BUF_SIZE);
+ stream->zalloc = (alloc_func)0;
+ stream->zfree = (free_func)0;
+ stream->opaque = (voidpf)0;
+ stream->avail_in = 2;
+ stream->next_in = pInfo->zBuf;
+ pInfo->zBuf[0] = 0x78;
+ pInfo->zBuf[1] = 0x01;
+ inflateInit(&pInfo->stream);
+ }
+ else {
+ pInfo->zBuf = 0;
+ }
+ pInfo->nByte = INT32(zBuf,22);
+ pInfo->nByteCompr = pInfo->nData = INT32(zBuf,18);
+ pInfo->readSoFar = 0;
+ Tcl_Seek(chan, INT16(zBuf,26)+INT16(zBuf,28), SEEK_CUR);
+ pInfo->startOfData = Tcl_Tell(chan);
+ sprintf(zName,"vfs_%x_%x",((int)pFile)>>12,count++);
+ chan = Tcl_CreateChannel(&vfsChannelType, zName,
+ (ClientData)pInfo, TCL_READABLE);
+
+ pInfo->uBuf = (unsigned char*)Tcl_Alloc(pInfo->nByte);
+ /* Read and decompress the file contents */
+ if (pInfo->uBuf) {
+ pInfo->uBuf[0] = 0;
+ vfsRead(pInfo, pInfo->uBuf, pInfo->nByte, &errCode);
+ pInfo->readSoFar = 0;
+ }
+
+ return chan;
+}
+
+/*
+** This routine does a stat() system call for a ZVFS file.
+*/
+static int ZvfsFileStat(char *path, Tcl_StatBuf *buf)
+{
+ ZvfsFile *pFile;
+
+ pFile = ZvfsLookup(path);
+ if( pFile==0 ){
+ return -1;
+ }
+ memset(buf, 0, sizeof(Tcl_StatBuf));
+ if (pFile->isdir)
+ buf->st_mode = 040555;
+ else
+ buf->st_mode = 0100555;
+ buf->st_size = pFile->nByte;
+ buf->st_mtime = pFile->timestamp;
+ buf->st_ctime = pFile->timestamp;
+ buf->st_atime = pFile->timestamp;
+ return 0;
+}
+
+/*
+** This routine does an access() system call for a ZVFS file.
+*/
+static int ZvfsFileAccess(char *path, int mode)
+{
+ ZvfsFile *pFile;
+
+ if( mode & 3 ){
+ return -1;
+ }
+ pFile = ZvfsLookup(path);
+ if( pFile==0 ){
+ return -1;
+ }
+ return 0;
+}
+
+Tcl_Channel Tobe_FSOpenFileChannelProc _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *pathPtr,int mode, int permissions))
+{
+ int len;
+ /* if (mode != O_RDONLY) return NULL; */
+ return ZvfsFileOpen(interp, Tcl_GetStringFromObj(pathPtr,&len), 0,
+ permissions);
+}
+
+/*
+** This routine does a stat() system call for a ZVFS file.
+*/
+int Tobe_FSStatProc _ANSI_ARGS_((Tcl_Obj *pathPtr, Tcl_StatBuf *buf))
+{
+ int len;
+ return ZvfsFileStat(Tcl_GetStringFromObj(pathPtr,&len), buf);
+}
+
+/*
+** This routine does an access() system call for a ZVFS file.
+*/
+int Tobe_FSAccessProc _ANSI_ARGS_((Tcl_Obj *pathPtr, int mode))
+{
+ int len;
+ return ZvfsFileAccess(Tcl_GetStringFromObj(pathPtr,&len), mode);
+}
+
+
+/* Tcl_Obj* Tobe_FSFilesystemSeparatorProc
+ _ANSI_ARGS_((Tcl_Obj *pathPtr)) {
+ return Tcl_NewStringObj("/",-1);;
+ } */
+/* Function to process a
+ * 'Tobe_FSMatchInDirectory()'. If not
+ * implemented, then glob and recursive
+ * copy functionality will be lacking in
+ * the filesystem. */
+int Tobe_FSMatchInDirectoryProc _ANSI_ARGS_((Tcl_Interp* interp, Tcl_Obj *result, Tcl_Obj *pathPtr, CONST char *pattern, Tcl_GlobTypeData * types))
+{
+ Tcl_HashEntry *pEntry;
+ Tcl_HashSearch sSearch;
+ int scnt, len, l;
+ char *zPattern, *z=Tcl_GetStringFromObj(pathPtr,&len);
+
+ if (!pattern) return TCL_ERROR;
+ l=strlen(pattern);
+ if (!z)
+ zPattern=strdup(pattern);
+ else {
+ zPattern=(char*)malloc(len+l+2);
+ memcpy(zPattern,z,len);
+ if (len > 1 || zPattern[0] != '/')
+ {
+ zPattern[len] = '/';
+ ++len;
+ }
+ memcpy(zPattern+len,pattern,l+1);
+ }
+ scnt=strchrcnt(zPattern,'/');
+ for(pEntry = Tcl_FirstHashEntry(&local.fileHash, &sSearch);
+ pEntry;
+ pEntry = Tcl_NextHashEntry(&sSearch)
+ ){
+ ZvfsFile *pFile = Tcl_GetHashValue(pEntry);
+ char *z = pFile->zName;
+ if (Tcl_StringCaseMatch(z, zPattern, 0) && scnt==pFile->depth) {
+ Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z, -1));
+ }
+ }
+ free(zPattern);
+ return TCL_OK;
+}
+
+/* Function to check whether a path is in
+ * this filesystem. This is the most
+ * important filesystem procedure. */
+int Tobe_FSPathInFilesystemProc _ANSI_ARGS_((Tcl_Obj *pathPtr, ClientData *clientDataPtr))
+{
+ int len;
+ char *path;
+
+ path = Tcl_GetStringFromObj(pathPtr,&len);
+ if (ZvfsLookup(path)!=0)
+ return TCL_OK;
+
+ return -1;
+}
+
+Tcl_Obj *Tobe_FSListVolumesProc _ANSI_ARGS_((void))
+{
+ Tcl_HashEntry *pEntry; /* Hash table entry */
+ Tcl_HashSearch zSearch; /* Search all mount points */
+ ZvfsArchive *pArchive; /* The ZIP archive being mounted */
+ Tcl_Obj *pVols=0, *pVol;
+ char mountpt[200];
+
+ pEntry=Tcl_FirstHashEntry(&local.archiveHash,&zSearch);
+ while (pEntry) {
+ if ((pArchive = Tcl_GetHashValue(pEntry))) {
+ if (!pVols) {
+ pVols=Tcl_NewListObj(0,0);
+ Tcl_IncrRefCount(pVols);
+ }
+ sprintf(mountpt, "zvfs:%s", pArchive->zMountPoint);
+ pVol=Tcl_NewStringObj(mountpt,-1);
+ Tcl_IncrRefCount(pVol);
+ Tcl_ListObjAppendElement(NULL, pVols,pVol);
+ /* Tcl_AppendResult(interp,pArchive->zMountPoint," ",pArchive->zName," ",0);*/
+ }
+ pEntry=Tcl_NextHashEntry(&zSearch);
+ }
+ return pVols;
+}
+
+int Tobe_FSChdirProc _ANSI_ARGS_((Tcl_Obj *pathPtr))
+{
+ /* Someday, we should actually check if this is a valid path. */
+ return TCL_OK;
+}
+
+static CONST char *TobeAttrs[] = { "uncompsize", "compsize", "offset", "mount", "archive", 0 };
+
+CONST char* const *Tobe_FSFileAttrStringsProc _ANSI_ARGS_((Tcl_Obj *pathPtr, Tcl_Obj** objPtrRef))
+{
+ return TobeAttrs;
+}
+
+int Tobe_FSFileAttrsGetProc _ANSI_ARGS_((Tcl_Interp *interp, int index, Tcl_Obj *pathPtr,Tcl_Obj **objPtrRef))
+{
+ char *zFilename;
+ ZvfsFile *pFile;
+ zFilename = Tcl_GetString(pathPtr);
+ pFile = ZvfsLookup(zFilename);
+ if(!pFile)
+ return TCL_ERROR;
+ switch (index) {
+ case 0:
+ *objPtrRef=Tcl_NewIntObj(pFile->nByteCompr);
+ return TCL_OK;
+ case 1:
+ *objPtrRef= Tcl_NewIntObj(pFile->nByte);
+ return TCL_OK;
+ case 2:
+ *objPtrRef= Tcl_NewIntObj(pFile->nByte);
+ return TCL_OK;
+ case 3:
+ *objPtrRef= Tcl_NewStringObj(pFile->pArchive->zMountPoint,-1);
+ return TCL_OK;
+ case 4:
+ *objPtrRef= Tcl_NewStringObj(pFile->pArchive->zName,-1);
+ return TCL_OK;
+ default:
+ return TCL_ERROR;
+ }
+ return TCL_OK;
+}
+
+int Tobe_FSFileAttrsSetProc _ANSI_ARGS_((Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj *objPtr))
+{
+ return TCL_ERROR;
+}
+
+Tcl_Obj* Tobe_FSFilesystemPathTypeProc _ANSI_ARGS_((Tcl_Obj *pathPtr))
+{
+ return Tcl_NewStringObj("zip",-1);
+}
+/****************************************************/
+
+// At some point, some of the following might get implemented?
+
+#define Tobe_FSFilesystemSeparatorProc 0
+#define Tobe_FSLoadFileProc 0
+#define Tobe_FSUnloadFileProc 0
+#define Tobe_FSGetCwdProc 0
+#define Tobe_FSGetCwdProc 0
+#define Tobe_FSCreateDirectoryProc 0
+#define Tobe_FSDeleteFileProc 0
+#define Tobe_FSCopyDirectoryProc 0
+#define Tobe_FSCopyFileProc 0
+#define Tobe_FSRemoveDirectoryProc 0
+#define Tobe_FSNormalizePathProc 0
+#define Tobe_FSUtimeProc 0
+#define Tobe_FSRenameFileProc 0
+#define Tobe_FSCreateInternalRepProc 0
+#define Tobe_FSInternalToNormalizedProc 0
+#define Tobe_FSDupInternalRepProc 0
+#define Tobe_FSFreeInternalRepProc 0
+#define Tobe_FSLinkProc 0
+
+static Tcl_Filesystem Tobe_Filesystem = {
+ "zvfs", /* The name of the filesystem. */
+ sizeof(Tcl_Filesystem), /* Length of this structure, so future
+ * binary compatibility can be assured. */
+ TCL_FILESYSTEM_VERSION_1,
+ /* Version of the filesystem type. */
+ Tobe_FSPathInFilesystemProc,
+ /* Function to check whether a path is in
+ * this filesystem. This is the most
+ * important filesystem procedure. */
+ Tobe_FSDupInternalRepProc,
+ /* Function to duplicate internal fs rep. May
+ * be NULL (but then fs is less efficient). */
+ Tobe_FSFreeInternalRepProc,
+ /* Function to free internal fs rep. Must
+ * be implemented, if internal representations
+ * need freeing, otherwise it can be NULL. */
+ Tobe_FSInternalToNormalizedProc,
+ /* Function to convert internal representation
+ * to a normalized path. Only required if
+ * the fs creates pure path objects with no
+ * string/path representation. */
+ Tobe_FSCreateInternalRepProc,
+ /* Function to create a filesystem-specific
+ * internal representation. May be NULL
+ * if paths have no internal representation,
+ * or if the Tobe_FSPathInFilesystemProc
+ * for this filesystem always immediately
+ * creates an internal representation for
+ * paths it accepts. */
+ Tobe_FSNormalizePathProc,
+ /* Function to normalize a path. Should
+ * be implemented for all filesystems
+ * which can have multiple string
+ * representations for the same path
+ * object. */
+ Tobe_FSFilesystemPathTypeProc,
+ /* Function to determine the type of a
+ * path in this filesystem. May be NULL. */
+ Tobe_FSFilesystemSeparatorProc,
+ /* Function to return the separator
+ * character(s) for this filesystem. Must
+ * be implemented. */
+ Tobe_FSStatProc,
+ /*
+ * Function to process a 'Tobe_FSStat()'
+ * call. Must be implemented for any
+ * reasonable filesystem.
+ */
+ Tobe_FSAccessProc,
+ /*
+ * Function to process a 'Tobe_FSAccess()'
+ * call. Must be implemented for any
+ * reasonable filesystem.
+ */
+ Tobe_FSOpenFileChannelProc,
+ /*
+ * Function to process a
+ * 'Tobe_FSOpenFileChannel()' call. Must be
+ * implemented for any reasonable
+ * filesystem.
+ */
+ Tobe_FSMatchInDirectoryProc,
+ /* Function to process a
+ * 'Tobe_FSMatchInDirectory()'. If not
+ * implemented, then glob and recursive
+ * copy functionality will be lacking in
+ * the filesystem. */
+ Tobe_FSUtimeProc,
+ /* Function to process a
+ * 'Tobe_FSUtime()' call. Required to
+ * allow setting (not reading) of times
+ * with 'file mtime', 'file atime' and
+ * the open-r/open-w/fcopy implementation
+ * of 'file copy'. */
+ Tobe_FSLinkProc,
+ /* Function to process a
+ * 'Tobe_FSLink()' call. Should be
+ * implemented only if the filesystem supports
+ * links. */
+ Tobe_FSListVolumesProc,
+ /* Function to list any filesystem volumes
+ * added by this filesystem. Should be
+ * implemented only if the filesystem adds
+ * volumes at the head of the filesystem. */
+ Tobe_FSFileAttrStringsProc,
+ /* Function to list all attributes strings
+ * which are valid for this filesystem.
+ * If not implemented the filesystem will
+ * not support the 'file attributes' command.
+ * This allows arbitrary additional information
+ * to be attached to files in the filesystem. */
+ Tobe_FSFileAttrsGetProc,
+ /* Function to process a
+ * 'Tobe_FSFileAttrsGet()' call, used by
+ * 'file attributes'. */
+ Tobe_FSFileAttrsSetProc,
+ /* Function to process a
+ * 'Tobe_FSFileAttrsSet()' call, used by
+ * 'file attributes'. */
+ Tobe_FSCreateDirectoryProc,
+ /* Function to process a
+ * 'Tobe_FSCreateDirectory()' call. Should
+ * be implemented unless the FS is
+ * read-only. */
+ Tobe_FSRemoveDirectoryProc,
+ /* Function to process a
+ * 'Tobe_FSRemoveDirectory()' call. Should
+ * be implemented unless the FS is
+ * read-only. */
+ Tobe_FSDeleteFileProc,
+ /* Function to process a
+ * 'Tobe_FSDeleteFile()' call. Should
+ * be implemented unless the FS is
+ * read-only. */
+ Tobe_FSCopyFileProc,
+ /* Function to process a
+ * 'Tobe_FSCopyFile()' call. If not
+ * implemented Tcl will fall back
+ * on open-r, open-w and fcopy as
+ * a copying mechanism. */
+ Tobe_FSRenameFileProc,
+ /* Function to process a
+ * 'Tobe_FSRenameFile()' call. If not
+ * implemented, Tcl will fall back on
+ * a copy and delete mechanism. */
+ Tobe_FSCopyDirectoryProc,
+ /* Function to process a
+ * 'Tobe_FSCopyDirectory()' call. If
+ * not implemented, Tcl will fall back
+ * on a recursive create-dir, file copy
+ * mechanism. */
+ Tobe_FSLoadFileProc,
+ /* Function to process a
+ * 'Tobe_FSLoadFile()' call. If not
+ * implemented, Tcl will fall back on
+ * a copy to native-temp followed by a
+ * Tobe_FSLoadFile on that temporary copy. */
+ Tobe_FSUnloadFileProc,
+ /* Function to unload a previously
+ * successfully loaded file. If load was
+ * implemented, then this should also be
+ * implemented, if there is any cleanup
+ * action required. */
+ Tobe_FSGetCwdProc,
+ /*
+ * Function to process a 'Tobe_FSGetCwd()'
+ * call. Most filesystems need not
+ * implement this. It will usually only be
+ * called once, if 'getcwd' is called
+ * before 'chdir'. May be NULL.
+ */
+ Tobe_FSChdirProc,
+ /*
+ * Function to process a 'Tobe_FSChdir()'
+ * call. If filesystems do not implement
+ * this, it will be emulated by a series of
+ * directory access checks. Otherwise,
+ * virtual filesystems which do implement
+ * it need only respond with a positive
+ * return result if the dirName is a valid
+ * directory in their filesystem. They
+ * need not remember the result, since that
+ * will be automatically remembered for use
+ * by GetCwd. Real filesystems should
+ * carry out the correct action (i.e. call
+ * the correct system 'chdir' api). If not
+ * implemented, then 'cd' and 'pwd' will
+ * fail inside the filesystem.
+ */
+};
+
+void (*Zvfs_PostInit)(Tcl_Interp *)=0;
+
+/*
+** Initialize the ZVFS system.
+*/
+int Zvfs_doInit(Tcl_Interp *interp, int safe)
+{
+ int n;
+
+ if (Tcl_InitStubs(interp, TCL_PATCH_LEVEL, 0) == NULL)
+ return TCL_ERROR;
+
+ if (!safe) {
+ Tcl_CreateCommand(interp, "zvfs::mount", ZvfsMountCmd,
+ (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
+ Tcl_CreateCommand(interp, "zvfs::unmount", ZvfsUnmountCmd,
+ (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
+ }
+
+ if (Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION) != TCL_OK)
+ return TCL_ERROR;
+
+ if( !local.isInit ){
+ /* One-time initialization of the ZVFS */
+ n = Tcl_FSRegister(0, &Tobe_Filesystem);
+ Tcl_InitHashTable(&local.fileHash, TCL_STRING_KEYS);
+ Tcl_InitHashTable(&local.archiveHash, TCL_STRING_KEYS);
+ Tcl_CreateObjCommand(interp, "zvfs::exists", ZvfsExistsObjCmd, 0, 0);
+ Tcl_CreateObjCommand(interp, "zvfs::info", ZvfsInfoObjCmd, 0, 0);
+ Tcl_CreateObjCommand(interp, "zvfs::list", ZvfsListObjCmd, 0, 0);
+
+ local.isInit = 1;
+ }
+
+ if (Zvfs_PostInit)
+ Zvfs_PostInit(interp);
+
+ return TCL_OK;
+}
+
+int Zvfs_Init(Tcl_Interp *interp){
+ return Zvfs_doInit(interp,0);
+}
+
+int Zvfs_SafeInit(Tcl_Interp *interp){
+ return Zvfs_doInit(interp,1);
+}
+