Compare commits

...
Author SHA1 Message Date
hypnotoad 84bd20b0a6 Tweaks to build 2014-11-15 23:45:53 +00:00
hypnotoad 98a85efd7c Added the C implementation of the zipvfs encoder that was originally bundled into
the zvfs.c file as a separate tool.
2014-11-15 14:48:22 +00:00
5 changed files with 1024 additions and 135 deletions
+996
View File
@@ -0,0 +1,996 @@
/*
** Copyright (c) 2000 D. Richard Hipp
** Copyright (c) 2007 PDQ Interfaces Inc.
** Copyright (c) 2014 Sean Woods
**
** This file is now released under the BSD style license
** outlined in the included file license.terms.
**
*************************************************************************
**
** Utilities to Create, Modify and Introspect Zip files
** Originally distributed as part of ZipVfs.c, split into
** Their own file for clarity.
**
** 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>
/*
** 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 */
};
/*
** 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))
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);
/*
** 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 */
static int strchrcnt(char *str, char ch) {
int cnt=0;
char *cp=str;
while ((cp=strchr(cp,ch))) { cp++; cnt++; }
return cnt;
}
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;
}
/************************************************************************/
/************************************************************************/
/************************************************************************/
/*
** 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,zipstart;
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 = ZvfsReadTOCStart(interp, chan, &pList,&zipstart);
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,zipstart;
/* 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 = ZvfsReadTOCStart(interp, chan, &pList,&zipstart);
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;
int zipstart;
/* 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 = ZvfsReadTOCStart(interp, chan, &pList,&zipstart);
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;
}
int ZvfsTools_Init(Tcl_Interp *interp){
#ifdef USE_TCL_STUBS
if( Tcl_InitStubs(interp,"8.0",0)==0 ){
return TCL_ERROR;
}
#endif
Tcl_CreateObjCommand(interp, "::zvfs::addlist", ZvfsAppendObjCmd, 0, 0);
Tcl_CreateObjCommand(interp, "::zvfs::add", ZvfsAddObjCmd, 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_PkgProvide(interp, "zvfsctools", "1.0");
return TCL_OK;
}
int ZvfsTools_SafeInit(Tcl_Interp *interp){
#ifdef USE_TCL_STUBS
if( Tcl_InitStubs(interp,"8.0",0)==0 ){
return TCL_ERROR;
}
#endif
Tcl_SetVar(interp, "::zvfs::auto_ext", ".tcl .tk .itcl .htcl .txt .c .h .tht", TCL_GLOBAL_ONLY);
Tcl_CreateObjCommand(interp, "::zvfs::dump", ZvfsDumpObjCmd, 0, 0);
Tcl_PkgProvide(interp, "zvfsctools", "1.0");
return TCL_OK;
}
+1 -1
View File
@@ -160,7 +160,7 @@ static time_t DosTimeDate(int dosDate, int dosTime){
}
/* Return count of char ch in str */
int strchrcnt(char *str, char ch) {
static int strchrcnt(char *str, char ch) {
int cnt=0;
char *cp=str;
while ((cp=strchr(cp,ch))) { cp++; cnt++; }
+10 -130
View File
@@ -90,119 +90,6 @@ proc ::zvfs::walk {base {excludes ""} {match *} {path {}}} {
return $result
}
# zvfs::add_file_to_archive --
#
# Add a single file to a zip archive. The zipchan channel should
# already be open and binary. You may provide a comment for the
# file The return value is the central directory record that
# will need to be used when finalizing the zip archive.
#
# FIX ME: should handle the current offset for non-seekable channels
#
proc ::zvfs::add_file_to_archive {zipchan base path {comment ""}} {
set fullpath [file join $base $path]
set mtime [timet_to_dos [file mtime $fullpath]]
if {[file isdirectory $fullpath]} {
append path /
}
set utfpath [encoding convertto utf-8 $path]
set utfcomment [encoding convertto utf-8 $comment]
set flags [expr {(1<<11)}] ;# utf-8 comment and path
set method 0 ;# store 0, deflate 8
set attr 0 ;# text or binary (default binary)
set version 20 ;# minumum version req'd to extract
set extra ""
set crc 0
set size 0
set csize 0
set data ""
set seekable [expr {[tell $zipchan] != -1}]
if {[file isdirectory $fullpath]} {
set attrex 0x41ff0010 ;# 0o040777 (drwxrwxrwx)
} elseif {[file executable $fullpath]} {
set attrex 0x81ff0080 ;# 0o100777 (-rwxrwxrwx)
} else {
set attrex 0x81b60020 ;# 0o100666 (-rw-rw-rw-)
if {[file extension $fullpath] in {".tcl" ".txt" ".c"}} {
set attr 1 ;# text
}
}
if {[file isfile $fullpath]} {
set size [file size $fullpath]
if {!$seekable} {set flags [expr {$flags | (1 << 3)}]}
}
set offset [tell $zipchan]
set local [binary format a4sssiiiiss PK\03\04 \
$version $flags $method $mtime $crc $csize $size \
[string length $utfpath] [string length $extra]]
append local $utfpath $extra
puts -nonewline $zipchan $local
if {[file isfile $fullpath]} {
# If the file is under 2MB then zip in one chunk, otherwize we use
# streaming to avoid requiring excess memory. This helps to prevent
# storing re-compressed data that may be larger than the source when
# handling PNG or JPEG or nested ZIP files.
if {$size < 0x00200000} {
set fin [::open $fullpath rb]
setbinary $fin
set data [::read $fin]
set crc [::zlib crc32 $data]
set cdata [::zlib deflate $data]
if {[string length $cdata] < $size} {
set method 8
set data $cdata
}
close $fin
set csize [string length $data]
puts -nonewline $zipchan $data
} else {
set method 8
set fin [::open $fullpath rb]
setbinary $fin
set zlib [::zlib stream deflate]
while {![eof $fin]} {
set data [read $fin 4096]
set crc [zlib crc32 $data $crc]
$zlib put $data
if {[string length [set zdata [$zlib get]]]} {
incr csize [string length $zdata]
puts -nonewline $zipchan $zdata
}
}
close $fin
$zlib finalize
set zdata [$zlib get]
incr csize [string length $zdata]
puts -nonewline $zipchan $zdata
$zlib close
}
if {$seekable} {
# update the header if the output is seekable
set local [binary format a4sssiiii PK\03\04 \
$version $flags $method $mtime $crc $csize $size]
set current [tell $zipchan]
seek $zipchan $offset
puts -nonewline $zipchan $local
seek $zipchan $current
} else {
# Write a data descriptor record
set ddesc [binary format a4iii PK\7\8 $crc $csize $size]
puts -nonewline $zipchan $ddesc
}
}
set hdr [binary format a4ssssiiiisssssii PK\01\02 0x0317 \
$version $flags $method $mtime $crc $csize $size \
[string length $utfpath] [string length $extra]\
[string length $utfcomment] 0 $attr $attrex $offset]
append hdr $utfpath $extra $utfcomment
return $hdr
}
# zvfs::mkzip --
#
# Create a zip archive in 'filename'. If a file already exists it will be
@@ -262,32 +149,25 @@ if {[file exists [file join [info script] main.tcl]] } {
source [file join [info script] main.tcl]
}
}
append zkd \x1A
::append zkd \x1A
puts -nonewline $zf $zkd
}
close $zf
set count 0
set cd ""
if {$opts(-directory) ne ""} {
set paths [walk $opts(-directory) $opts(-exclude)]
set paths [walk $opts(-directory) $opts(-exclude)]
set zippathlist {}
foreach path $paths {
if {[file isdirectory [file join $opts(-directory) $path]]} continue
lappend zippathlist [file join $opts(-directory) $path] $path
}
::zvfs::addlist $filename {*}$zippathlist
} else {
set paths [glob -nocomplain {*}$args]
::zvfs::add $filename {*}[glob -nocomplain {*}$args]
}
foreach path $paths {
append cd [add_file_to_archive $zf $opts(-directory) $path]
incr count
}
set cdoffset [tell $zf]
set endrec [binary format a4ssssiis PK\05\06 0 0 \
$count $count [string length $cd] $cdoffset\
[string length $opts(-comment)]]
append endrec $opts(-comment)
puts -nonewline $zf $cd
puts -nonewline $zf $endrec
close $zf
return
}
###
+11 -4
View File
@@ -285,7 +285,7 @@ LIBS = @TCL_LIBS@
DEPEND_SWITCHES = ${CFLAGS} -I${UNIX_DIR} -I${GENERIC_DIR} \
${AC_FLAGS} ${PROTO_FLAGS} ${EXTRA_CFLAGS} @EXTRA_CC_SWITCHES@
TCLSH_OBJS = tclAppInit.o tclZipVfs.o tclZipVfsBoot.o
TCLSH_OBJS = tclAppInit.o tclZipVfs.o tclZipVfsBoot.o tclZipTools.o
TCLTEST_OBJS = tclTestInit.o tclTest.o tclTestObj.o tclTestProcBodyObj.o \
tclThreadTest.o tclUnixTest.o
@@ -662,14 +662,14 @@ basekit.vfs:
"$(UNIX_DIR)/basekit.vfs/boot/tcl" "$(TOP_DIR)" unix
# Builds an executable directly from the Tcl sources
${BASEKIT_EXE}: ${TCLSH_OBJS} ${OBJS} ${ZLIB_OBJS} basekit.vfs
${BASEKIT_EXE}: ${TCLSH_OBJS} ${OBJS} ${ZLIB_OBJS} ${TCL_EXE} basekit.vfs
${CC} ${CFLAGS} ${LDFLAGS} \
${TCLSH_OBJS} ${OBJS} ${ZLIB_OBJS} \
${LIBS} @EXTRA_TCLSH_LIBS@ \
${CC_SEARCH_FLAGS} -o ${BASEKIT_EXE}_bare
@echo zipping...
@$(TCL_EXE) ../tools/mkzip.tcl ${BASEKIT_EXE} \
@echo zipping... $(TCL_EXE)
./$(TCL_EXE) ../tools/mkzip.tcl ${BASEKIT_EXE} \
-runtime ${BASEKIT_EXE}_bare \
-directory basekit.vfs
chmod a+x ${BASEKIT_EXE}
@@ -890,6 +890,10 @@ install-libraries: libraries
@echo "Installing package platform::shell 1.1.4 as a Tcl Module";
@$(INSTALL_DATA) $(TOP_DIR)/library/platform/shell.tcl "$(SCRIPT_INSTALL_DIR)"/../tcl8/8.4/platform/shell-1.1.4.tm;
@echo "Installing package zvfstools 0.1 as a Tcl Module";
@$(INSTALL_DATA) $(TOP_DIR)/library/zvfstools/zvfstools.tcl "$(SCRIPT_INSTALL_DIR)"/../tcl8/8.6/zvfstools-0.1.tm;
@echo "Installing encoding files to $(SCRIPT_INSTALL_DIR)/encoding/";
@for i in $(TOP_DIR)/library/encoding/*.enc ; do \
$(INSTALL_DATA) $$i "$(SCRIPT_INSTALL_DIR)"/encoding; \
@@ -1362,6 +1366,9 @@ tclZipVfs.o: $(GENERIC_DIR)/tclZipVfs.c
tclZipVfsBoot.o: $(GENERIC_DIR)/tclZipVfsBoot.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclZipVfsBoot.c
tclZipTools.o: $(GENERIC_DIR)/tclZipTools.c
$(CC) -c $(CC_SWITCHES) $(ZLIB_INCLUDE) $(GENERIC_DIR)/tclZipTools.c
tclZlib.o: $(GENERIC_DIR)/tclZlib.c
$(CC) -c $(CC_SWITCHES) $(ZLIB_INCLUDE) $(GENERIC_DIR)/tclZlib.c
+6
View File
@@ -43,6 +43,8 @@ MODULE_SCOPE int main(int, char **);
MODULE_SCOPE int Tcl_Zvfs_Boot(const char *,const char *,const char *);
MODULE_SCOPE int Zvfs_Init(Tcl_Interp *);
MODULE_SCOPE int Zvfs_SafeInit(Tcl_Interp *);
MODULE_SCOPE int ZvfsTools_Init(Tcl_Interp *);
MODULE_SCOPE int ZvfsTools_SafeInit(Tcl_Interp *);
/*
* The following #if block allows you to change how Tcl finds the startup
@@ -123,6 +125,10 @@ Tcl_AppInit(
if(Zvfs_Init(interp) == TCL_ERROR) {
return TCL_ERROR;
}
Tcl_StaticPackage(interp, "zvfsctools", ZvfsTools_Init, ZvfsTools_SafeInit);
if(ZvfsTools_Init(interp) == TCL_ERROR) {
return TCL_ERROR;
}
#ifdef TCL_XT_TEST
if (Tclxttest_Init(interp) == TCL_ERROR) {
return TCL_ERROR;