/*
** Copyright (c) 2000 D. Richard Hipp
** Copyright (c) 2007 PDQ Interfaces Inc.
** Copyright (c) 2013 Sean Woods
**
** This file is now released under the BSD style license
** outlined in the included file license.terms.
**
*************************************************************************
** 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.
**
** Well, almost... Actually, the virtual file system is limited
** in a number of ways. The only things you can do are "stat"
** and "read" file content files. You cannot use "cd".
** But it turns out that "stat"
** and "read" are sufficient for most purposes.
**
** This version has been modified to run under Tcl 8.6
**
*/
#include "tcl.h"
#include <ctype.h>
#include <zlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#ifdef TCL_FILESYSTEM_VERSION_1
#define USE_TCL_VFS 1
#endif
/*
** Size of the decompression input buffer
*/
#define COMPR_BUF_SIZE 8192
static int maptolower=0;
static int openarch=0; /* Set to 1 when opening archive. */
/*
** 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 */
} 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 */
time_t timestamp; /* Modification time */
int isdir; /* Set to 2 if directory, or 1 if mount */
int depth; /* Number of slashes in path. */
int permissions; /* File permissions. */
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 */
} ZvfsFile;
/*
** Information about each file within a ZIP archive is stored in
** an instance of the following structure. A list of these structures
** forms a table of contents for the archive.
*/
typedef struct ZFile ZFile;
struct ZFile {
char *zName; /* Name of the file */
int isSpecial; /* Not really a file in the ZIP archive */
int dosTime; /* Modification time (DOS format) */
int dosDate; /* Modification date (DOS format) */
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 nExtra; /* Extra space in the TOC header */
int iCRC; /* Cyclic Redundancy Check of the data */
int permissions; /* File permissions. */
int flags; /* Deletion = bit 0. */
ZFile *pNext; /* Next file in the same archive */
};
EXTERN int Tcl_Zvfs_Mount(Tcl_Interp *interp,CONST char *zArchive,CONST char *zMountPoint);
EXTERN int Tcl_Zvfs_Umount(CONST char *zArchive);
EXTERN int Tcl_Zvfs_Init(Tcl_Interp *interp);
EXTERN int Tcl_Zvfs_SafeInit(Tcl_Interp *interp);
/*
** 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))
/*
** Write a 16- or 32-bit integer as little-endian into the given buffer.
*/
static void put16(char *z, int v){
z[0] = v & 0xff;
z[1] = (v>>8) & 0xff;
}
static void put32(char *z, int v){
z[0] = v & 0xff;
z[1] = (v>>8) & 0xff;
z[2] = (v>>16) & 0xff;
z[3] = (v>>24) & 0xff;
}
/*
** Make a new ZFile structure with space to hold a name of the number of
** characters given. Return a pointer to the new structure.
*/
static ZFile *newZFile(int nName, ZFile **ppList){
ZFile *pNew;
pNew = (ZFile*)Tcl_Alloc( sizeof(*pNew) + nName + 1 );
memset(pNew, 0, sizeof(*pNew));
pNew->zName = (char*)&pNew[1];
pNew->pNext = *ppList;
*ppList = pNew;
return pNew;
}
/*
** Delete an entire list of ZFile structures
*/
static void deleteZFileList(ZFile *pList){
ZFile *pNext;
while( pList ){
pNext = pList->pNext;
Tcl_Free((char*)pList);
pList = pNext;
}
}
/* Convert DOS time to unix time. */
static void UnixTimeDate(struct tm *tm, int *dosDate, int *dosTime){
*dosDate = ((((tm->tm_year-80)<<9)&0xfe00) | (((tm->tm_mon+1)<<5)&0x1e0) | (tm->tm_mday&0x1f));
*dosTime = (((tm->tm_hour<<11)&0xf800) | ((tm->tm_min<<5)&0x7e0) | (tm->tm_sec&0x1f));
}
/* 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);
tm->tm_mday=(dosDate & 0x1f);
tm->tm_hour=(dosTime&0xf800)>>11;
tm->tm_min=(dosTime&0x7e0)>>5;
tm->tm_sec=(dosTime&0x1f);
return mktime(tm);
}
/*
** Translate a DOS time and date stamp into a human-readable string.
*/
static void translateDosTimeDate(char *zStr, int dosDate, int dosTime){
static char *zMonth[] = { "nil",
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
};
sprintf(zStr, "%02d-%s-%d %02d:%02d:%02d",
dosDate & 0x1f,
zMonth[ ((dosDate&0x1e0)>>5) ],
((dosDate&0xfe00)>>9) + 1980,
(dosTime&0xf800)>>11,
(dosTime&0x7e)>>5,
dosTime&0x1f
);
}
/* 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 form 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++; }
#endif
if( zTail[0]=='/' ){ zRoot = ""; zTail++; }
zPath = Tcl_Alloc( strlen(zRoot) + strlen(zTail) + 2 );
if( zPath==0 ) return 0;
if (zTail[0]) {
sprintf(zPath, "%s/%s", zRoot, zTail);
} else {
strcpy(zPath, zRoot);
}
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++] = '/'; }
// if (j>1 && zPath[j-1] == '/') j--;
zPath[j] = 0;
return zPath;
}
/*
** Construct an absolute pathname where memory is obtained from Tcl_Alloc
** that means the same file as the pathname given.
*/
static char *AbsolutePath(const char *zRelative){
Tcl_DString pwd;
char *zResult;
int len;
Tcl_DStringInit(&pwd);
if (zRelative[0] == '~' && zRelative[1] == '/') {
/* TODO: do this for all paths??? */
if (Tcl_TranslateFileName(0, zRelative, &pwd) != NULL) {
zResult = CanonicalPath( "", Tcl_DStringValue(&pwd));
goto done;
}
} else if( zRelative[0] != '/'){
#ifdef __WIN32__
if(!(zRelative[0]=='\\' || (zRelative[0] && zRelative[1] == ':'))) {
/*Tcl_GetCwd(0, &pwd); */
}
#else
Tcl_GetCwd(0, &pwd);
#endif
}
zResult = CanonicalPath( Tcl_DStringValue(&pwd), zRelative);
done:
Tcl_DStringFree(&pwd);
len=strlen(zResult);
if (len > 0 && zResult[len-1] == '/')
zResult[len-1] = 0;
return zResult;
}
int ZvfsReadTOCStart(
Tcl_Interp *interp, /* Leave error messages in this interpreter */
Tcl_Channel chan,
ZFile **pList,
int *iStart
) {
char *zArchiveName = 0; /* A copy of zArchive */
int nFile; /* Number of files in the archive */
int 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 */
ZFile *p;
int zipStart;
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, zBuf, 22);
if (memcmp(zBuf, "\120\113\05\06", 4)) {
/* Tcl_AppendResult(interp, "not a ZIP archive", NULL); */
return TCL_BREAK;
}
/* Compute the starting location of the directory for the ZIP archive
** in iPos then seek to that location.
*/
zipStart = iPos;
nFile = INT16(zBuf,8);
iPos -= INT32(zBuf,12);
Tcl_Seek(chan, iPos, SEEK_SET);
while(1) {
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 */
if (nFile-- <= 0 ){
break;
}
/* 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, 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 (iData<zipStart) {
zipStart = iData;
}
p = newZFile(lenName, pList);
if (!p) break;
Tcl_Read(chan, p->zName, lenName);
p->zName[lenName] = 0;
if (lenName>0 && p->zName[lenName-1] == '/') {
p->isSpecial = 1;
}
p->dosDate = INT16(zBuf, 14);
p->dosTime = INT16(zBuf, 12);
p->nByteCompr = INT32(zBuf, 20);
p->nByte = INT32(zBuf, 24);
p->nExtra = INT32(zBuf, 28);
p->iCRC = INT32(zBuf, 32);
if (nFile < 0)
break;
/* 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);
}
*iStart = zipStart;
return TCL_OK;
}
int ZvfsReadTOC(
Tcl_Interp *interp, /* Leave error messages in this interpreter */
Tcl_Channel chan,
ZFile **pList
) {
int iStart;
return ZvfsReadTOCStart( interp, chan, pList, &iStart);
}
/*
** 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 Tcl_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 */
char *zTrueName = 0; /* A copy of zMountPoint */
int nFile; /* Number of files in the archive */
int 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 */
unsigned int startZip;
if( !local.isInit ) return TCL_ERROR;
/* If null archive name, return all current mounts. */
if (!zArchive) {
Tcl_DString dStr;
Tcl_DStringInit(&dStr);
pEntry=Tcl_FirstHashEntry(&local.archiveHash,&zSearch);
while (pEntry) {
if (pArchive = Tcl_GetHashValue(pEntry)) {
Tcl_DStringAppendElement(&dStr, pArchive->zName);
Tcl_DStringAppendElement(&dStr, pArchive->zMountPoint);
}
pEntry=Tcl_NextHashEntry(&zSearch);
}
Tcl_DStringResult(interp, &dStr);
return TCL_OK;
}
/* If null mount, return mount point. */
// TODO: cleanup allocations of Absolute() path.
if (!zMountPoint) {
zTrueName=AbsolutePath(zArchive);
pEntry = Tcl_FindHashEntry(&local.archiveHash,zTrueName);
if (pEntry) {
if (pArchive = Tcl_GetHashValue(pEntry)) {
Tcl_AppendResult(interp, pArchive->zMountPoint, 0);
}
}
Tcl_Free(zTrueName);
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, 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;
}
if (!*zMountPoint) {
/* Empty string is the special case of mounting on itself. */
zMountPoint = zTrueName = AbsolutePath(zArchive);
}
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);
startZip = iPos;
while(1) {
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 */
if (nFile-- <= 0 ){
isdir = 1;
zFullPath = CanonicalPath(zMountPoint, "");
iData = startZip;
goto addentry;
}
/* 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, zBuf, 46);
if (memcmp(zBuf, "\120\113\01\02", 4)) {
Tcl_AppendResult(interp, "ill-formed central directory entry", NULL);
if (zTrueName)
Tcl_Free(zTrueName);
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=2;
}
zName[lenName] = 0;
zFullPath = CanonicalPath(zMountPoint, zName);
addentry:
pZvfs = (ZvfsFile*)Tcl_Alloc( sizeof(*pZvfs) );
pZvfs->zName = zFullPath;
pZvfs->pArchive = pArchive;
pZvfs->isdir = isdir;
pZvfs->depth=strchrcnt(zFullPath,'/');
pZvfs->iOffset = iData;
if (iData<startZip) {
startZip = 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;
pZvfs->permissions = (0xffff&(INT32(zBuf, 38) >> 16));
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);
if (nFile < 0)
break;
/* 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);
done:
if (zTrueName)
Tcl_Free(zTrueName);
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.
*/
int Tcl_Zvfs_Umount(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 0;
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);
}
return 1;
}
static void Zvfs_Unmount(CONST char *zArchive){
Tcl_Zvfs_Umount(zArchive);
}
/*
** 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.
**
** With no mount-point, return mount point for archive.
** With no archive, return all archive/mount pairs.
** If mount-point is specified as an empty string, mount on file path.
**
*/
static int ZvfsMountCmd(
ClientData clientData, /* Client data for this command */
Tcl_Interp *interp, /* The interpreter used to report errors */
int argc, /* Number of arguments */
CONST char *argv[] /* Values of all arguments */
){
if( argc>3 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ? ZIP-FILE ? MOUNT-POINT ? ?\"", 0);
return TCL_ERROR;
}
return Tcl_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, /* Client data for this command */
Tcl_Interp *interp, /* The interpreter used to report errors */
int argc, /* Number of arguments */
CONST char *argv[] /* Values of all arguments */
){
ZvfsArchive *pArchive; /* The ZIP archive being mounted */
Tcl_HashEntry *pEntry; /* Hash table entry */
Tcl_HashSearch zSearch; /* Search all mount points */
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ZIP-FILE\"", 0);
return TCL_ERROR;
}
if (Tcl_Zvfs_Umount(argv[1])) {
return TCL_OK;
}
if( !local.isInit ) return TCL_ERROR;
pEntry=Tcl_FirstHashEntry(&local.archiveHash,&zSearch);
while (pEntry) {
if (((pArchive = Tcl_GetHashValue(pEntry)))
&& pArchive->zMountPoint[0]
&& (strcmp(pArchive->zMountPoint, argv[1]) == 0)) {
if (Tcl_Zvfs_Umount(pArchive->zName)) {
return TCL_OK;
}
break;
}
pEntry=Tcl_NextHashEntry(&zSearch);
}
Tcl_AppendResult( interp, "unknown zvfs mount point or file: ", argv[1], 0);
return TCL_ERROR;
}
/*
** zvfs::exists filename
**
** Return TRUE if the given filename exists in the ZVFS and FALSE if
** it does not.
*/
static int ZvfsExistsObjCmd(
ClientData clientData, /* Client data for this command */
Tcl_Interp *interp, /* The interpreter used to report errors */
int objc, /* Number of arguments */
Tcl_Obj *CONST* objv /* Values of all arguments */
){
char *zFilename;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
return TCL_ERROR;
}
zFilename = Tcl_GetString(objv[1]);
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.
**
** Note: querying the mount point gives the start of zip data offset in
** (4), which can be used to truncate the zip info off an executable.
*/
static int ZvfsInfoObjCmd(
ClientData clientData, /* Client data for this command */
Tcl_Interp *interp, /* The interpreter used to report errors */
int objc, /* Number of arguments */
Tcl_Obj *const* objv /* Values of all arguments */
){
char *zFilename;
ZvfsFile *pFile;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
return TCL_ERROR;
}
zFilename = Tcl_GetString(objv[1]);
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(
ClientData clientData, /* Client data for this command */
Tcl_Interp *interp, /* The interpreter used to report errors */
int objc, /* Number of arguments */
Tcl_Obj *const* objv /* Values of all arguments */
){
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_GetString(objv[1]);
}
if( 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,1) ){
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 = 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 = 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 int nByte; /* number of bytes of read uncompressed data */
unsigned int nByteCompr; /* number of bytes of unread compressed data */
unsigned int nData; /* total number of bytes of compressed data */
int readSoFar; /* Number of bytes read so far */
long startOfData; /* File position of start of data in ZIP archive */
int isCompressed; /* True data is compressed */
Tcl_Channel chan; /* Open to the archive file */
unsigned char *zBuf; /* buffer used by the decompressor */
z_stream stream; /* state of the decompressor */
} 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 twice 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, /* A ZvfsChannelInfo structure */
Tcl_Interp *interp /* The TCL interpreter */
){
ZvfsChannelInfo* pInfo = (ZvfsChannelInfo*)instanceData;
if( pInfo->zBuf ){
Tcl_Free(pInfo->zBuf);
inflateEnd(&pInfo->stream);
}
if( pInfo->chan ){
Tcl_Close(interp, pInfo->chan);
Tcl_DeleteExitHandler(vfsExit, pInfo);
}
Tcl_Free((char*)pInfo);
return TCL_OK;
}
/*
** The TCL I/O system calls this function to actually read information
** from a ZVFS file.
*/
static int vfsInput (
ClientData instanceData, /* The channel to read from */
char *buf, /* Buffer to fill */
int toRead, /* Requested number of bytes */
int *pErrorCode /* Location of error flag */
){
ZvfsChannelInfo* pInfo = (ZvfsChannelInfo*) instanceData;
if( toRead > pInfo->nByte ){
toRead = pInfo->nByte;
}
if( toRead == 0 ){
return 0;
}
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) {
int len = pInfo->nByteCompr;
if (len > COMPR_BUF_SIZE) {
len = COMPR_BUF_SIZE;
}
len = Tcl_Read(pInfo->chan, pInfo->zBuf, len);
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, buf, toRead);
}
pInfo->nByte -= toRead;
pInfo->readSoFar += toRead;
*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, /* The channel to write to */
CONST char *buf, /* Data to be stored. */
int toWrite, /* Number of bytes to write. */
int *pErrorCode /* Location of error flag. */
){
*pErrorCode = EINVAL;
return -1;
}
/*
** Move the file pointer so that the next byte read will be "offset".
*/
static int vfsSeek(
ClientData instanceData, /* The file structure */
long offset, /* Offset to seek to */
int mode, /* One of SEEK_CUR, SEEK_SET or SEEK_END */
int *pErrorCode /* Write the error code here */
){
ZvfsChannelInfo* pInfo = (ZvfsChannelInfo*) instanceData;
switch( mode ){
case SEEK_CUR: {
offset += pInfo->readSoFar;
break;
}
case SEEK_END: {
offset += pInfo->readSoFar + pInfo->nByte;
break;
}
default: {
/* Do nothing */
break;
}
}
if (offset < 0) offset = 0;
if( !pInfo->isCompressed ){
Tcl_Seek(pInfo->chan, offset + pInfo->startOfData, SEEK_SET);
pInfo->nByte = pInfo->nData;
pInfo->readSoFar = offset;
}else{
if( offset<pInfo->readSoFar ){
z_stream *stream = &pInfo->stream;
inflateEnd(stream);
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);
Tcl_Seek(pInfo->chan, pInfo->startOfData, SEEK_SET);
pInfo->nByte += pInfo->readSoFar;
pInfo->nByteCompr = pInfo->nData;
pInfo->readSoFar = 0;
}
while( pInfo->readSoFar < offset ){
int toRead, errCode;
char zDiscard[100];
toRead = offset - pInfo->readSoFar;
if( toRead>sizeof(zDiscard) ) toRead = sizeof(zDiscard);
vfsInput(instanceData, zDiscard, toRead, &errCode);
}
}
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, /* Channel to watch */
int mask /* Events of interest */
){
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, /* Channel to query */
int direction, /* Direction of interest */
ClientData* handlePtr /* Space to the handle into */
){
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, /* The TCL interpreter doing the open */
char *zFilename, /* Name of the file to open */
char *modeString, /* Mode string for the open (ignored) */
int permissions /* Permissions for a newly created file (ignored) */
){
ZvfsFile *pFile;
ZvfsChannelInfo *pInfo;
Tcl_Channel chan;
static int count = 1;
char zName[50];
unsigned char zBuf[50];
pFile = ZvfsLookup(zFilename);
if( pFile==0 ) return NULL;
openarch=1;
chan = Tcl_OpenFileChannel(interp, pFile->pArchive->zName, "r", 0);
openarch=0;
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, 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->isCompressed = INT16(zBuf, 8);
if( pInfo->isCompressed ){
z_stream *stream = &pInfo->stream;
pInfo->zBuf = 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);
return chan;
}
/*
** This routine does a stat() system call for a ZVFS file.
*/
static int ZvfsFileStat(char *path, struct stat *buf){
ZvfsFile *pFile;
pFile = ZvfsLookup(path);
if( pFile==0 ){
return -1;
}
memset(buf, 0, sizeof(*buf));
if (pFile->isdir)
buf->st_mode = 040555;
else
buf->st_mode = (0100000|pFile->permissions);
buf->st_ino = 0;
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;
}
#ifndef USE_TCL_VFS
/*
** This TCL procedure can be used to copy a file. The built-in
** "file copy" command of TCL bypasses the I/O system and does not
** work with zvfs. You have to use a procedure like the following
** instead.
*/
static char zFileCopy[] =
"proc zvfs::filecopy {from to {outtype binary}} {\n"
" set f [open $from r]\n"
" if {[catch {\n"
" fconfigure $f -translation binary\n"
" set t [open $to w]\n"
" } msg]} {\n"
" close $f\n"
" error $msg\n"
" }\n"
" if {[catch {\n"
" fconfigure $t -translation $outtype\n"
" set size [file size $from]\n"
" for {set i 0} {$i<$size} {incr i 40960} {\n"
" puts -nonewline $t [read $f 40960]\n"
" }\n"
" } msg]} {\n"
" close $f\n"
" close $t\n"
" error $msg\n"
" }\n"
" close $f\n"
" close $t\n"
"}\n"
;
#else
Tcl_Channel Tobe_FSOpenFileChannelProc
_ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *pathPtr,
int mode, int permissions)) {
static int inopen=0;
Tcl_Channel chan;
if (inopen) {
puts("recursive zvfs open");
return NULL;
}
inopen = 1;
/* if (mode != O_RDONLY) return NULL; */
chan = ZvfsFileOpen(interp, Tcl_GetString(pathPtr), 0,
permissions);
inopen = 0;
return chan;
}
/*
** This routine does a stat() system call for a ZVFS file.
*/
int Tobe_FSStatProc _ANSI_ARGS_((Tcl_Obj *pathPtr, struct stat *buf)) {
return ZvfsFileStat(Tcl_GetString(pathPtr), buf);
}
/*
** This routine does an access() system call for a ZVFS file.
*/
int Tobe_FSAccessProc _ANSI_ARGS_((Tcl_Obj *pathPtr, int mode)) {
return ZvfsFileAccess(Tcl_GetString(pathPtr), 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, dirglob, dirmnt;
char *zPattern = NULL, *zp=Tcl_GetStringFromObj(pathPtr,&len);;
if (!zp) return TCL_ERROR;
if (pattern != NULL) {
l=strlen(pattern);
if (!zp)
zPattern=strdup(pattern);
else {
zPattern=(char*)malloc(len+l+3);
sprintf(zPattern,"%s%s%s", zp, zp[len-1]=='/'?"":"/",pattern);
}
scnt=strchrcnt(zPattern,'/');
}
dirglob = (types && types->type && (types->type&TCL_GLOB_TYPE_DIR));
dirmnt = (types && types->type && (types->type&TCL_GLOB_TYPE_MOUNT));
if (strcmp(zp, "/") == 0 && strcmp(zPattern, ".*") == 0) {
}
for(pEntry = Tcl_FirstHashEntry(&local.fileHash, &sSearch);
pEntry;
pEntry = Tcl_NextHashEntry(&sSearch)
){
ZvfsFile *pFile = Tcl_GetHashValue(pEntry);
char *z = pFile->zName;
if (zPattern != NULL) {
if( Tcl_StringCaseMatch(z, zPattern, 0) == 0 ||
(scnt!=pFile->depth /* && !dirglob */ )) { // TODO: ???
continue;
}
} else {
if (strcmp(zp, z))
continue;
}
if (dirmnt) {
if (pFile->isdir != 1)
continue;
} else if (dirglob) {
if (!pFile->isdir)
continue;
} else if (types && !(types->type&TCL_GLOB_TYPE_DIR)) {
if (pFile->isdir)
continue;
}
Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(z, -1));
}
if (zPattern)
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)) {
ZvfsFile *zFile;
char *path=Tcl_GetString(pathPtr);
// if (ZvfsLookupMount(path)!=0)
// return TCL_OK;
// TODO: also check this is the archive.
if (openarch)
return -1;
zFile = ZvfsLookup(path);
if (zFile!=NULL && strcmp(path,zFile->pArchive->zName))
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=NULL, *pVol;
pEntry=Tcl_FirstHashEntry(&local.archiveHash,&zSearch);
while (pEntry) {
if (pArchive = Tcl_GetHashValue(pEntry)) {
if (!pVols) {
pVols=Tcl_NewListObj(0,0);
Tcl_IncrRefCount(pVols);
}
pVol=Tcl_NewStringObj(pArchive->zMountPoint,-1);
Tcl_IncrRefCount(pVol);
Tcl_ListObjAppendElement(NULL, pVols,pVol);
Tcl_DecrRefCount(pVol);
}
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;
}
CONST char** Tobe_FSFileAttrStringsProc _ANSI_ARGS_((Tcl_Obj *pathPtr,
Tcl_Obj** objPtrRef)) {
Tcl_Obj *listPtr;
Tcl_Interp *interp = NULL;
char *path=Tcl_GetString(pathPtr);
#ifdef __WIN32__
static CONST char *attrs[] = { "-archive", "-hidden", "-readonly", "-system", "-shortname", 0 };
#else
static CONST char *attrs[] = { "-group", "-owner", "-permissions", 0 };
#endif
if (ZvfsLookup(path)==0)
return NULL;
return attrs;
}
int Tobe_FSFileAttrsGetProc _ANSI_ARGS_((Tcl_Interp *interp,
int index, Tcl_Obj *pathPtr,
Tcl_Obj **objPtrRef)) {
char *path=Tcl_GetString(pathPtr);
char buf[50];
ZvfsFile *zFile;
if ((zFile = ZvfsLookup(path))==0)
return TCL_ERROR;
switch (index) {
#ifdef __WIN32__
case 0: /* -archive */
*objPtrRef = Tcl_NewStringObj("0", -1); break;
case 1: /* -hidden */
*objPtrRef = Tcl_NewStringObj("0", -1); break;
case 2: /* -readonly */
*objPtrRef = Tcl_NewStringObj("", -1); break;
case 3: /* -system */
*objPtrRef = Tcl_NewStringObj("", -1); break;
case 4: /* -shortname */
*objPtrRef = Tcl_NewStringObj("", -1);
#else
case 0: /* -group */
*objPtrRef = Tcl_NewStringObj("", -1); break;
case 1: /* -owner */
*objPtrRef = Tcl_NewStringObj("", -1); break;
case 2: /* -permissions */
sprintf(buf, "%03o", zFile->permissions);
*objPtrRef = Tcl_NewStringObj(buf, -1); break;
#endif
}
return TCL_OK;
}
/****************************************************/
// At some point, some of the following might get implemented?
#if 1
#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_FSFileAttrsSetProc 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_FSFilesystemPathTypeProc 0
#define Tobe_FSLinkProc 0
#else
/* 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. */
int Tobe_FSLoadFileProc _ANSI_ARGS_((Tcl_Interp * interp,
Tcl_Obj *pathPtr, char * sym1, char * sym2,
Tcl_PackageInitProc ** proc1Ptr,
Tcl_PackageInitProc ** proc2Ptr,
ClientData * clientDataPtr)) { return 0; }
/* 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. */
void Tobe_FSUnloadFileProc _ANSI_ARGS_((ClientData clientData)) {
return;
}
Tcl_Obj* Tobe_FSGetCwdProc _ANSI_ARGS_((Tcl_Interp *interp)) { return 0; }
int Tobe_FSCreateDirectoryProc _ANSI_ARGS_((Tcl_Obj *pathPtr)) { return 0; }
int Tobe_FSDeleteFileProc _ANSI_ARGS_((Tcl_Obj *pathPtr)) { return 0; }
int Tobe_FSCopyDirectoryProc _ANSI_ARGS_((Tcl_Obj *srcPathPtr,
Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr)) { return 0; }
int Tobe_FSCopyFileProc _ANSI_ARGS_((Tcl_Obj *srcPathPtr,
Tcl_Obj *destPathPtr)) { return 0; }
int Tobe_FSRemoveDirectoryProc _ANSI_ARGS_((Tcl_Obj *pathPtr,
int recursive, Tcl_Obj **errorPtr)) { return 0; }
int Tobe_FSRenameFileProc _ANSI_ARGS_((Tcl_Obj *srcPathPtr,
Tcl_Obj *destPathPtr)) { return 0; }
/* We have to declare the utime structure here. */
int Tobe_FSUtimeProc _ANSI_ARGS_((Tcl_Obj *pathPtr,
struct utimbuf *tval)) { return 0; }
int Tobe_FSNormalizePathProc _ANSI_ARGS_((Tcl_Interp *interp,
Tcl_Obj *pathPtr, int nextCheckpoint)) { return 0; }
int Tobe_FSFileAttrsSetProc _ANSI_ARGS_((Tcl_Interp *interp,
int index, Tcl_Obj *pathPtr,
Tcl_Obj *objPtr)) { return 0; }
Tcl_Obj* Tobe_FSLinkProc _ANSI_ARGS_((Tcl_Obj *pathPtr)) { return 0; }
Tcl_Obj* Tobe_FSFilesystemPathTypeProc
_ANSI_ARGS_((Tcl_Obj *pathPtr)) { return 0; }
void Tobe_FSFreeInternalRepProc _ANSI_ARGS_((ClientData clientData)) { return; }
ClientData Tobe_FSDupInternalRepProc
_ANSI_ARGS_((ClientData clientData)) { return 0; }
Tcl_Obj* Tobe_FSInternalToNormalizedProc
_ANSI_ARGS_((ClientData clientData)) { return 0; }
ClientData Tobe_FSCreateInternalRepProc _ANSI_ARGS_((Tcl_Obj *pathPtr)) {
return 0;
}
#endif
static Tcl_Filesystem Tobe_Filesystem = {
"tobe", /* 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.
*/
};
#endif
//////////////////////////////////////////////////////////////
void (*Zvfs_PostInit)(Tcl_Interp *)=0;
static int ZvfsAppendObjCmd( void *NotUsed, Tcl_Interp *interp, int objc, Tcl_Obj *const* objv);
static int ZvfsAddObjCmd( void *NotUsed, Tcl_Interp *interp, int objc, Tcl_Obj *const* objv);
static int ZvfsDumpObjCmd( void *NotUsed, Tcl_Interp *interp, int objc, Tcl_Obj *const* objv);
static int ZvfsStartObjCmd( void *NotUsed, Tcl_Interp *interp, int objc, Tcl_Obj *const* objv);
/*
** Initialize the ZVFS system.
*/
int Zvfs_doInit(Tcl_Interp *interp, int safe){
int n;
#ifdef USE_TCL_STUBS
if( Tcl_InitStubs(interp,"8.0",0)==0 ){
return TCL_ERROR;
}
#endif
Tcl_StaticPackage(interp, "zvfs", Tcl_Zvfs_Init, Tcl_Zvfs_SafeInit);
if (!safe) {
Tcl_CreateCommand(interp, "zvfs::mount", ZvfsMountCmd, 0, 0);
Tcl_CreateCommand(interp, "zvfs::unmount", ZvfsUnmountCmd, 0, 0);
Tcl_CreateObjCommand(interp, "zvfs::append", ZvfsAppendObjCmd, 0, 0);
Tcl_CreateObjCommand(interp, "zvfs::add", ZvfsAddObjCmd, 0, 0);
}
Tcl_CreateObjCommand(interp, "zvfs::exists", ZvfsExistsObjCmd, 0, 0);
Tcl_CreateObjCommand(interp, "zvfs::info", ZvfsInfoObjCmd, 0, 0);
Tcl_CreateObjCommand(interp, "zvfs::list", ZvfsListObjCmd, 0, 0);
Tcl_CreateObjCommand(interp, "zvfs::dump", ZvfsDumpObjCmd, 0, 0);
Tcl_CreateObjCommand(interp, "zvfs::start", ZvfsStartObjCmd, 0, 0);
Tcl_SetVar(interp, "::zvfs::auto_ext", ".tcl .tk .itcl .htcl .txt .c .h .tht", TCL_GLOBAL_ONLY);
/* Tcl_CreateObjCommand(interp, "zip::open", ZipOpenObjCmd, 0, 0); */
#ifndef USE_TCL_VFS
Tcl_GlobalEval(interp, zFileCopy);
#endif
if( !local.isInit ){
/* One-time initialization of the ZVFS */
#ifdef USE_TCL_VFS
n = Tcl_FSRegister(0, &Tobe_Filesystem);
#else
extern void TclAccessInsertProc();
extern void TclStatInsertProc();
extern void TclOpenFileChannelInsertProc();
TclAccessInsertProc(ZvfsFileAccess);
TclStatInsertProc(ZvfsFileStat);
TclOpenFileChannelInsertProc(ZvfsFileOpen);
#endif
Tcl_InitHashTable(&local.fileHash, TCL_STRING_KEYS);
Tcl_InitHashTable(&local.archiveHash, TCL_STRING_KEYS);
local.isInit = 1;
}
if (Zvfs_PostInit) Zvfs_PostInit(interp);
return TCL_OK;
}
int Tcl_Zvfs_Init(Tcl_Interp *interp){
return Zvfs_doInit(interp,0);
}
int Tcl_Zvfs_SafeInit(Tcl_Interp *interp){
return Zvfs_doInit(interp,1);
}
/************************************************************************/
/************************************************************************/
/************************************************************************/
/*
** Implement the zvfs::dump command
**
** zvfs::dump ARCHIVE
**
** Each entry in the list returned is of the following form:
**
** {FILENAME DATE-TIME SPECIAL-FLAG OFFSET SIZE COMPRESSED-SIZE}
**
*/
static int ZvfsDumpObjCmd(
void *NotUsed, /* Client data for this command */
Tcl_Interp *interp, /* The interpreter used to report errors */
int objc, /* Number of arguments */
Tcl_Obj *const* objv /* Values of all arguments */
){
char *zFilename;
Tcl_Channel chan;
ZFile *pList;
int rc;
Tcl_Obj *pResult;
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
return TCL_ERROR;
}
zFilename = Tcl_GetString(objv[1]);
chan = Tcl_OpenFileChannel(interp, zFilename, "r", 0);
if( chan==0 ) return TCL_ERROR;
rc = ZvfsReadTOC(interp, chan, &pList);
if( rc==TCL_ERROR ){
deleteZFileList(pList);
return rc;
}
Tcl_Close(interp, chan);
pResult = Tcl_GetObjResult(interp);
while( pList ){
Tcl_Obj *pEntry = Tcl_NewObj();
ZFile *pNext;
char zDateTime[100];
Tcl_ListObjAppendElement(interp, pEntry, Tcl_NewStringObj(pList->zName,-1));
translateDosTimeDate(zDateTime, pList->dosDate, pList->dosTime);
Tcl_ListObjAppendElement(interp, pEntry, Tcl_NewStringObj(zDateTime, -1));
Tcl_ListObjAppendElement(interp, pEntry, Tcl_NewIntObj(pList->isSpecial));
Tcl_ListObjAppendElement(interp, pEntry, Tcl_NewIntObj(pList->iOffset));
Tcl_ListObjAppendElement(interp, pEntry, Tcl_NewIntObj(pList->nByte));
Tcl_ListObjAppendElement(interp, pEntry, Tcl_NewIntObj(pList->nByteCompr));
Tcl_ListObjAppendElement(interp, pResult, pEntry);
pNext = pList->pNext;
Tcl_Free((char*)pList);
pList = pList->pNext;
}
return TCL_OK;
}
/*
** Write a file record into a ZIP archive at the current position of
** the write cursor for channel "chan". Add a ZFile record for the file
** to *ppList. If an error occurs, leave an error message on interp
** and return TCL_ERROR. Otherwise return TCL_OK.
*/
static int writeFile(
Tcl_Interp *interp, /* Leave an error message here */
Tcl_Channel out, /* Write the file here */
Tcl_Channel in, /* Read data from this file */
char *zSrc, /* Name the new ZIP file entry this */
char *zDest, /* Name the new ZIP file entry this */
ZFile **ppList /* Put a ZFile struct for the new file here */
){
z_stream stream;
ZFile *p;
int iEndOfData;
int nameLen;
int skip;
int toOut;
char zHdr[30];
char zInBuf[100000];
char zOutBuf[100000];
struct tm *tm;
time_t now;
struct stat stat;
/* Create a new ZFile structure for this file.
* TODO: fill in date/time etc.
*/
nameLen = strlen(zDest);
p = newZFile(nameLen, ppList);
strcpy(p->zName, zDest);
p->isSpecial = 0;
Tcl_Stat(zSrc, &stat);
now=stat.st_mtime;
tm = localtime(&now);
UnixTimeDate(tm, &p->dosDate, &p->dosTime);
p->iOffset = Tcl_Tell(out);
p->nByte = 0;
p->nByteCompr = 0;
p->nExtra = 0;
p->iCRC = 0;
p->permissions = stat.st_mode;
/* Fill in as much of the header as we know.
*/
put32(&zHdr[0], 0x04034b50);
put16(&zHdr[4], 0x0014);
put16(&zHdr[6], 0);
put16(&zHdr[8], 8);
put16(&zHdr[10], p->dosTime);
put16(&zHdr[12], p->dosDate);
put16(&zHdr[26], nameLen);
put16(&zHdr[28], 0);
/* Write the header and filename.
*/
Tcl_Write(out, zHdr, 30);
Tcl_Write(out, zDest, nameLen);
/* The first two bytes that come out of the deflate compressor are
** some kind of header that ZIP does not use. So skip the first two
** output bytes.
*/
skip = 2;
/* Write the compressed file. Compute the CRC as we progress.
*/
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
stream.opaque = 0;
stream.avail_in = 0;
stream.next_in = zInBuf;
stream.avail_out = sizeof(zOutBuf);
stream.next_out = zOutBuf;
#if 1
deflateInit(&stream, 9);
#else
{
int i, err, WSIZE = 0x8000, windowBits, level=6;
for (i = ((unsigned)WSIZE), windowBits = 0; i != 1; i >>= 1, ++windowBits);
err = deflateInit2(&stream, level, Z_DEFLATED, -windowBits, 8, 0);
}
#endif
p->iCRC = crc32(0, 0, 0);
while( !Tcl_Eof(in) ){
if( stream.avail_in==0 ){
int amt = Tcl_Read(in, zInBuf, sizeof(zInBuf));
if( amt<=0 ) break;
p->iCRC = crc32(p->iCRC, zInBuf, amt);
stream.avail_in = amt;
stream.next_in = zInBuf;
}
deflate(&stream, 0);
toOut = sizeof(zOutBuf) - stream.avail_out;
if( toOut>skip ){
Tcl_Write(out, &zOutBuf[skip], toOut - skip);
skip = 0;
}else{
skip -= toOut;
}
stream.avail_out = sizeof(zOutBuf);
stream.next_out = zOutBuf;
}
do{
stream.avail_out = sizeof(zOutBuf);
stream.next_out = zOutBuf;
deflate(&stream, Z_FINISH);
toOut = sizeof(zOutBuf) - stream.avail_out;
if( toOut>skip ){
Tcl_Write(out, &zOutBuf[skip], toOut - skip);
skip = 0;
}else{
skip -= toOut;
}
}while( stream.avail_out==0 );
p->nByte = stream.total_in;
p->nByteCompr = stream.total_out - 2;
deflateEnd(&stream);
Tcl_Flush(out);
/* Remember were we are in the file. Then go back and write the
** header, now that we know the compressed file size.
*/
iEndOfData = Tcl_Tell(out);
Tcl_Seek(out, p->iOffset, SEEK_SET);
put32(&zHdr[14], p->iCRC);
put32(&zHdr[18], p->nByteCompr);
put32(&zHdr[22], p->nByte);
Tcl_Write(out, zHdr, 30);
Tcl_Seek(out, iEndOfData, SEEK_SET);
/* Close the input file.
*/
Tcl_Close(interp, in);
/* Finished!
*/
return TCL_OK;
}
/*
** The arguments are two lists of ZFile structures sorted by iOffset.
** Either or both list may be empty. This routine merges the two
** lists together into a single sorted list and returns a pointer
** to the head of the unified list.
**
** This is part of the merge-sort algorithm.
*/
static ZFile *mergeZFiles(ZFile *pLeft, ZFile *pRight){
ZFile fakeHead;
ZFile *pTail;
pTail = &fakeHead;
while( pLeft && pRight ){
ZFile *p;
if( pLeft->iOffset <= pRight->iOffset ){
p = pLeft;
pLeft = p->pNext;
}else{
p = pRight;
pRight = p->pNext;
}
pTail->pNext = p;
pTail = p;
}
if( pLeft ){
pTail->pNext = pLeft;
}else if( pRight ){
pTail->pNext = pRight;
}else{
pTail->pNext = 0;
}
return fakeHead.pNext;
}
/*
** Sort a ZFile list so in accending order by iOffset.
*/
static ZFile *sortZFiles(ZFile *pList){
# define NBIN 30
int i;
ZFile *p;
ZFile *aBin[NBIN+1];
for(i=0; i<=NBIN; i++) aBin[i] = 0;
while( pList ){
p = pList;
pList = p->pNext;
p->pNext = 0;
for(i=0; i<NBIN && aBin[i]; i++){
p = mergeZFiles(aBin[i],p);
aBin[i] = 0;
}
aBin[i] = aBin[i] ? mergeZFiles(aBin[i], p) : p;
}
p = 0;
for(i=0; i<=NBIN; i++){
if( aBin[i]==0 ) continue;
p = mergeZFiles(p, aBin[i]);
}
return p;
}
/*
** Write a ZIP archive table of contents to the given
** channel.
*/
static void writeTOC(Tcl_Channel chan, ZFile *pList){
int iTocStart, iTocEnd;
int nEntry = 0;
int i;
char zBuf[100];
iTocStart = Tcl_Tell(chan);
for(; pList; pList=pList->pNext){
if( pList->isSpecial ) continue;
put32(&zBuf[0], 0x02014b50);
put16(&zBuf[4], 0x0317);
put16(&zBuf[6], 0x0014);
put16(&zBuf[8], 0);
put16(&zBuf[10], pList->nByte>pList->nByteCompr ? 0x0008 : 0x0000);
put16(&zBuf[12], pList->dosTime);
put16(&zBuf[14], pList->dosDate);
put32(&zBuf[16], pList->iCRC);
put32(&zBuf[20], pList->nByteCompr);
put32(&zBuf[24], pList->nByte);
put16(&zBuf[28], strlen(pList->zName));
put16(&zBuf[30], 0);
put16(&zBuf[32], pList->nExtra);
put16(&zBuf[34], 1);
put16(&zBuf[36], 0);
put32(&zBuf[38], pList->permissions<<16);
put32(&zBuf[42], pList->iOffset);
Tcl_Write(chan, zBuf, 46);
Tcl_Write(chan, pList->zName, strlen(pList->zName));
for(i=pList->nExtra; i>0; i-=40){
int toWrite = i<40 ? i : 40;
Tcl_Write(chan," ",toWrite);
}
nEntry++;
}
iTocEnd = Tcl_Tell(chan);
put32(&zBuf[0], 0x06054b50);
put16(&zBuf[4], 0);
put16(&zBuf[6], 0);
put16(&zBuf[8], nEntry);
put16(&zBuf[10], nEntry);
put32(&zBuf[12], iTocEnd - iTocStart);
put32(&zBuf[16], iTocStart);
put16(&zBuf[20], 0);
Tcl_Write(chan, zBuf, 22);
Tcl_Flush(chan);
}
/*
** Implementation of the zvfs::append command.
**
** zvfs::append ARCHIVE (SOURCE DESTINATION)*
**
** This command reads SOURCE files and appends them (using the name
** DESTINATION) to the zip archive named ARCHIVE. A new zip archive
** is created if it does not already exist. If ARCHIVE refers to a
** file which exists but is not a zip archive, then this command
** turns ARCHIVE into a zip archive by appending the necessary
** records and the table of contents. Treat all files as binary.
**
** Note: No dup checking is done, so multiple occurances of the
** same file is allowed.
*/
static int ZvfsAppendObjCmd(
void *NotUsed, /* Client data for this command */
Tcl_Interp *interp, /* The interpreter used to report errors */
int objc, /* Number of arguments */
Tcl_Obj *const* objv /* Values of all arguments */
){
char *zArchive;
Tcl_Channel chan;
ZFile *pList = NULL, *pToc;
int rc = TCL_OK, i;
/* Open the archive and read the table of contents
*/
if( objc<2 || (objc&1)!=0 ){
Tcl_WrongNumArgs(interp, 1, objv, "ARCHIVE (SRC DEST)+");
return TCL_ERROR;
}
zArchive = Tcl_GetString(objv[1]);
chan = Tcl_OpenFileChannel(interp, zArchive, "r+", 0644);
if( chan==0 ) {
chan = Tcl_OpenFileChannel(interp, zArchive, "w+", 0644);
}
if( chan==0 ) return TCL_ERROR;
if( Tcl_SetChannelOption(interp, chan, "-translation", "binary")
|| Tcl_SetChannelOption(interp, chan, "-encoding", "binary")
){
/* this should never happen */
Tcl_Close(0, chan);
return TCL_ERROR;
}
if (Tcl_Seek(chan, 0, SEEK_END) == 0) {
/* Null file is ok, we're creating new one. */
} else {
Tcl_Seek(chan, 0, SEEK_SET);
rc = ZvfsReadTOC(interp, chan, &pList);
if( rc==TCL_ERROR ){
deleteZFileList(pList);
Tcl_Close(interp, chan);
return rc;
} else rc=TCL_OK;
}
/* Move the file pointer to the start of
** the table of contents.
*/
for(pToc=pList; pToc; pToc=pToc->pNext){
if( pToc->isSpecial && strcmp(pToc->zName,"*TOC*")==0 ) break;
}
if( pToc ){
Tcl_Seek(chan, pToc->iOffset, SEEK_SET);
}else{
Tcl_Seek(chan, 0, SEEK_END);
}
/* Add new files to the end of the archive.
*/
for(i=2; rc==TCL_OK && i<objc; i+=2){
char *zSrc = Tcl_GetString(objv[i]);
char *zDest = Tcl_GetString(objv[i+1]);
Tcl_Channel in;
/* Open the file that is to be added to the ZIP archive
*/
in = Tcl_OpenFileChannel(interp, zSrc, "r", 0);
if( in==0 ){
break;
}
if( Tcl_SetChannelOption(interp, in, "-translation", "binary")
|| Tcl_SetChannelOption(interp, in, "-encoding", "binary")
){
/* this should never happen */
Tcl_Close(0, in);
rc = TCL_ERROR;
break;
}
rc = writeFile(interp, chan, in, zSrc, zDest, &pList);
}
/* Write the table of contents at the end of the archive.
*/
if (rc == TCL_OK) {
pList = sortZFiles(pList);
writeTOC(chan, pList);
}
/* Close the channel and exit */
deleteZFileList(pList);
Tcl_Close(interp, chan);
return rc;
}
static CONST char *
GetExtension( CONST char *name)
{
CONST char *p, *lastSep;
#ifdef __WIN32__
lastSep = NULL;
for (p = name; *p != '\0'; p++) {
if (strchr("/\\:", *p) != NULL) {
lastSep = p;
}
}
#else
lastSep = strrchr(name, '/');
#endif
p = strrchr(name, '.');
if ((p != NULL) && (lastSep != NULL) && (lastSep > p)) {
p = NULL;
}
return p;
}
/*
** Implementation of the zvfs::add command.
**
** zvfs::add ?-fconfigure optpairs? ARCHIVE FILE1 FILE2 ...
**
** This command is similar to append in that it adds files to the zip
** archive named ARCHIVE, however file names are relative the current directory.
** In addition, fconfigure is used to apply option pairs to set upon opening
** of each file. Otherwise, default translation is allowed
** for those file extensions listed in the ::zvfs::auto_ext var.
** Binary translation will be used for unknown extensions.
**
** NOTE Use '-fconfigure {}' to use auto translation for all.
*/
static int ZvfsAddObjCmd(
void *NotUsed, /* Client data for this command */
Tcl_Interp *interp, /* The interpreter used to report errors */
int objc, /* Number of arguments */
Tcl_Obj *CONST* objv /* Values of all arguments */
){
char *zArchive;
Tcl_Channel chan;
ZFile *pList = NULL, *pToc;
int rc = TCL_OK, i, j, oLen;
char *zOpts = NULL;
Tcl_Obj *confOpts = NULL;
int tobjc;
Tcl_Obj **tobjv;
Tcl_Obj *varObj = NULL;
/* Open the archive and read the table of contents
*/
if (objc>3) {
zOpts = Tcl_GetStringFromObj(objv[1], &oLen);
if (!strncmp("-fconfigure", zOpts, oLen)) {
confOpts = objv[2];
if (TCL_OK != Tcl_ListObjGetElements(interp, confOpts, &tobjc, &tobjv) || (tobjc%2)) {
return TCL_ERROR;
}
objc -= 2;
objv += 2;
}
}
if( objc==2) {
return TCL_OK;
}
if( objc<3) {
Tcl_WrongNumArgs(interp, 1, objv, "?-fconfigure OPTPAIRS? ARCHIVE FILE1 FILE2 ..");
return TCL_ERROR;
}
zArchive = Tcl_GetString(objv[1]);
chan = Tcl_OpenFileChannel(interp, zArchive, "r+", 0644);
if( chan==0 ) {
chan = Tcl_OpenFileChannel(interp, zArchive, "w+", 0644);
}
if( chan==0 ) return TCL_ERROR;
if( Tcl_SetChannelOption(interp, chan, "-translation", "binary")
|| Tcl_SetChannelOption(interp, chan, "-encoding", "binary")
){
/* this should never happen */
Tcl_Close(0, chan);
return TCL_ERROR;
}
if (Tcl_Seek(chan, 0, SEEK_END) == 0) {
/* Null file is ok, we're creating new one. */
} else {
Tcl_Seek(chan, 0, SEEK_SET);
rc = ZvfsReadTOC(interp, chan, &pList);
if( rc==TCL_ERROR ){
deleteZFileList(pList);
Tcl_Close(interp, chan);
return rc;
} else rc=TCL_OK;
}
/* Move the file pointer to the start of
** the table of contents.
*/
for(pToc=pList; pToc; pToc=pToc->pNext){
if( pToc->isSpecial && strcmp(pToc->zName,"*TOC*")==0 ) break;
}
if( pToc ){
Tcl_Seek(chan, pToc->iOffset, SEEK_SET);
}else{
Tcl_Seek(chan, 0, SEEK_END);
}
/* Add new files to the end of the archive.
*/
for(i=2; rc==TCL_OK && i<objc; i++){
char *zSrc = Tcl_GetString(objv[i]);
Tcl_Channel in;
/* Open the file that is to be added to the ZIP archive
*/
in = Tcl_OpenFileChannel(interp, zSrc, "r", 0);
if( in==0 ){
break;
}
if (confOpts == NULL) {
CONST char *ext = GetExtension(zSrc);
if (ext != NULL) {
/* Use auto translation for known text files. */
if (varObj == NULL) {
varObj=Tcl_GetVar2Ex(interp, "::zvfs::auto_ext", NULL, TCL_GLOBAL_ONLY);
}
if (varObj && TCL_OK != Tcl_ListObjGetElements(interp, varObj, &tobjc, &tobjv)) {
for (j=0; j<tobjc; j++) {
if (!strcmp(ext, Tcl_GetString(tobjv[j]))) {
break;
}
}
if (j>=tobjc) {
ext = NULL;
}
}
}
if (ext == NULL) {
if (( Tcl_SetChannelOption(interp, in, "-translation", "binary")
|| Tcl_SetChannelOption(interp, in, "-encoding", "binary"))
) {
/* this should never happen */
Tcl_Close(0, in);
rc = TCL_ERROR;
break;
}
}
} else {
for (j=0; j<tobjc; j+=2) {
if (Tcl_SetChannelOption(interp, in, Tcl_GetString(tobjv[j]), Tcl_GetString(tobjv[j+1]))) {
Tcl_Close(0, in);
rc = TCL_ERROR;
break;
}
}
}
if (rc == TCL_OK)
rc = writeFile(interp, chan, in, zSrc, zSrc, &pList);
}
/* Write the table of contents at the end of the archive.
*/
if (rc == TCL_OK) {
pList = sortZFiles(pList);
writeTOC(chan, pList);
}
/* Close the channel and exit */
deleteZFileList(pList);
Tcl_Close(interp, chan);
return rc;
}
/*
** Implementation of the zvfs::start command.
**
** zvfs::start ARCHIVE
**
** This command strips returns the offset of zip data.
**
*/
static int ZvfsStartObjCmd(
void *NotUsed, /* Client data for this command */
Tcl_Interp *interp, /* The interpreter used to report errors */
int objc, /* Number of arguments */
Tcl_Obj *CONST* objv /* Values of all arguments */
) {
char *zArchive;
Tcl_Channel chan;
ZFile *pList = NULL, *pToc;
int rc = TCL_OK, i, j, oLen;
char *zOpts = NULL;
Tcl_Obj *confOpts = NULL;
int tobjc;
Tcl_Obj **tobjv;
Tcl_Obj *varObj = NULL;
int zipStart;
/* Open the archive and read the table of contents
*/
if( objc != 2) {
Tcl_WrongNumArgs(interp, 1, objv, "ARCHIVE");
return TCL_ERROR;
}
zArchive = Tcl_GetString(objv[1]);
chan = Tcl_OpenFileChannel(interp, zArchive, "r", 0644);
if( chan==0 ) return TCL_ERROR;
if( Tcl_SetChannelOption(interp, chan, "-translation", "binary")
|| Tcl_SetChannelOption(interp, chan, "-encoding", "binary")
){
/* this should never happen */
Tcl_Close(0, chan);
return TCL_ERROR;
}
if (Tcl_Seek(chan, 0, SEEK_END) == 0) {
Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
return TCL_OK;
} else {
Tcl_Seek(chan, 0, SEEK_SET);
rc = ZvfsReadTOCStart(interp, chan, &pList, &zipStart);
if( rc!=TCL_OK ){
deleteZFileList(pList);
Tcl_Close(interp, chan);
Tcl_AppendResult(interp, "not an archive", 0);
return TCL_ERROR;
}
}
/* Close the channel and exit */
deleteZFileList(pList);
Tcl_Close(interp, chan);
Tcl_SetObjResult(interp, Tcl_NewIntObj(zipStart));
return TCL_OK;
}