diff --git a/Makefile b/Makefile index e454d66..124f8bb 100644 --- a/Makefile +++ b/Makefile @@ -183,6 +183,16 @@ ZIPOBJS = zipfile.o \ #--------------------------build all : build +ifdef USE_SYSTEM_LIBS +build : dirs \ + tktable tkcon xmlrpc blt \ + tclxml tkimg tkmpeg html \ + xpa iis checkdns $(SIGNAL) funtools \ + ast wcssubs \ + rice hcompress plio \ + $(OPTDIR) \ + saotk zip zvfs ds9 +else build : dirs \ tcl tk \ tktable tkcon xmlrpc blt \ @@ -192,6 +202,7 @@ build : dirs \ rice hcompress plio \ $(OPTDIR) \ saotk zip zvfs ds9 +endif doc : FORCE @echo "Making Documentation..." @@ -206,8 +217,8 @@ language: FORCE grep 'msgcat::mc' src/*.tcl | tclsh8.5 util/mergedict.tcl fr iso8859-1 grep 'msgcat::mc' src/*.tcl | tclsh8.5 util/mergedict.tcl pt iso8859-1 grep 'msgcat::mc' src/*.tcl | tclsh8.5 util/mergedict.tcl cs iso8859-2 - grep 'msgcat::mc' src/*.tcl | tclsh8.5 util/mergedict.tcl ja euc-jp - grep 'msgcat::mc' src/*.tcl | tclsh8.5 util/mergedict.tcl zh big5 + grep 'msgcat::mc' src/*.tcl | tclsh8.5 util/mergedict.tcl ja utf-8 + grep 'msgcat::mc' src/*.tcl | tclsh8.5 util/mergedict.tcl zh utf-8 #--------------------------items @@ -257,18 +268,22 @@ dirs : FORCE tcl : FORCE @echo "Installing Tcl..." cd $(TCLDIRDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) $(TCLFLAGS) --disable-shared --cache-file=$(CACHE) - cd $(TCLDIRDIR); $(MAKE) -j $(JOBS); $(MAKE) install + $(MAKE) -C $(TCLDIRDIR) + $(MAKE) -C $(TCLDIRDIR) install tk : FORCE @echo "Installing Tk..." cd $(TKDIRDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) $(TCLFLAGS) --disable-shared --cache-file=$(CACHE) - cd $(TKDIRDIR); $(MAKE) -j $(JOBS); $(MAKE) install + $(MAKE) -C $(TKDIRDIR) + $(MAKE) -C $(TKDIRDIR) install $(RM) -r lib/$(TKVER)/demos tktable : FORCE @echo "Installing TkTable..." - cd $(TKTABLEDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) $(TKTABLEFLAGS) --disable-shared --cache-file=$(CACHE) - cd $(TKTABLEDIR); $(MAKE) -j $(JOBS) ; $(MAKE) install + cd $(TKTABLEDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) $(TCL_CONFIG) $(TK_CONFIG) $(TKTABLEFLAGS) --disable-shared --cache-file=$(CACHE) + $(MAKE) -C $(TKTABLEDIR) + mkdir -p lib/Tktable2.10 + cp $(TKTABLEDIR)/*.a lib/Tktable2.10 tkcon : FORCE @echo "Installing TkCon..." @@ -284,64 +299,73 @@ xmlrpc : FORCE blt : FORCE @echo "Installing BLT..." - cd $(BLTDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) --with-tcl=$(root)/$(TCLDIR) --with-tk=$(root)/$(TKDIR) $(BLTFLAGS) --disable-shared --cache-file=$(CACHE) - cd $(BLTDIR)/src; $(MAKE) -j $(JOBS) build_static + cd $(BLTDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) $(TCL_CONFIG) $(TK_CONFIG) $(BLTFLAGS) --disable-shared --cache-file=$(CACHE) + $(MAKE) -C $(BLTDIR)/src build_static cp $(BLTDIR)/src/*.a lib/. cd $(BLTDIR)/src; cp $(BLTINCL) ../../include/. zlib : FORCE @echo "Installing zlib..." cd $(ZLIBDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) --static - cd $(ZLIBDIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(ZLIBDIR) install tclxml : FORCE @echo "Installing TCLXML..." cd $(TCLXMLDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) --disable-shared --disable-threads --with-xml-static=1 $(TCLXMLFLAGS) --cache-file=$(CACHE) - cd $(TCLXMLDIR); $(MAKE) -j $(JOBS) ; $(MAKE) install + $(MAKE) -C $(TCLXMLDIR) + mkdir -p lib/Tclxml3.2 + cp $(TCLXMLDIR)/*.a lib/Tclxml3.2 tkimg : libtiff @echo "Installing TKIMG..." - cd $(TKIMGDIR); CC='$(CC)' CFLAGS='$(OPTS) -DPNG_NO_WRITE_gAMA' LDFLAGS='$(LIBS)' ./configure --prefix $(root) --with-tcl=$(root)/$(TCLDIRDIR) --with-tk=$(root)/$(TKDIRDIR) $(TKIMGFLAGS) --disable-shared --disable-threads --cache-file=$(LOCALCACHE) - cd $(TKIMGDIR); $(MAKE) -j $(JOBS) all; $(MAKE) install + cd $(TKIMGDIR); CC='$(CC)' CFLAGS='$(OPTS) -DPNG_NO_WRITE_gAMA' LDFLAGS='$(LIBS)' ./configure --prefix $(root) $(TCL_CONFIG) $(TK_CONFIG) $(TKIMGFLAGS) --disable-shared --disable-threads --cache-file=$(LOCALCACHE) + $(MAKE) -C $(TKIMGDIR) all + mkdir -p lib/Img1.4 + find $(TKIMGDIR) -name "*.a" | xargs -n1 -i cp {} lib/Img1.4 libtiff : FORCE @echo "Installing LIBTIFF..." cd $(TKIMGDIR)/compat/libtiff; CC='$(CC)' CFLAGS='$(OPTS)' CXX='$(CC)' CXXFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) --disable-shared --cache-file=$(LOCALCACHE) - cd $(TKIMGDIR)/compat/libtiff; $(MAKE) -j $(JOBS) ; $(MAKE) install + $(MAKE) -C $(TKIMGDIR)/compat/libtiff + $(MAKE) -C $(TKIMGDIR)/compat/libtiff install tkmpeg : FORCE @echo "Installing TKMPEG..." - cd $(TKMPEGDIR); $(MAKE) -j $(JOBS) - cd $(TKMPEGDIR); $(MAKE) install + $(MAKE) -C $(TKMPEGDIR) + $(MAKE) -C $(TKMPEGDIR) install +# This needs a special make. Run only with TCL_CONFIG and ignore +# cache file html : FORCE @echo "Installing HTMLWIDGET..." - cd $(HTMLDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' $(PREHTMLFLAGS) $(root)/htmlwidget/configure --prefix $(root) --with-tcl=$(root)/$(TCLDIR) --with-tk=$(root)/$(TKDIR) $(XFLAGS) --enable-shared=no --cache-file=$(CACHE) - cd $(HTMLDIR); $(MAKE) headers libtkhtml.a + rm -f $(HTMLDIR)/config.cache + cd $(HTMLDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' $(PREHTMLFLAGS) $(root)/htmlwidget/configure --prefix $(root) $(TCL_CONFIG) --enable-shared=no + $(MAKE) -C $(HTMLDIR) headers libtkhtml.a cp $(HTMLDIR)/libtkhtml.a lib/. xpa : FORCE @echo "Installing XPA..." cd $(XPADIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) --with-tcl=$(root)/$(TCLDIRDIR) $(XPAFLAGS) --disable-shared --cache-file=$(CACHE) - cd $(XPADIR); $(MAKE) -j $(JOBS); $(MAKE) install + $(MAKE) -C $(XPADIR) + $(MAKE) -C $(XPADIR) install cd bin; strip xpa* iis : FORCE @echo "Installing IIS..." - cd $(IISDIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(IISDIR) install checkdns: FORCE @echo "Installing CheckDNS..." - cd $(CHECKDNSDIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(CHECKDNSDIR) install signal: FORCE @echo "Installing Signal..." - cd $(SIGNALDIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(SIGNALDIR) install funtools: FORCE @echo "Installing Funtools..." - cd $(FUNTOOLSDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) --with-zlib=../../lib/libz.a --with-wcslib=../lib/libwcs.a --enable-mainlib $(FUNTOOLSFLAGS) - cd $(FUNTOOLSDIR); $(MAKE) lib + cd $(FUNTOOLSDIR); CC='$(CC)' CFLAGS='$(OPTS)' LDFLAGS='$(LIBS)' ./configure --prefix $(root) --with-zlib=$(LIBDIR)/libz.a --with-wcslib=../lib/libwcs.a --enable-mainlib $(FUNTOOLSFLAGS) + $(MAKE) -C $(FUNTOOLSDIR) lib cp $(FUNTOOLSDIR)/libfuntools.a lib/. ast : FORCE @@ -351,33 +375,33 @@ ast : FORCE touch Makefile.in; sleep 1; \ touch configure; \ ./configure --enable-shared=no --prefix=$(root) $(ASTFLAGS) CC='$(CC)' CFLAGS='$(OPTS) -I.'; \ - $(MAKE) -j $(JOBS) ast.h install-libLTLIBRARIES install-nodist_includeHEADERS install-includeHEADERS + $(MAKE) ast.h install-libLTLIBRARIES install-nodist_includeHEADERS install-includeHEADERS wcssubs : FORCE @echo "Installing WCSSUBS..." - cd $(WCSSUBSDIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(WCSSUBSDIR) install rice : FORCE @echo "Installing RICE..." - cd $(RICEDIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(RICEDIR) install hcompress: FORCE @echo "Installing HCOMPRESS..." - cd $(HCOMPRESSDIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(HCOMPRESSDIR) install plio: FORCE @echo "Installing PLIO..." - cd $(PLIODIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(PLIODIR) install ifdef OPTDIR $(OPTDIR) : FORCE @echo "Installing $(OPTDIR)..." - cd $(OPTDIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(OPTDIR) install endif saotk : FORCE @echo "Installing SAOTK..." - cd $(SAOTKDIR); $(MAKE) -j $(JOBS) install + $(MAKE) -C $(SAOTKDIR) CCOPT='$(TKTCL_INCLUDE)' install zip : FORCE @echo "Installing ZIP..." @@ -389,8 +413,8 @@ zip : FORCE zvfs : FORCE @echo "Installing ZVFS..." - cd $(ZVFSDIR); $(MAKE) - cd $(ZVFSDIR); $(MAKE) install + $(MAKE) -C $(ZVFSDIR) CCOPT='$(TKTCL_INCLUDE)' + $(MAKE) -C $(ZVFSDIR) install #--------------------------clean diff --git a/ds9/Makefile b/ds9/Makefile index d21a188..c62dba8 100644 --- a/ds9/Makefile +++ b/ds9/Makefile @@ -4,10 +4,15 @@ include ../make.pkgs #--------------------------defines ZDIR = zipdir/zvfsmntpt -FFILES = $(ZDIR)/$(TCLVER) \ - $(ZDIR)/tcl8 \ - $(ZDIR)/$(TKVER) \ - $(ZDIR)/$(BLTVER) \ +ifdef USE_SYSTEM_LIBS +FFILES = +else +FFILES = $(ZDIR)/$(TCLVER) \ + $(ZDIR)/tcl8 \ + $(ZDIR)/$(TKVER) +endif + +FFILES += $(ZDIR)/$(BLTVER) \ $(ZDIR)/$(TCLLIBVER) \ $(ZDIR)/$(TKCONVER) \ $(ZDIR)/$(XMLRPCVER) \ @@ -27,6 +32,10 @@ endif # for unix, macosx MAINDIR = ../$(TKDIR)/unix MAIN = tkAppInit +ifndef USE_SYSTEM_LIBS +LIBSZ = ../lib/libzip.a ../lib/libz.a +LIBSTK = ../lib/libtk8.5.a ../lib/libtcl8.5.a +endif LIBS = \ ../lib/libsaotk.a \ @@ -55,18 +64,14 @@ LIBS = \ ../lib/libast_pal.a \ ../lib/libsaotk.a \ ../lib/libwcs.a \ - ../lib/libzvfs.a \ - ../lib/libzip.a \ - ../lib/libz.a \ + ../lib/libzvfs.a $(LIBSZ) \ ../lib/libxpa.a \ ../lib/libiis.a \ ../lib/libcheckdns.a \ ../lib/libsignal_ext.a \ ../lib/libxxlib.a \ ../lib/libBLTX30.a \ - ../lib/libBLTCore30.a \ - ../lib/libtk8.5.a \ - ../lib/libtcl8.5.a + ../lib/libBLTCore30.a $(LIBSTK) # if windows, redefine ifeq ($(OS),windows) @@ -309,6 +314,9 @@ $(ZDIR)/$(XMLRPCVER) : zipdir ../lib/$(XMLRPCVER) $(ZDIR)/src : zipdir ../src/*.tcl $(RM) -r $@ cp -r ../src $(ZDIR)/. +ifdef USE_SYSTEM_LIBS + cp ../src/ds9.syslib.tcl $(ZDIR)/src/ds9.tcl +endif $(ZDIR)/msgs : zipdir ../msgs/* $(RM) -r $@ @@ -354,6 +362,15 @@ endif #--------------------------linux ifneq (,$(findstring linux,$(ARCH))) +ifdef USE_SYSTEM_LIBS +ds9Base : $(OBJS) $(LIBS) + $(RM) $@ + $(CXX) ${OPTS} -Wl,--export-dynamic \ + -o $@ $(OBJS) $(LIBS) \ + -L$(X11LIB) -lX11 -lXext -lXft -lXrender -lXss \ + -lxml2 -lfontconfig -lfreetype -ljbig -lzip -lz -ltk -ltcl \ + -ldl -lpthread +else ds9Base : $(OBJS) $(LIBS) $(RM) $@ rm -f libstdc++.a @@ -367,6 +384,7 @@ ds9Base : $(OBJS) $(LIBS) -ldl -lpthread rm -f libstdc++.a endif +endif #--------------------------darwin --- a/htmlwidget/configure +++ b/htmlwidget/configure @@ -1374,7 +1374,7 @@ fi fi if test "$ok" = "0"; then - tkconf=/usr/lib/tclConfig.sh + tkconf=$with_tcl/tclConfig.sh ac_safe=`echo "$tkconf" | sed 'y%./+-%__p_%'` echo $ac_n "checking for $tkconf""... $ac_c" 1>&6 diff --git a/make.linux b/make.linux index a5671e3..cbe453a 100644 --- a/make.linux +++ b/make.linux @@ -4,7 +4,7 @@ ARCH = linux X11INCLUDE=/usr/X11R6/include X11LIB = /usr/X11R6/lib -XX = -O2 +XX = -g -O2 YY = -gstabs+ -fno-inline ZZ = @@ -13,19 +13,34 @@ ZZ = # AA = -fPIC -DHAVE_SYS_UN_H -DHAVE_SYS_SHM_H AA = -fPIC -DHAVE_SYS_UN_H -DHAVE_SYS_SHM_H -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -#OPTS = ${XX} ${ZZ} -OPTS = ${YY} ${ZZ} +OPTS = ${XX} ${ZZ} +#OPTS = ${YY} ${ZZ} NOPTS = ${YY} ${ZZ} -#CXX = g++ -CXX = g++44 +CXX = g++ CXXOPT = ${OPTS} ${AA} CXXNOPT = ${NOPTS} ${AA} -#CC = gcc -CC = gcc44 +CC = gcc CCOPT = ${OPTS} ${AA} CCNOPT = ${NOPTS} ${AA} ZCAT = zcat JOBS = 4 +LIBDIR=/usr/lib +TCL_CONFIG = --with-tcl=/usr/lib \ +--with-tcllibdir=/usr/lib + +TK_CONFIG = --with-tk=/usr/lib \ +--with-tklibdir=/usr/lib + +TKTCL_INCLUDE=-I/usr/include/tcl-private/generic \ +-I/usr/include/tcl-private/unix \ +-I/usr/include/tk-private/generic \ +-I/usr/include/tk-private/unix \ +-DHAVE_UNISTD_H + +USE_SYSTEM_LIBS=1 + + + diff --git a/make.linux64 b/make.linux64 index c6e80f5..3f486e8 100644 --- a/make.linux64 +++ b/make.linux64 @@ -4,24 +4,39 @@ ARCH = linux64 X11INCLUDE=/usr/include/X11 X11LIB = /usr/lib64 -XX = -O2 +XX = -g -O2 YY = -g -fno-inline ZZ = -m64 -Wl,--hash-style=both AA = -fPIC -DHAVE_SYS_UN_H -DHAVE_SYS_SHM_H -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -D__M64 -#OPTS = ${XX} ${ZZ} -OPTS = ${YY} ${ZZ} +OPTS = ${XX} ${ZZ} +#OPTS = ${YY} ${ZZ} NOPTS = ${YY} ${ZZ} CXX = g++ CXXOPT = ${OPTS} ${AA} CXXNOPT = ${NOPTS} ${AA} CC = gcc CCOPT = ${OPTS} ${AA} CCNOPT = ${NOPTS} ${AA} ZCAT = zcat JOBS = 4 +LIBDIR=/usr/lib64 +TCL_CONFIG = --with-tcl=/usr/lib64 \ +--with-tcllibdir=/usr/lib64 + +TK_CONFIG = --with-tk=/usr/lib64 \ +--with-tklibdir=/usr/lib64 + +TKTCL_INCLUDE=-I/usr/include/tcl-private/generic \ +-I/usr/include/tcl-private/unix \ +-I/usr/include/tk-private/generic \ +-I/usr/include/tk-private/unix \ +-DHAVE_UNISTD_H + +USE_SYSTEM_LIBS=1 + diff --git a/saotk/Makefile b/saotk/Makefile index d865d0b..d024931 100644 --- a/saotk/Makefile +++ b/saotk/Makefile @@ -59,7 +59,7 @@ fitsy++ : FORCE cd fitsy++; $(MAKE) util : FORCE - cd util; $(MAKE) + $(MAKE) -C util clean : FORCE cd frame; $(MAKE) clean diff --git a/saotk/util/Makefile b/saotk/util/Makefile index 592572c..99eef4b 100644 --- a/saotk/util/Makefile +++ b/saotk/util/Makefile @@ -5,7 +5,9 @@ CXXFLAGS = $(CXXOPT) -w \ -I. -I.. -I../widget -I../vector -I../frame -I../fitsy++ -I../list \ -I../../include -I$(X11INCLUDE) \ -I../../$(FUNTOOLSDIR)/util \ - -I../../$(ASTDIR) + -I../../$(ASTDIR) + +CFLAGS= $(CCOPT) $(CXXFLAGS) SRC = attribute.C \ grf.C \ @@ -17,12 +19,14 @@ SRC = attribute.C \ ps.C \ smooth.C \ saotk.C \ - util.C + util.C \ + tkCanvPsScaled.c + INCLS = smooth.h \ util.h -OBJS = $(SRC:%.C=%.o) +OBJS = $(SRC:%.C=%.o) $(SRC:%.c=%.o) all : $(OBJS) TAGS diff --git a/zvfs/Makefile b/zvfs/Makefile index 0637a0c..fb17fe1 100644 --- a/zvfs/Makefile +++ b/zvfs/Makefile @@ -1,9 +1,8 @@ include ../make.include include ../make.pkgs -CFLAGS = $(CCOPT) -I. -I../include - -SRC = zvfs.c zip.c +CFLAGS = $(CCOPT) -I. -I../include +SRC = zvfs.c OBJS = $(SRC:%.c=%.o) @@ -13,16 +12,10 @@ all : $(LIB) install : all cp -f $(LIB) ../lib/. -$(LIB) : update $(OBJS) +$(LIB) : $(OBJS) $(RM) $@ $(AR) -cr $@ $(OBJS) -update : FORCE - cp ../$(ZIPDIR)/zip.c . - -zip.o : zip.c - $(CC) $(CFLAGS) -DUSE_ZIPMAIN -I../$(ZIPDIR) -o $@ -c $? - clean : FORCE rm -f core *~ *# diff --git a/zvfs/zvfs.c b/zvfs/zvfs.c index 0aec622..81234b1 100644 --- a/zvfs/zvfs.c +++ b/zvfs/zvfs.c @@ -1,993 +1,1719 @@ /* -** Copyright (c) 2000 D. Richard Hipp +** By the overt act of typing this comment, the author of this code +** releases it into the public domain. No claim of copyright is made. +** In place of a legal notice, here is a blessing: ** -** This program is free software; you can redistribute it and/or -** modify it under the terms of the GNU General Public -** License as published by the Free Software Foundation; either -** version 2 of the License, or (at your option) any later version. +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. ** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -** General Public License for more details. -** -** You should have received a copy of the GNU General Public -** License along with this library; if not, write to the -** Free Software Foundation, Inc., 59 Temple Place - Suite 330, -** Boston, MA 02111-1307, USA. -** -** Author contact information: -** drh@hwaci.com -** http://www.hwaci.com/drh/ -** -************************************************************************* +*************************************************************************** ** A ZIP archive virtual filesystem for Tcl. ** ** This package of routines enables Tcl to use a Zip file as ** a virtual file system. Each of the content files of the Zip ** archive appears as a real file to Tcl. ** -** 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 "seek", nor "cd" and -** the "glob" command doesn't work. But it turns out that "stat" -** and "read" are sufficient for most purposes. +** Converted to Tcl VFS by Peter MacDonald +** peter@pdqi.com +** http://pdqi.com +** ** -** @(#) $Id: zvfs.c,v 1.7 2006/03/14 22:12:35 joye Exp $ +** Modified by Damon Courtney to complete the VFS work. +** +** @(#) $Id: zvfs.c,v 1.1.1.1 2002/01/27 17:44:02 cvs Exp $ */ -#include "tcl.h" -#include <ctype.h> -#include <zlib.h> -#include <errno.h> -#include <string.h> -#include <sys/stat.h> - -#define DEBUG 0 +/* Modified by Joseph Wang (joequant@gmail.com) to check if + file is zip when looking at path and skipping if its + not a zip file */ +#include "tclInt.h" +#include "tclPort.h" +#include <zlib.h> /* -** Size of the decompression input buffer -*/ + * Size of the decompression input buffer + */ #define COMPR_BUF_SIZE 8192 -static int maptolower=0; - +#ifdef __WIN32__ +#define NOCASE_PATHS 1 +#else +#define NOCASE_PATHS 0 +#endif /* -** 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.". -*/ + * 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 */ - Tcl_Interp *interp; + 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 -*/ + * 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 */ + int refCount; + Tcl_Obj *zName; /* Name of the archive */ + Tcl_Obj *zMountPoint; /* Where this archive is mounted */ } ZvfsArchive; - /* -** Particulars about each virtual file are recorded in an instance -** of the following structure. -*/ + * 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 */ - 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 */ + int refCount; /* Reference count */ + Tcl_Obj *zName; /* The full pathname of the virtual file */ + ZvfsArchive *pArchive; /* The ZIP archive holding this file data */ + int iOffset; /* Offset into the ZIP archive of the data */ + int nByte; /* Uncompressed size of the virtual file */ + int nByteCompr; /* Compressed size of the virtual file */ + int isdir; /* Set to 1 if directory */ + int timestamp; /* Modification time */ + int iCRC; /* Cyclic Redundancy Check of the data */ + struct ZvfsFile *parent; /* Parent directory. */ + Tcl_HashTable children; /* For directory entries, a hash table of + * all of the files in the directory. + */ } ZvfsFile; - - /* -** Macros to read 16-bit and 32-bit big-endian integers into the -** native format of this local processor. B is an array of -** characters and the integer begins at the N-th character of -** the array. -*/ + * 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 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; +/* The attributes defined for each file in the archive. + * These are accessed via the 'file attributes' command in Tcl. + */ +static CONST char *ZvfsAttrs[] = { + "-archive", "-compressedsize", "-crc", "-mount", "-offset", + "-uncompressedsize", (char *)NULL +}; +enum { + ZVFS_ATTR_ARCHIVE, ZVFS_ATTR_COMPSIZE, ZVFS_ATTR_CRC, + ZVFS_ATTR_MOUNT, ZVFS_ATTR_OFFSET, ZVFS_ATTR_UNCOMPSIZE +}; +/* Forward declarations for the callbacks to the Tcl filesystem. */ +static Tcl_FSPathInFilesystemProc PathInFilesystem; +static Tcl_FSDupInternalRepProc DupInternalRep; +static Tcl_FSFreeInternalRepProc FreeInternalRep; +static Tcl_FSInternalToNormalizedProc InternalToNormalized; +static Tcl_FSFilesystemPathTypeProc FilesystemPathType; +static Tcl_FSFilesystemSeparatorProc FilesystemSeparator; +static Tcl_FSStatProc Stat; +static Tcl_FSAccessProc Access; +static Tcl_FSOpenFileChannelProc OpenFileChannel; +static Tcl_FSMatchInDirectoryProc MatchInDirectory; +static Tcl_FSListVolumesProc ListVolumes; +static Tcl_FSFileAttrStringsProc FileAttrStrings; +static Tcl_FSFileAttrsGetProc FileAttrsGet; +static Tcl_FSFileAttrsSetProc FileAttrsSet; +static Tcl_FSChdirProc Chdir; +static Tcl_Filesystem zvfsFilesystem = { + "zvfs", + sizeof(Tcl_Filesystem), + TCL_FILESYSTEM_VERSION_1, + &PathInFilesystem, + &DupInternalRep, + &FreeInternalRep, + &InternalToNormalized, + NULL, /* &CreateInternalRep, */ + NULL, /* &NormalizePath, */ + &FilesystemPathType, + &FilesystemSeparator, + &Stat, + &Access, + &OpenFileChannel, + &MatchInDirectory, + NULL, /* &Utime, */ + NULL, /* &Link, */ + &ListVolumes, + &FileAttrStrings, + &FileAttrsGet, + &FileAttrsSet, + NULL, /* &CreateDirectory, */ + NULL, /* &RemoveDirectory, */ + NULL, /* &DeleteFile, */ + NULL, /* &CopyFile, */ + NULL, /* &RenameFile, */ + NULL, /* &CopyDirectory, */ + NULL, /* &Lstat, */ + NULL, /* &LoadFile, */ + NULL, /* &GetCwd, */ + &Chdir +}; +/* + * Forward declarations describing the channel type structure for + * opening and reading files inside of an archive. + */ +static Tcl_DriverCloseProc DriverClose; +static Tcl_DriverInputProc DriverInput; +static Tcl_DriverOutputProc DriverOutput; +static Tcl_DriverSeekProc DriverSeek; +static Tcl_DriverWatchProc DriverWatch; +static Tcl_DriverGetHandleProc DriverGetHandle; +static Tcl_ChannelType vfsChannelType = { + "zvfs", /* Type name. */ + TCL_CHANNEL_VERSION_2, /* Set blocking/nonblocking behaviour. NULL'able */ + DriverClose, /* Close channel, clean instance data */ + DriverInput, /* Handle read request */ + DriverOutput, /* Handle write request */ + DriverSeek, /* Move location of access point. NULL'able */ + NULL, /* Set options. NULL'able */ + NULL, /* Get options. NULL'able */ + DriverWatch, /* Initialize notifier */ + DriverGetHandle /* Get OS handle from the channel. */ +}; +/* + * 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)) - - /* -** 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; - + *---------------------------------------------------------------------- + * + * DosTimeDate -- + * + * Convert DOS date and time from a zip archive into clock seconds. + * + * Results: + * Clock seconds + * + *---------------------------------------------------------------------- + */ +static time_t +DosTimeDate( int dosDate, int dosTime ) +{ + time_t now; + struct tm *tm; + now=time(NULL); + tm = localtime(&now); + tm->tm_year=(((dosDate&0xfe00)>>9) + 80); + tm->tm_mon=((dosDate&0x1e0)>>5)-1; + tm->tm_mday=(dosDate & 0x1f); + tm->tm_hour=(dosTime&0xf800)>>11; + tm->tm_min=(dosTime&0x7e)>>5; + tm->tm_sec=(dosTime&0x1f); + return mktime(tm); +} +/* + *---------------------------------------------------------------------- + * + * StrDup -- + * + * Create a copy of the given string and lower it if necessary. + * + * Results: + * Pointer to the new string. Space to hold the returned + * string is obtained from Tcl_Alloc() and should be freed + * by the calling function. + * + *---------------------------------------------------------------------- + */ +char * +StrDup( char *str, int lower ) +{ + int i, c, len; + char *newstr; + len = strlen(str); + newstr = Tcl_Alloc( len + 1 ); + memcpy( newstr, str, len ); + newstr[len] = '\0'; + if( lower ) { + for( i = 0; (c = newstr[i]) != 0; ++i ) + { + if( isupper(c) ) { + newstr[i] = tolower(c); + } + } + } + return newstr; +} +/* + *---------------------------------------------------------------------- + * + * CanonicalPath -- + * + * Concatenate zTail onto zRoot to form a pathname. After + * concatenation, simplify the pathname by removing ".." and + * "." directories. + * + * Results: + * Pointer to the new pathname. Space to hold the returned + * path is obtained from Tcl_Alloc() and should be freed by + * the calling function. + * + *---------------------------------------------------------------------- + */ +static char * +CanonicalPath( const char *zRoot, const char *zTail ) +{ + char *zPath; + int i, j, c; + int len = strlen(zRoot) + strlen(zTail) + 2; #ifdef __WIN32__ - if( isalpha(zTail[0]) && zTail[1]==':' ){ zTail += 2; } - if( zTail[0]=='\\' ){ zRoot = ""; zTail++; } + if( isalpha(zTail[0]) && zTail[1]==':' ){ zTail += 2; } + if( zTail[0]=='\\' ){ zRoot = ""; zTail++; } + if( zTail[0]=='\\' ){ zRoot = "/"; zTail++; } // account for UNC style path #endif - if( zTail[0]=='/' ){ zRoot = ""; zTail++; } - zPath = Tcl_Alloc( strlen(zRoot) + strlen(zTail) + 2 ); - if( zPath==0 ) return 0; - sprintf(zPath, "%s/%s", zRoot, zTail); - for(i=j=0; (c = zPath[i])!=0; i++){ + if( zTail[0]=='/' ){ zRoot = ""; zTail++; } + if( zTail[0]=='/' ){ zRoot = "/"; zTail++; } // account for UNC style path + zPath = Tcl_Alloc( len ); + if( !zPath ) return NULL; + sprintf( zPath, "%s/%s", zRoot, zTail ); + for( i = j = 0; (c = zPath[i]) != 0; i++ ) + { #ifdef __WIN32__ - if( isupper(c) ) { if (maptolower) c = tolower(c); } - else if( c=='\\' ) c = '/'; -#endif - if( c=='/' ){ - int c2 = zPath[i+1]; - if( c2=='/' ) continue; - if( c2=='.' ){ - int c3 = zPath[i+2]; - if( c3=='/' || c3==0 ){ - i++; - continue; + if( c == '\\' ) { + c = '/'; } - if( c3=='.' && (zPath[i+3]=='.' || zPath[i+3]==0) ){ - i += 2; - while( j>0 && zPath[j-1]!='/' ){ j--; } - continue; +#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; } - zPath[j++] = c; - } - if( j==0 ){ zPath[j++] = '/'; } - zPath[j] = 0; - return zPath; -} -*/ -static char *CanonicalPath(const char *zRoot, const char *zTail) -{ - int argc = 2; - const char *argv[2]; - char *rr,*zPath; - - Tcl_DString pwd; - Tcl_DStringInit(&pwd); - - argv[0] = zRoot; - argv[1] = zTail; - - Tcl_JoinPath(argc, argv, &pwd); - rr = Tcl_DStringValue(&pwd); - zPath = Tcl_Alloc(strlen(rr)+1); - strcpy(zPath,rr); - Tcl_DStringFree(&pwd); - - return zPath; + if( j == 0 ) { + zPath[j++] = '/'; + } + zPath[j] = 0; + return zPath; } - -/* -** Construct an absolute pathname is memory obtained from Tcl_Alloc -** that means the same file as the pathname given. -*/ /* -static char *AbsolutePath(const char *zRelative){ - Tcl_DString pwd; - char *zResult; - Tcl_DStringInit(&pwd); - if( zRelative[0]!='/' ){ + *---------------------------------------------------------------------- + * + * AbsolutePath -- + * + * Construct an absolute pathname from the given pathname. On + * Windows, all backslash (\) characters are converted to + * forward slash (/), and if NOCASE_PATHS is true, all letters + * are converted to lowercase. The drive letter, if present, is + * preserved. + * + * Results: + * Pointer to the new pathname. Space to hold the returned + * path is obtained from Tcl_Alloc() and should be freed by + * the calling function. + * + *---------------------------------------------------------------------- + */ +static char * +AbsolutePath( const char *z ) +{ + int len; + char *zResult; + if( *z != '/' +#ifdef __WIN32__ + && *z != '\\' && (!isalpha(*z) || z[1] != ':' ) +#endif + ) { + /* Case 1: "z" is a relative path, so prepend the current + * working directory in order to generate an absolute path. + */ + Tcl_Obj *pwd = Tcl_FSGetCwd(NULL); + zResult = CanonicalPath( Tcl_GetString(pwd), z ); + Tcl_DecrRefCount(pwd); + } else { + /* Case 2: "z" is an absolute path already, so we + * just need to make a copy of it. + */ + zResult = StrDup( (char *)z, 0); + } + /* If we're on Windows, we want to convert all backslashes to + * forward slashes. If NOCASE_PATHS is true, we want to also + * lower the alpha characters in the path. + */ +#if NOCASE_PATHS || defined(__WIN32__) + { + int i, c; + for( i = 0; (c = zResult[i]) != 0; i++ ) + { +#if NOCASE_PATHS + if( isupper(c) ) { + zResult[i] = tolower(c); + } +#endif #ifdef __WIN32__ - if( zRelative[0]!='\\' ) + if( c == '\\' ) { + zResult[i] = '/'; + } #endif - Tcl_GetCwd(0, &pwd); - } - zResult = CanonicalPath( Tcl_DStringValue(&pwd), zRelative); - Tcl_DStringFree(&pwd); - return zResult; + } + } +#endif /* NOCASE_PATHS || defined(__WIN32__) */ + len = strlen(zResult); + /* Strip the trailing / from any directory. */ + if( zResult[len-1] == '/' ) { + zResult[len-1] = 0; + } + return zResult; } -*/ -static char *AbsolutePath(const char *zRelative) +/* + *---------------------------------------------------------------------- + * + * AddPathToArchive -- + * + * Add the given pathname to the given archive. zName is usually + * the pathname pulled from the file header in a zip archive. We + * concatenate it onto the archive's mount point to obtain a full + * path before adding it to our hash table. + * + * All parent directories of the given path will be created and + * added to the hash table. + * + * Results: + * Pointer to the new file structure or to the old file structure + * if it already existed. newPath will be true if this path is + * new to this archive or false if we already had it. + * + *---------------------------------------------------------------------- + */ +static ZvfsFile * +AddPathToArchive( ZvfsArchive *pArchive, char *zName, int *newPath ) { - Tcl_Obj *rr,*aa; - const char* zz; - int ll; - char* zResult; - - rr = Tcl_NewStringObj(zRelative, strlen(zRelative)); - Tcl_IncrRefCount(rr); - aa = Tcl_FSGetNormalizedPath(local.interp, rr); - Tcl_IncrRefCount(aa); - zz = Tcl_GetStringFromObj(aa,&ll); - zResult = Tcl_Alloc(ll+1); - strcpy(zResult,zz); - - Tcl_DecrRefCount(rr); - Tcl_DecrRefCount(aa); - - return zResult; + int i, len, isNew; + char *zFullPath, *izFullPath; + char *zParentPath, *izParentPath; + Tcl_HashEntry *pEntry; + Tcl_Obj *nameObj, *pathObj, *listObj; + ZvfsFile *pZvfs, *parent = NULL; + zFullPath = CanonicalPath( Tcl_GetString(pArchive->zMountPoint), zName ); + izFullPath = zFullPath; + pathObj = Tcl_NewStringObj( zFullPath, -1 ); + Tcl_IncrRefCount( pathObj ); + listObj = Tcl_FSSplitPath( pathObj, &len ); + Tcl_IncrRefCount( listObj ); + Tcl_DecrRefCount( pathObj ); + /* Walk through all the parent directories of this + * file and add them all to our archive. This is + * because some zip files don't store directory + * entries in the archive, but we need to know all + * of the directories to create the proper filesystem. + */ + for( i = 1; i < len; ++i ) + { + pathObj = Tcl_FSJoinPath( listObj, i ); + izParentPath = zParentPath = Tcl_GetString(pathObj); +#if NOCASE_PATHS + izParentPath = StrDup( zParentPath, 1 ); +#endif + pEntry = Tcl_CreateHashEntry( &local.fileHash, izParentPath, &isNew ); +#if NOCASE_PATHS + Tcl_Free( izParentPath ); +#endif + if( !isNew ) { + /* We already have this directory in our archive. */ + parent = Tcl_GetHashValue( pEntry ); + continue; + } + Tcl_ListObjIndex( NULL, listObj, i-1, &nameObj ); + Tcl_IncrRefCount(nameObj); + /* We don't have this directory in our archive yet. Add it. */ + pZvfs = (ZvfsFile*)Tcl_Alloc( sizeof(*pZvfs) ); + pZvfs->refCount = 1; + pZvfs->zName = nameObj; + pZvfs->pArchive = pArchive; + pZvfs->isdir = 1; + pZvfs->iOffset = 0; + pZvfs->timestamp = 0; + pZvfs->iCRC = 0; + pZvfs->nByteCompr = 0; + pZvfs->nByte = 0; + pZvfs->parent = parent; + Tcl_InitHashTable( &pZvfs->children, TCL_STRING_KEYS ); + Tcl_SetHashValue( pEntry, pZvfs ); + if( parent ) { + /* Add this directory to its parent's list of children. */ + pEntry = Tcl_CreateHashEntry(&parent->children,zParentPath,&isNew); + if( isNew ) { + Tcl_SetHashValue( pEntry, pZvfs ); + } + } + parent = pZvfs; + } + /* Check to see if we already have this file in our archive. */ +#if NOCASE_PATHS + izFullPath = StrDup( zFullPath, 1 ); +#endif + pEntry = Tcl_CreateHashEntry(&local.fileHash, izFullPath, newPath); +#if NOCASE_PATHS + Tcl_Free( izFullPath ); +#endif + if( *newPath ) { + /* We don't have this file in our archive. Add it. */ + Tcl_ListObjIndex( NULL, listObj, len-1, &nameObj ); + Tcl_IncrRefCount(nameObj); + pZvfs = (ZvfsFile*)Tcl_Alloc( sizeof(*pZvfs) ); + pZvfs->refCount = 1; + pZvfs->zName = nameObj; + pZvfs->pArchive = pArchive; + Tcl_SetHashValue( pEntry, pZvfs ); + /* Add this path to its parent's list of children. */ + pEntry = Tcl_CreateHashEntry(&parent->children, zFullPath, &isNew); + if( isNew ) { + Tcl_SetHashValue( pEntry, pZvfs ); + } + } else { + /* We already have this file. Set the pointer and return. */ + pZvfs = Tcl_GetHashValue( pEntry ); + } + Tcl_DecrRefCount(listObj); + Tcl_Free(zFullPath); + return pZvfs; } - /* -** Read a ZIP archive and make entries in the virutal file hash table for all -** content files of that ZIP archive. Also initialize the ZVFS if this -** routine has not been previously called. -*/ -int Zvfs_Mount( - Tcl_Interp *interp, /* Leave error messages in this interpreter */ - char *zArchive, /* The ZIP archive file */ - char *zMountPoint /* Mount contents at this directory */ -){ - Tcl_Channel chan; /* Used for reading the ZIP archive file */ - char *zArchiveName = 0; /* A copy of zArchive */ - int nFile; /* Number of files in the archive */ - 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 */ - - if( !local.isInit ) return TCL_ERROR; - if (!zArchive) { - pEntry=Tcl_FirstHashEntry(&local.archiveHash,&zSearch); - while (pEntry) { - if (pArchive = Tcl_GetHashValue(pEntry)) { - Tcl_AppendResult(interp,pArchive->zMountPoint," ",pArchive->zName," ",0); - } - pEntry=Tcl_NextHashEntry(&zSearch); + *---------------------------------------------------------------------- + * + * Zvfs_Mount -- + * + * Read a zip archive and make entries in the file hash table for + * all of the files in the archive. If Zvfs has not been initialized, + * it will be initialized here before mounting the archive. + * + * Results: + * Standard Tcl result. + * + *---------------------------------------------------------------------- + */ +int +Zvfs_Mount( + Tcl_Interp *interp, /* Leave error messages in this interpreter */ + CONST char *zArchive, /* The ZIP archive file */ + CONST char *zMountPoint /* Mount contents at this directory */ +) { + Tcl_Channel chan = NULL; /* Used for reading the ZIP archive file */ + char *zArchiveName = 0; /* A copy of zArchive */ + char *zFullMountPoint = 0; /* Absolute path to the mount point */ + int nFile; /* Number of files in the archive */ + int iPos; /* Current position in the archive file */ + int code = TCL_ERROR; /* Return code */ + int update = 1; /* Whether to update the mounts */ + 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]; /* Buffer to read from the ZIP archive */ + ZvfsFile *pZvfs; /* A new virtual file */ + Tcl_Obj *hashKeyObj = NULL; + Tcl_Obj *resultObj = Tcl_GetObjResult(interp); + Tcl_Obj *readObj = Tcl_NewObj(); + Tcl_IncrRefCount(readObj); + if( !local.isInit ) { + if( Zvfs_Init( interp ) == TCL_ERROR ) { + Tcl_SetStringObj( resultObj, "failed to initialize zvfs", -1 ); + return TCL_ERROR; + } } - return TCL_OK; - } - if (!zMountPoint) { - pEntry = Tcl_FindHashEntry(&local.archiveHash,AbsolutePath(zArchive)); - if (pEntry) { - if (pArchive = Tcl_GetHashValue(pEntry)) { - Tcl_AppendResult(interp, pArchive->zMountPoint, 0); - } + /* If zArchive is NULL, set the result to a list of all + * mounted files. + */ + if( !zArchive ) { + Tcl_HashSearch zSearch; + for( pEntry = Tcl_FirstHashEntry( &local.archiveHash,&zSearch ); + pEntry; pEntry = Tcl_NextHashEntry(&zSearch) ) + { + if( (pArchive = Tcl_GetHashValue(pEntry)) ) { + Tcl_ListObjAppendElement( interp, resultObj, + Tcl_DuplicateObj(pArchive->zName) ); + } + } + code = TCL_OK; + update = 0; + goto done; } - 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; - } - pArchive = (ZvfsArchive*)Tcl_Alloc(sizeof(*pArchive) + strlen(zMountPoint)+1); - pArchive->zName = zArchiveName; - pArchive->zMountPoint = (char*)&pArchive[1]; - strcpy(pArchive->zMountPoint, zMountPoint); - pArchive->pFiles = 0; - Tcl_SetHashValue(pEntry, pArchive); - - /* Compute the starting location of the directory for the ZIP archive - ** in iPos then seek to that location. - */ - nFile = INT16(zBuf,8); - iPos -= INT32(zBuf,12); - Tcl_Seek(chan, iPos, SEEK_SET); - - while( nFile-- > 0 ){ - int lenName; /* Length of the next filename */ - int lenExtra; /* Length of "extra" data for next file */ - int iData; /* Offset to start of file data */ - ZvfsFile *pZvfs; /* A new virtual file */ - char *zFullPath; /* Full pathname of the virtual file */ - char zName[1024]; /* Space to hold the filename */ - - /* Read the next directory entry. Extract the size of the filename, - ** the size of the "extra" information, and the offset into the archive - ** file of the file data. - */ - Tcl_Read(chan, zBuf, 46); - if (memcmp(zBuf, "\120\113\01\02", 4)) { - Tcl_AppendResult(interp, "ill-formed central directory entry", NULL); - return TCL_ERROR; - } - lenName = INT16(zBuf,28); - lenExtra = INT16(zBuf,30) + INT16(zBuf,32); - iData = INT32(zBuf,42); - - /* If the virtual filename is too big to fit in zName[], then skip - ** this file - */ - if( lenName >= sizeof(zName) ){ - Tcl_Seek(chan, lenName + lenExtra, SEEK_CUR); - continue; + /* If zMountPoint is NULL, set the result to the mount point + * for the specified archive file. + */ + if( !zMountPoint ) { + int found = 0; + Tcl_HashSearch zSearch; + zArchiveName = AbsolutePath( zArchive ); + for( pEntry = Tcl_FirstHashEntry(&local.archiveHash,&zSearch); + pEntry; pEntry = Tcl_NextHashEntry(&zSearch) ) + { + pArchive = Tcl_GetHashValue(pEntry); + if ( !strcmp( Tcl_GetString(pArchive->zName), zArchiveName ) ) { + ++found; + Tcl_SetStringObj( resultObj, + Tcl_GetString(pArchive->zMountPoint), -1 ); + break; + } + } + if( !found ) { + Tcl_SetStringObj( resultObj, "archive not mounted by zvfs", -1 ); + } + code = found ? TCL_OK : TCL_ERROR; + update = 0; + goto done; } - - /* Construct an entry in local.fileHash for this virtual file. - */ - Tcl_Read(chan, zName, lenName); - zName[lenName] = 0; - zFullPath = CanonicalPath(zMountPoint, zName); - if (DEBUG) - printf("Mount: %s %s \n",zName, zFullPath); - pZvfs = (ZvfsFile*)Tcl_Alloc( sizeof(*pZvfs) ); - pZvfs->zName = zFullPath; - pZvfs->pArchive = pArchive; - pZvfs->iOffset = iData; - pZvfs->nByte = INT32(zBuf, 24); - pZvfs->nByteCompr = INT32(zBuf, 20); - pZvfs->pNext = pArchive->pFiles; - pArchive->pFiles = pZvfs; - pEntry = Tcl_CreateHashEntry(&local.fileHash, zFullPath, &isNew); - if( isNew ){ - pZvfs->pNextName = 0; - }else{ - ZvfsFile *pOld = (ZvfsFile*)Tcl_GetHashValue(pEntry); - pOld->pPrevName = pZvfs; - pZvfs->pNextName = pOld; - } - pZvfs->pPrevName = 0; - Tcl_SetHashValue(pEntry, (ClientData) pZvfs); - - /* Skip over the extra information so that the next read will be from - ** the beginning of the next directory entry. - */ - Tcl_Seek(chan, lenExtra, SEEK_CUR); - } - Tcl_Close(interp, chan); - return TCL_OK; -} - -/* -** Locate the ZvfsFile structure that corresponds to the file named. -** Return NULL if there is no such ZvfsFile. -*/ -static ZvfsFile *ZvfsLookup(char *zFilename){ - char *zTrueName; - Tcl_HashEntry *pEntry; - ZvfsFile *pFile; - - if( local.isInit==0 ) return 0; - zTrueName = AbsolutePath(zFilename); - pEntry = Tcl_FindHashEntry(&local.fileHash, zTrueName); - pFile = pEntry ? Tcl_GetHashValue(pEntry) : 0; - if (DEBUG) - printf("%s %s %d\n",zFilename, zTrueName, pFile); - Tcl_Free(zTrueName); - return pFile; + if( !(chan = Tcl_OpenFileChannel(interp, zArchive, "r", 0)) ) { + goto done; + } + if(Tcl_SetChannelOption(interp, chan, "-translation", "binary") != TCL_OK) { + goto done; + } + /* 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_SetStringObj( resultObj, "bad end of central directory record", -1); + goto done; + } + zArchiveName = AbsolutePath( zArchive ); + zFullMountPoint = AbsolutePath( zMountPoint ); + hashKeyObj = Tcl_NewObj(); + Tcl_IncrRefCount(hashKeyObj); + Tcl_AppendStringsToObj( hashKeyObj, zArchiveName, ":", zFullMountPoint, + (char *)NULL ); + pEntry = Tcl_CreateHashEntry( &local.archiveHash, + Tcl_GetString(hashKeyObj), &isNew ); + if( !isNew ) { + /* This archive is already mounted. Set the result to + * the current mount point and return. + */ + pArchive = Tcl_GetHashValue(pEntry); + code = TCL_OK; + update = 0; + goto done; + } + pArchive = (ZvfsArchive*)Tcl_Alloc(sizeof(*pArchive)); + pArchive->refCount = 1; + pArchive->zName = Tcl_NewStringObj(zArchiveName,-1); + pArchive->zMountPoint = Tcl_NewStringObj(zFullMountPoint,-1); + Tcl_SetHashValue(pEntry, pArchive); + /* Add the root mount point to our list of archive files as a directory. */ + pEntry = Tcl_CreateHashEntry(&local.fileHash, zFullMountPoint, &isNew); + if( isNew ) { + pZvfs = (ZvfsFile*)Tcl_Alloc( sizeof(*pZvfs) ); + pZvfs->refCount = 1; + pZvfs->zName = Tcl_NewStringObj(zFullMountPoint,-1); + pZvfs->pArchive = pArchive; + pZvfs->isdir = 1; + pZvfs->iOffset = 0; + pZvfs->timestamp = 0; + pZvfs->iCRC = 0; + pZvfs->nByteCompr = 0; + pZvfs->nByte = 0; + pZvfs->parent = NULL; + Tcl_InitHashTable( &pZvfs->children, TCL_STRING_KEYS ); + Tcl_SetHashValue( pEntry, pZvfs ); + } + /* Compute the starting location of the directory for the + * ZIP archive in iPos then seek to that location. + */ + nFile = INT16(zBuf,8); + iPos -= INT32(zBuf,12); + Tcl_Seek( chan, iPos, SEEK_SET ); + while( nFile-- > 0 ) + { + int isdir = 0; + int iData; /* Offset to start of file data */ + int lenName; /* Length of the next filename */ + int lenExtra; /* Length of "extra" data for next file */ + int attributes; /* DOS attributes */ + char *zName; + char *zFullPath; /* Full pathname of the virtual file */ + char *izFullPath; /* Lowercase full pathname */ + ZvfsFile *parent; + /* 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) ) { + Zvfs_Unmount( interp, zArchiveName ); + Tcl_SetStringObj( resultObj, "bad central file record", -1 ); + goto done; + } + lenName = INT16(zBuf,28); + lenExtra = INT16(zBuf,30) + INT16(zBuf,32); + iData = INT32(zBuf,42); + /* Construct an entry in local.fileHash for this virtual file. */ + Tcl_ReadChars( chan, readObj, lenName, 0 ); + zName = Tcl_GetString(readObj); + if( zName[--lenName] == '/' ) { + isdir = 1; + Tcl_SetObjLength( readObj, lenName ); + } + pZvfs = AddPathToArchive( pArchive, zName, &isNew ); + pZvfs->isdir = isdir; + pZvfs->iOffset = iData; + pZvfs->timestamp = DosTimeDate(INT16(zBuf, 14), INT16(zBuf, 12)); + pZvfs->iCRC = INT32(zBuf, 16); + pZvfs->nByteCompr = INT32(zBuf, 20); + pZvfs->nByte = INT32(zBuf, 24); + /* If this is a directory we want to initialize the + * hash table to store its children if it has any. + */ + if( isNew && isdir ) { + Tcl_InitHashTable( &pZvfs->children, TCL_STRING_KEYS ); + } + /* 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 ); + } + code = TCL_OK; +done: + if( chan ) Tcl_Close( interp, chan ); + if( readObj ) Tcl_DecrRefCount(readObj); + if( hashKeyObj ) Tcl_DecrRefCount(hashKeyObj); + if( zArchiveName ) Tcl_Free(zArchiveName); + if( zFullMountPoint ) Tcl_Free(zFullMountPoint); + if( code == TCL_OK && update ) { + Tcl_FSMountsChanged( &zvfsFilesystem ); + Tcl_SetStringObj( resultObj, zMountPoint, -1 ); + } + return code; } - /* -** Unmount all the files in the given ZIP archive. -*/ -static void Zvfs_Unmount(char *zArchive){ - char *zArchiveName; - ZvfsArchive *pArchive; - ZvfsFile *pFile, *pNextFile; - Tcl_HashEntry *pEntry; - - zArchiveName = AbsolutePath(zArchive); - pEntry = Tcl_FindHashEntry(&local.archiveHash, zArchiveName); - Tcl_Free(zArchiveName); - if( pEntry==0 ) return; - pArchive = Tcl_GetHashValue(pEntry); - Tcl_DeleteHashEntry(pEntry); - Tcl_Free(pArchive->zName); - for(pFile=pArchive->pFiles; pFile; pFile=pNextFile){ - pNextFile = pFile->pNext; - if( pFile->pNextName ){ - pFile->pNextName->pPrevName = pFile->pPrevName; - } - if( pFile->pPrevName ){ - pFile->pPrevName->pNextName = pFile->pNextName; - }else{ - pEntry = Tcl_FindHashEntry(&local.fileHash, pFile->zName); - if( pEntry==0 ){ - /* This should never happen */ - }else if( pFile->pNextName ){ - Tcl_SetHashValue(pEntry, pFile->pNextName); - }else{ + *---------------------------------------------------------------------- + * + * Zvfs_Unmount -- + * + * Unmount all the files in the given zip archive. All the + * entries in the file hash table for the archive are deleted + * as well as the entry in the archive hash table. + * + * Any memory associated with the entries will be freed as well. + * + * Results: + * Standard Tcl result. + * + *---------------------------------------------------------------------- + */ +int +Zvfs_Unmount( Tcl_Interp *interp, CONST char *zMountPoint ) +{ + int found = 0; + ZvfsFile *pFile; + ZvfsArchive *pArchive; + Tcl_HashEntry *pEntry; + Tcl_HashSearch zSearch; + Tcl_HashEntry *fEntry; + Tcl_HashSearch fSearch; + for( pEntry = Tcl_FirstHashEntry( &local.archiveHash, &zSearch ); + pEntry; pEntry = Tcl_NextHashEntry(&zSearch) ) + { + pArchive = Tcl_GetHashValue(pEntry); + if( !Tcl_StringCaseMatch( zMountPoint, + Tcl_GetString(pArchive->zMountPoint), NOCASE_PATHS ) ) continue; + found++; + for( fEntry = Tcl_FirstHashEntry( &local.fileHash, &fSearch ); + fEntry; fEntry = Tcl_NextHashEntry(&fSearch) ) + { + pFile = Tcl_GetHashValue(fEntry); + if( pFile->pArchive == pArchive ) { + FreeInternalRep( (ClientData)pFile ); + Tcl_DeleteHashEntry(fEntry); + } + } Tcl_DeleteHashEntry(pEntry); - } + Tcl_DecrRefCount(pArchive->zName); + Tcl_DecrRefCount(pArchive->zMountPoint); + Tcl_Free( (char *)pArchive ); } - Tcl_Free(pFile->zName); - Tcl_Free((char*)pFile); - } -} - -/* -** zvfs::mount Zip-archive-name mount-point -** -** Create a new mount point on the given ZIP archive. After this -** command executes, files contained in the ZIP archive will appear -** to Tcl to be regular files at the mount point. -*/ -static int ZvfsMountCmd( - void *NotUsed, /* Client data for this command */ - Tcl_Interp *interp, /* The interpreter used to report errors */ - int argc, /* Number of arguments */ - 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 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( - void *NotUsed, /* Client data for this command */ - Tcl_Interp *interp, /* The interpreter used to report errors */ - int argc, /* Number of arguments */ - char **argv /* Values of all arguments */ -){ - if( argc!=2 ){ - Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], - " ZIP-FILE\"", 0); - return TCL_ERROR; - } - Zvfs_Unmount(argv[1]); - return TCL_OK; + if( !found ) { + if( interp ) { + Tcl_AppendStringsToObj( Tcl_GetObjResult(interp), + zMountPoint, " is not a zvfs mount", (char *)NULL ); + } + return TCL_ERROR; + } + Tcl_FSMountsChanged( &zvfsFilesystem ); + return TCL_OK; } - /* -** zvfs::exists filename -** -** Return TRUE if the given filename exists in the ZVFS and FALSE if -** it does not. -*/ -static int ZvfsExistsObjCmd( - void *NotUsed, /* 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_GetStringFromObj(objv[1], 0); - if (DEBUG) - printf("ZvfsExistsObjCmd: "); - Tcl_SetBooleanObj( Tcl_GetObjResult(interp), ZvfsLookup(zFilename)!=0); - return TCL_OK; + *---------------------------------------------------------------------- + * + * ZvfsLookup -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Look into the file hash table for a given path and see if + * it belongs to our filesystem. + * + * Results: + * Pointer to the file structure or NULL if it was not found. + * + *---------------------------------------------------------------------- + */ +static ZvfsFile * +ZvfsLookup( Tcl_Obj *pathPtr ) +{ + char *zTrueName; + Tcl_HashEntry *pEntry; + zTrueName = AbsolutePath( Tcl_GetString(pathPtr) ); + pEntry = Tcl_FindHashEntry( &local.fileHash, zTrueName ); + Tcl_Free(zTrueName); + return pEntry ? Tcl_GetHashValue(pEntry) : NULL; } - /* -** zvfs::info filename -** -** Return information about the given file in the ZVFS. The information -** consists of (1) the name of the ZIP archive that contains the file, -** (2) the size of the file after decompressions, (3) the compressed -** size of the file, and (4) the offset of the compressed data in the archive. -*/ -static int ZvfsInfoObjCmd( - void *NotUsed, /* 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_GetStringFromObj(objv[1], 0); - if (DEBUG) - printf("ZvfsInfoObjCmd: "); - 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; + *---------------------------------------------------------------------- + * + * GetZvfsFile -- + * + * Part of the "zvfs" Tcl_Filesystem. + * For a given pathPtr, return the internal representation + * of the path for our filesystem. + * + * Results: + * Pointer to the file structure or NULL if it was not found. + * + *---------------------------------------------------------------------- + */ +static ZvfsFile * +GetZvfsFile( Tcl_Obj *pathPtr ) +{ + ZvfsFile *pFile = (ZvfsFile *)Tcl_FSGetInternalRep(pathPtr,&zvfsFilesystem); + return pFile == NULL || pFile->pArchive->refCount == 0 ? NULL : pFile; } /* -** zvfs::list -** -** Return a list of all files in the ZVFS. The order of the names -** in the list is arbitrary. -*/ -static int ZvfsListObjCmd( - void *NotUsed, /* 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_GetStringFromObj(objv[1], 0); - } - 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; + *---------------------------------------------------------------------- + * + * ZvfsFileMatchesType -- + * + * Part of the "zvfs" Tcl_Filesystem. + * See if the given ZvfsFile matches the type data given. + * + * Results: + * 1 if true, 0 if false + * + *---------------------------------------------------------------------- + */ +static int +ZvfsFileMatchesType( ZvfsFile *pFile, Tcl_GlobTypeData *types ) +{ + if( types ) { + if( types->type & TCL_GLOB_TYPE_FILE && pFile->isdir ) { + return 0; + } + if( types->type & (TCL_GLOB_TYPE_DIR | TCL_GLOB_TYPE_MOUNT) + && !pFile->isdir ) { + return 0; + } + if( types->type & TCL_GLOB_TYPE_MOUNT && pFile->parent ) { + return 0; + } + } + return 1; } - -/* -** 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; + *---------------------------------------------------------------------- + * + * DriverExit -- + * + * This function is called as an exit handler for the channel + * driver. If we do not set pInfo.chan to NULL, Tcl_Close() + * will be called twice on that channel when Tcl_Exit runs. + * This will lead to a core dump + * + * Results: + * None + * + *---------------------------------------------------------------------- + */ +static void +DriverExit( void *pArg ) +{ + ZvfsChannelInfo *pInfo = (ZvfsChannelInfo*)pArg; + pInfo->chan = 0; } - /* -** This routine is called when the ZVFS channel is closed -*/ -static int vfsClose( + *---------------------------------------------------------------------- + * + * DriverClose -- + * + * Called when a channel is closed. + * + * Results: + * Returns TCL_OK. + * + *---------------------------------------------------------------------- + */ +static int +DriverClose( 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; +) { + ZvfsChannelInfo* pInfo = (ZvfsChannelInfo*)instanceData; + if( pInfo->zBuf ){ + Tcl_Free(pInfo->zBuf); + inflateEnd(&pInfo->stream); + } + if( pInfo->chan ){ + Tcl_Close(interp, pInfo->chan); + Tcl_DeleteExitHandler(DriverExit, 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 ( + *---------------------------------------------------------------------- + * + * DriverInput -- + * + * The Tcl channel system calls this function on each read + * from a channel. The channel is opened into the actual + * archive file, but the data is read from the individual + * file entry inside the zip archive. + * + * Results: + * Number of bytes read. + * + *---------------------------------------------------------------------- + */ +static int +DriverInput ( 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; +) { + 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; } - 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; + 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); } - }else{ - toRead = Tcl_Read(pInfo->chan, buf, toRead); - } - pInfo->nByte -= toRead; - pInfo->readSoFar += toRead; - *pErrorCode = 0; - return 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( + *---------------------------------------------------------------------- + * + * DriverOutput -- + * + * Called to write to a file. Since this is a read-only file + * system, this function will always return an error. + * + * Results: + * Returns -1. + * + *---------------------------------------------------------------------- + */ +static int +DriverOutput( ClientData instanceData, /* The channel to write to */ - char *buf, /* Data to be stored. */ + CONST char *buf, /* Data to be stored. */ int toWrite, /* Number of bytes to write. */ int *pErrorCode /* Location of error flag. */ -){ - *pErrorCode = EINVAL; - return -1; +) { + *pErrorCode = EINVAL; + return -1; } - /* -** Move the file pointer so that the next byte read will be "offset". -*/ -static int vfsSeek( + *---------------------------------------------------------------------- + * + * DriverSeek -- + * + * Seek along the open channel to another point. + * + * Results: + * Offset into the file. + * + *---------------------------------------------------------------------- + */ +static int +DriverSeek( 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( !pInfo->isCompressed ){ - Tcl_Seek(pInfo->chan, offset + pInfo->startOfData, SEEK_SET); - }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; + 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( !pInfo->isCompressed ){ + /* dont seek behind end of data */ + if (pInfo->nData < (unsigned long)offset) { + return -1; + } + /* do the job, save and check the result */ + offset = Tcl_Seek(pInfo->chan, offset + pInfo->startOfData, SEEK_SET); + if (offset == -1) { + return -1; + } + /* adjust the counters (use real offset) */ + pInfo->readSoFar = offset - pInfo->startOfData; + pInfo->nByte = pInfo->nData - pInfo->readSoFar; + } 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); + DriverInput(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( + *---------------------------------------------------------------------- + * + * DriverWatch -- + * + * Called to handle events on the channel. Since zvfs files + * don't generate events, this is a no-op. + * + * Results: + * None + * + *---------------------------------------------------------------------- + */ +static void +DriverWatch( ClientData instanceData, /* Channel to watch */ int mask /* Events of interest */ -){ - return; +) { + 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( + *---------------------------------------------------------------------- + * + * DriverGetHandle -- + * + * Retrieve a device-specific handle from the given channel. + * Since we don't have a device-specific handle, this is a no-op. + * + * Results: + * Returns TCL_ERROR. + * + *---------------------------------------------------------------------- + */ +static int +DriverGetHandle( ClientData instanceData, /* Channel to query */ int direction, /* Direction of interest */ ClientData* handlePtr /* Space to the handle into */ -){ - return TCL_ERROR; +) { + 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. */ -}; + *---------------------------------------------------------------------- + * + * PathInFilesystem -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Check to see if the given path is part of our filesystem. + * We check the file hash table for the path, and if we find + * it, set clientDataPtr to the ZvfsFile pointer so that Tcl + * will cache it for later. + * + * Results: + * TCL_OK on success, or -1 on failure + * + *---------------------------------------------------------------------- + */ +static int +PathInFilesystem( Tcl_Obj *pathPtr, ClientData *clientDataPtr ) +{ + ZvfsFile *pFile = ZvfsLookup(pathPtr); + unsigned char zBuf[50]; + int chan, bytes_read; + if( !pFile ) { + return -1; + } + if(!(chan = open(Tcl_GetString(pFile->pArchive->zName), + O_RDONLY))) { + return -1; + } -/* -** 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]; + lseek(chan, pFile->iOffset, SEEK_SET); + bytes_read = read(chan, zBuf, 4); + close( chan ); + if( bytes_read < 4 || + memcmp(zBuf, "\120\113\03\04", 4) ){ + return -1; + } - if (DEBUG) - printf("ZvfsFileOpen: "); - pFile = ZvfsLookup(zFilename); - if( pFile==0 ) return NULL; - chan = Tcl_OpenFileChannel(interp, pFile->pArchive->zName, "r", 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); + *clientDataPtr = DupInternalRep((ClientData)pFile); + return TCL_OK; +} +/* + *---------------------------------------------------------------------- + * + * DupInternalRep -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Duplicate the ZvfsFile "native" rep of a path. + * + * Results: + * Returns clientData, with refcount incremented. + * + *---------------------------------------------------------------------- + */ +static ClientData +DupInternalRep( ClientData clientData ) +{ + ZvfsFile *pFile = (ZvfsFile *)clientData; + pFile->refCount++; + return (ClientData)pFile; +} +/* + *---------------------------------------------------------------------- + * + * FreeInternalRep -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Free one reference to the ZvfsFile "native" rep of a path. + * When all references are gone, free the struct. + * + * Side effects: + * May free memory. + * + *---------------------------------------------------------------------- + */ +static void +FreeInternalRep( ClientData clientData ) +{ + ZvfsFile *pFile = (ZvfsFile *)clientData; + if (--pFile->refCount <= 0) { + if( pFile->isdir ) { + /* Delete the hash table containing the children + * of this directory. We don't need to free the + * data for each entry in the table because they're + * just pointers to the ZvfsFiles, and those will + * be freed below. + */ + Tcl_DeleteHashTable( &pFile->children ); + } + Tcl_DecrRefCount(pFile->zName); + Tcl_Free((char *)pFile); + } +} +/* + *---------------------------------------------------------------------- + * + * InternalToNormalized -- + * + * Part of the "zvfs" Tcl_Filesystem. + * From a ZvfsFile representation, produce the path string rep. + * + * Results: + * Returns a Tcl_Obj holding the string rep. + * + *---------------------------------------------------------------------- + */ +static Tcl_Obj * +InternalToNormalized( ClientData clientData ) +{ + ZvfsFile *pFile = (ZvfsFile *)clientData; + if( !pFile->parent ) { + return Tcl_DuplicateObj( pFile->zName ); + } else { + return Tcl_FSJoinToPath( pFile->parent->zName, 1, &pFile->zName ); + } +} +/* + *---------------------------------------------------------------------- + * + * FilesystemPathType -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Used for informational purposes only. Return a Tcl_Obj + * which describes the "type" of path this is. For our + * little filesystem, they're all "zip". + * + * Results: + * Tcl_Obj with 0 refCount + * + *---------------------------------------------------------------------- + */ +static Tcl_Obj * +FilesystemPathType( Tcl_Obj *pathPtr ) +{ + return Tcl_NewStringObj( "zip", -1 ); +} +/* + *---------------------------------------------------------------------- + * + * FileSystemSeparator -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Return a Tcl_Obj describing the separator character for + * our filesystem. We like things the old-fashioned way, + * so we'll just use /. + * + * Results: + * Tcl_Obj with 0 refCount + * + *---------------------------------------------------------------------- + */ +static Tcl_Obj * +FilesystemSeparator( Tcl_Obj *pathPtr ) +{ + return Tcl_NewStringObj( "/", -1 ); +} +/* + *---------------------------------------------------------------------- + * + * Stat -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Does a stat() system call for a zvfs file. Fill the stat + * buf with as much information as we have. + * + * Results: + * 0 on success, -1 on failure. + * + *---------------------------------------------------------------------- + */ +static int +Stat( Tcl_Obj *pathPtr, Tcl_StatBuf *buf ) +{ + ZvfsFile *pFile; + if( !(pFile = GetZvfsFile(pathPtr)) ) { + return -1; + } + memset(buf, 0, sizeof(*buf)); + if (pFile->isdir) { + buf->st_mode = 040555; + } else { + buf->st_mode = 0100555; + } + buf->st_size = pFile->nByte; + buf->st_mtime = pFile->timestamp; + buf->st_ctime = pFile->timestamp; + buf->st_atime = pFile->timestamp; return 0; - } - 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); +} +/* + *---------------------------------------------------------------------- + * + * Access -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Does an access() system call for a zvfs file. + * + * Results: + * 0 on success, -1 on failure. + * + *---------------------------------------------------------------------- + */ +static int +Access( Tcl_Obj *pathPtr, int mode ) +{ + if( mode & 3 || !GetZvfsFile(pathPtr) ) return -1; 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; - - if (DEBUG) - printf("ZvfsFileStat: "); - pFile = ZvfsLookup(path); - if( pFile==0 ){ - return -1; - } - memset(buf, 0, sizeof(*buf)); - buf->st_mode = 0400; - buf->st_size = pFile->nByte; - return 0; + *---------------------------------------------------------------------- + * + * OpenFileChannel -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Called when Tcl wants to open a file inside a zvfs file system. + * We actually open the zip file back up and seek to the offset + * of the given file. The channel driver will take care of the + * rest. + * + * Results: + * New channel on success, NULL on failure. + * + *---------------------------------------------------------------------- + */ +static Tcl_Channel +OpenFileChannel( Tcl_Interp *interp, Tcl_Obj *pathPtr, + int mode, int permissions ) +{ + ZvfsFile *pFile; + ZvfsChannelInfo *pInfo; + Tcl_Channel chan; + static int count = 1; + char zName[50]; + unsigned char zBuf[50]; + if( !(pFile = GetZvfsFile(pathPtr)) ) { + return NULL; + } + if(!(chan = Tcl_OpenFileChannel(interp, + Tcl_GetString(pFile->pArchive->zName), "r", 0))) { + return NULL; + } + if( Tcl_SetChannelOption(interp, chan, "-translation", "binary") ) { + /* this should never happen */ + Tcl_Close( NULL, chan ); + return NULL; + } + Tcl_Seek(chan, pFile->iOffset, SEEK_SET); + Tcl_Read(chan, zBuf, 30); + if( memcmp(zBuf, "\120\113\03\04", 4) ){ + if( interp ) { + Tcl_SetStringObj( Tcl_GetObjResult(interp), + "bad central file record", -1 ); + } + Tcl_Close( interp, chan ); + return NULL; + } + pInfo = (ZvfsChannelInfo*)Tcl_Alloc( sizeof(*pInfo) ); + pInfo->chan = chan; + Tcl_CreateExitHandler(DriverExit, 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, "zvfs%x%x", ((int)pFile)>>12, count++ ); + return Tcl_CreateChannel( &vfsChannelType, zName, + (ClientData)pInfo, TCL_READABLE ); } - /* -** 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; - } - if (DEBUG) - printf("ZvfsFileAccess: "); - pFile = ZvfsLookup(path); - if( pFile==0 ){ - return -1; - } - return 0; + *---------------------------------------------------------------------- + * + * MatchInDirectory -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Called when Tcl is globbing around through the filesystem. + * This function can be called when Tcl is looking for mount + * points or when it is looking for files within a mount point + * that it has already determined belongs to us. + * + * Any matching file in our filesystem is appended to the + * result pointer. + * + * Results: + * Standard Tcl result + * + *---------------------------------------------------------------------- + */ +/* Function to process a 'MatchInDirectory()'. + * If not implemented, then glob and recursive + * copy functionality will be lacking in the filesystem. + */ +static int +MatchInDirectory( + Tcl_Interp* interp, + Tcl_Obj *result, + Tcl_Obj *pathPtr, + CONST char *pattern, + Tcl_GlobTypeData *types +) { + ZvfsFile *pFile; + Tcl_HashEntry *pEntry; + Tcl_HashSearch sSearch; + if( types && types->type & TCL_GLOB_TYPE_MOUNT ) { + /* Tcl is looking for a list of our mount points that + * match the given pattern. This is so that Tcl can + * append vfs mounted directories to a list of actual + * filesystem directories. + */ + char *path, *zPattern; + ZvfsArchive *pArchive; + Tcl_Obj *patternObj = Tcl_NewObj(); + path = AbsolutePath( Tcl_GetString(pathPtr) ); + Tcl_AppendStringsToObj( patternObj, path, "/", pattern, (char *)NULL ); + Tcl_Free(path); + zPattern = Tcl_GetString( patternObj ); + for( pEntry = Tcl_FirstHashEntry( &local.archiveHash, &sSearch ); + pEntry; pEntry = Tcl_NextHashEntry( &sSearch ) ) + { + pArchive = Tcl_GetHashValue(pEntry); + if( Tcl_StringCaseMatch( Tcl_GetString(pArchive->zMountPoint), + zPattern, NOCASE_PATHS ) ) { + Tcl_ListObjAppendElement( NULL, result, + Tcl_DuplicateObj(pArchive->zMountPoint) ); + } + } + Tcl_DecrRefCount(patternObj); + return TCL_OK; + } + if( !(pFile = GetZvfsFile(pathPtr)) ) { + Tcl_SetStringObj( Tcl_GetObjResult(interp), "stale file handle", -1 ); + return TCL_ERROR; + } + if( !pattern ) { + /* If pattern is null, Tcl is actually just checking to + * see if this file exists in our filesystem. Check to + * make sure the path matches any type data and then + * append it to the result and return. + */ + if( ZvfsFileMatchesType( pFile, types ) ) { + Tcl_ListObjAppendElement( NULL, result, pathPtr ); + } + return TCL_OK; + } + /* We've determined that the requested path is in our filesystem, + * so now we want to walk through the children of the directory + * and find any that match the given pattern and type. Any we + * find will be appended to the result. + */ + for( pEntry = Tcl_FirstHashEntry(&pFile->children, &sSearch); + pEntry; pEntry = Tcl_NextHashEntry(&sSearch) ) + { + char *zName; + pFile = Tcl_GetHashValue(pEntry); + zName = Tcl_GetString(pFile->zName); + if( ZvfsFileMatchesType( pFile, types ) + && Tcl_StringCaseMatch(zName, pattern, NOCASE_PATHS) ) { + Tcl_ListObjAppendElement( NULL, result, + Tcl_FSJoinToPath(pathPtr, 1, &pFile->zName ) ); + } + } + return TCL_OK; } - /* -** 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" -; - - -void (*Zvfs_PostInit)(Tcl_Interp *)=0; - + *---------------------------------------------------------------------- + * + * ListVolumes -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Called when Tcl is looking for a list of open volumes + * for our filesystem. The mountpoint for each open archive + * is appended to a list object. + * + * Results: + * A Tcl_Obj with 0 refCount + * + *---------------------------------------------------------------------- + */ +static Tcl_Obj * +ListVolumes(void) +{ + Tcl_HashEntry *pEntry; /* Hash table entry */ + Tcl_HashSearch zSearch; /* Search all mount points */ + ZvfsArchive *pArchive; /* The ZIP archive being mounted */ + Tcl_Obj *pVols = Tcl_NewObj(); + + for( pEntry = Tcl_FirstHashEntry(&local.archiveHash,&zSearch); + pEntry; pEntry = Tcl_NextHashEntry(&zSearch) ) + { + pArchive = Tcl_GetHashValue(pEntry); + Tcl_ListObjAppendElement( NULL, pVols, + Tcl_DuplicateObj(pArchive->zMountPoint) ); + } + return pVols; +} /* -** Initialize the ZVFS system. -*/ -int Zvfs_doInit(Tcl_Interp *interp, int safe){ -#ifdef USE_TCL_STUBS - if( Tcl_InitStubs(interp,"8.0",0)==0 ){ + *---------------------------------------------------------------------- + * + * FileAttrStrings -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Return an array of strings for all of the possible + * attributes for a file in zvfs. + * + * Results: + * Pointer to ZvfsAttrs + * + *---------------------------------------------------------------------- + */ +static CONST char ** +FileAttrStrings( Tcl_Obj *pathPtr, Tcl_Obj** objPtrRef ) +{ + return ZvfsAttrs; +} +/* + *---------------------------------------------------------------------- + * + * FileAttrsGet -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Called for a "file attributes" command from Tcl + * to return the attributes for a file in our filesystem. + * + * objPtrRef will point to a 0 refCount Tcl_Obj on success. + * + * Results: + * Standard Tcl result + * + *---------------------------------------------------------------------- + */ +static int +FileAttrsGet( Tcl_Interp *interp, int index, + Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef ) +{ + char *zFilename; + ZvfsFile *pFile; + zFilename = Tcl_GetString(pathPtr); + if( !(pFile = GetZvfsFile(pathPtr)) ) { + return TCL_ERROR; + } + switch(index) { + case ZVFS_ATTR_ARCHIVE: + *objPtrRef= Tcl_DuplicateObj(pFile->pArchive->zName); + return TCL_OK; + case ZVFS_ATTR_COMPSIZE: + *objPtrRef=Tcl_NewIntObj(pFile->nByteCompr); + return TCL_OK; + case ZVFS_ATTR_CRC: + *objPtrRef=Tcl_NewIntObj(pFile->iCRC); + return TCL_OK; + case ZVFS_ATTR_MOUNT: + *objPtrRef= Tcl_DuplicateObj(pFile->pArchive->zMountPoint); + return TCL_OK; + case ZVFS_ATTR_OFFSET: + *objPtrRef= Tcl_NewIntObj(pFile->nByte); + return TCL_OK; + case ZVFS_ATTR_UNCOMPSIZE: + *objPtrRef= Tcl_NewIntObj(pFile->nByte); + return TCL_OK; + default: + return TCL_ERROR; + } + return TCL_OK; +} +/* + *---------------------------------------------------------------------- + * + * FileAttrsSet -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Called to set the value of an attribute for the + * given file. Since we're a read-only filesystem, this + * always returns an error. + * + * Results: + * Returns TCL_ERROR + * + *---------------------------------------------------------------------- + */ +static int +FileAttrsSet( Tcl_Interp *interp, int index, + Tcl_Obj *pathPtr, Tcl_Obj *objPtr ) +{ return TCL_ERROR; - } -#endif - Tcl_PkgProvide(interp, "zvfs", "1.0"); - if (!safe) { - Tcl_CreateCommand(interp, "zvfs::mount", ZvfsMountCmd, 0, 0); - Tcl_CreateCommand(interp, "zvfs::unmount", ZvfsUnmountCmd, 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_GlobalEval(interp, zFileCopy); - if( !local.isInit ){ - /* One-time initialization of the ZVFS */ - extern void TclAccessInsertProc(); - extern void TclStatInsertProc(); - extern void TclOpenFileChannelInsertProc(); - Tcl_InitHashTable(&local.fileHash, TCL_STRING_KEYS); - Tcl_InitHashTable(&local.archiveHash, TCL_STRING_KEYS); - TclAccessInsertProc(ZvfsFileAccess); - TclStatInsertProc(ZvfsFileStat); - TclOpenFileChannelInsertProc(ZvfsFileOpen); - local.isInit = 1; - local.interp = interp; - } - if (Zvfs_PostInit) Zvfs_PostInit(interp); - return TCL_OK; } - -int Zvfs_Init(Tcl_Interp *interp){ - return Zvfs_doInit(interp,0); +/* + *---------------------------------------------------------------------- + * + * Chdir -- + * + * Part of the "zvfs" Tcl_Filesystem. + * Handles a chdir() call for the filesystem. Tcl has + * already determined that the directory belongs to us, + * so we just need to check and make sure that the path + * is actually a directory in our filesystem and not a + * regular file. + * + * Results: + * 0 on success, -1 on failure. + * + *---------------------------------------------------------------------- + */ +static int +Chdir( Tcl_Obj *pathPtr ) +{ + ZvfsFile *zFile = GetZvfsFile(pathPtr); + if( !zFile || !zFile->isdir ) return -1; + return 0; } - -int Zvfs_SafeInit(Tcl_Interp *interp){ - return Zvfs_doInit(interp,1); +/* + *---------------------------------------------------------------------- + * + * MountObjCmd -- + * + * This function implements the [zvfs::mount] command. + * + * zvfs::mount ?zipFile? ?mountPoint? + * + * Creates a new mount point to the given zip archive. + * All files in the zip archive will be added to the + * virtual filesystem and be available to Tcl as regular + * files and directories. + * + * Results: + * Standard Tcl result + * + *---------------------------------------------------------------------- + */ +static int +MountObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +) { + char *zipFile = NULL, *mountPoint = NULL; + if( objc > 3 ) { + Tcl_WrongNumArgs( interp, 1, objv, "?zipFile? ?mountPoint?" ); + return TCL_ERROR; + } + if( objc > 1 ) { + zipFile = Tcl_GetString( objv[1] ); + } + if( objc > 2 ) { + mountPoint = Tcl_GetString( objv[2] ); + } + return Zvfs_Mount( interp, zipFile, mountPoint ); +} +/* + *---------------------------------------------------------------------- + * + * UnmountObjCmd -- + * + * This function implements the [zvfs::unmount] command. + * + * zvfs::unmount mountPoint + * + * Unmount the given mountPoint if it is mounted in our + * filesystem. + * + * Results: + * 0 on success, -1 on failure. + * + *---------------------------------------------------------------------- + */ +static int +UnmountObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +) { + if( objc != 2 ) { + Tcl_WrongNumArgs( interp, objc, objv, "mountPoint" ); + return TCL_ERROR; + } + return Zvfs_Unmount( interp, Tcl_GetString(objv[1]) ); +} +/* + *---------------------------------------------------------------------- + * + * Zvfs_Init, Zvfs_SafeInit -- + * + * Initialize the zvfs package. + * + * Safe interpreters do not receive the ability to mount and + * unmount zip files. + * + * Results: + * Standard Tcl result + * + *---------------------------------------------------------------------- + */ +int +Zvfs_SafeInit( Tcl_Interp *interp ) +{ +#ifdef USE_TCL_STUBS + if( Tcl_InitStubs( interp, "8.0", 0 ) == TCL_ERROR ) return TCL_ERROR; +#endif + if( !local.isInit ) { + /* Register the filesystem and initialize the hash tables. */ + Tcl_FSRegister( 0, &zvfsFilesystem ); + Tcl_InitHashTable( &local.fileHash, TCL_STRING_KEYS ); + Tcl_InitHashTable( &local.archiveHash, TCL_STRING_KEYS ); + local.isInit = 1; + } + Tcl_PkgProvide( interp, "zvfs", "1.0" ); + return TCL_OK; +} +int +Zvfs_Init( Tcl_Interp *interp ) +{ + if( Zvfs_SafeInit( interp ) == TCL_ERROR ) return TCL_ERROR; + if( !Tcl_IsSafe(interp) ) { + Tcl_CreateObjCommand(interp, "zvfs::mount", MountObjCmd, 0, 0); + Tcl_CreateObjCommand(interp, "zvfs::unmount", UnmountObjCmd, 0, 0); + } + return TCL_OK; } diff --git a/saotk/util/tkCanvPsScaled.c b/saotk/util/tkCanvPsScaled.c new file mode 100644 index 0000000..6214c20 --- /dev/null +++ b/saotk/util/tkCanvPsScaled.c @@ -0,0 +1,686 @@ +/* + * tkCanvPs.c -- + * + * This module provides Postscript output support for canvases, including + * the "postscript" widget command plus a few utility functions used for + * generating Postscript. + * + * Copyright (c) 1991-1994 The Regents of the University of California. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * RCS: @(#) $Id: tkCanvPs.c,v 1.2 2011/07/26 21:07:34 joye Exp $ + */ +/* + * This contains an override for saotk that allows scaling */ + +#include "tkInt.h" +#include "tkCanvas.h" + +/*** waj ***/ +float psScale =1; +/*** waj ***/ + +/* + * See tkCanvas.h for key data structures used to implement canvases. + */ + + +/* + * One of the following structures is created to keep track of Postscript + * output being generated. It consists mostly of information provided on the + * widget command line. + */ + +typedef struct TkPostscriptInfo { + Tk_Window tkwin; /* The canvas being printed. */ + int x, y, width, height; /* Area to print, in canvas pixel + * coordinates. */ + int x2, y2; /* x+width and y+height. */ + char *pageXString; /* String value of "-pagex" option or NULL. */ + char *pageYString; /* String value of "-pagey" option or NULL. */ + double pageX, pageY; /* Postscript coordinates (in points) + * corresponding to pageXString and + * pageYString. Don't forget that y-values + * grow upwards for Postscript! */ + char *pageWidthString; /* Printed width of output. */ + char *pageHeightString; /* Printed height of output. */ + double scale; /* Scale factor for conversion: each pixel + * maps into this many points. */ + Tk_Anchor pageAnchor; /* How to anchor bbox on Postscript page. */ + int rotate; /* Non-zero means output should be rotated on + * page (landscape mode). */ + char *fontVar; /* If non-NULL, gives name of global variable + * containing font mapping information. + * Malloc'ed. */ + char *colorVar; /* If non-NULL, give name of global variable + * containing color mapping information. + * Malloc'ed. */ + char *colorMode; /* Mode for handling colors: "monochrome", + * "gray", or "color". Malloc'ed. */ + int colorLevel; /* Numeric value corresponding to colorMode: 0 + * for mono, 1 for gray, 2 for color. */ + char *fileName; /* Name of file in which to write Postscript; + * NULL means return Postscript info as + * result. Malloc'ed. */ + char *channelName; /* If -channel is specified, the name of the + * channel to use. */ + Tcl_Channel chan; /* Open channel corresponding to fileName. */ + Tcl_HashTable fontTable; /* Hash table containing names of all font + * families used in output. The hash table + * values are not used. */ + int prepass; /* Non-zero means that we're currently in the + * pre-pass that collects font information, so + * the Postscript generated isn't relevant. */ + int prolog; /* Non-zero means output should contain the + * prolog definitions in the header. */ +} TkPostscriptInfo; + +/* + * The table below provides a template that's used to process arguments to the + * canvas "postscript" command and fill in TkPostscriptInfo structures. + */ + +static Tk_ConfigSpec configSpecs[] = { + {TK_CONFIG_STRING, "-colormap", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, colorVar), 0}, + {TK_CONFIG_STRING, "-colormode", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, colorMode), 0}, + {TK_CONFIG_STRING, "-file", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, fileName), 0}, + {TK_CONFIG_STRING, "-channel", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, channelName), 0}, + {TK_CONFIG_STRING, "-fontmap", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, fontVar), 0}, + {TK_CONFIG_PIXELS, "-height", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, height), 0}, + {TK_CONFIG_ANCHOR, "-pageanchor", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, pageAnchor), 0}, + {TK_CONFIG_STRING, "-pageheight", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, pageHeightString), 0}, + {TK_CONFIG_STRING, "-pagewidth", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, pageWidthString), 0}, + {TK_CONFIG_STRING, "-pagex", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, pageXString), 0}, + {TK_CONFIG_STRING, "-pagey", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, pageYString), 0}, + {TK_CONFIG_BOOLEAN, "-prolog", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, prolog), 0}, + {TK_CONFIG_BOOLEAN, "-rotate", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, rotate), 0}, + {TK_CONFIG_PIXELS, "-width", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, width), 0}, + {TK_CONFIG_PIXELS, "-x", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, x), 0}, + {TK_CONFIG_PIXELS, "-y", NULL, NULL, + "", Tk_Offset(TkPostscriptInfo, y), 0}, + {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +/* + * Forward declarations for functions defined later in this file: + */ + +static int GetPostscriptPoints(Tcl_Interp *interp, + char *string, double *doublePtr); + +/* + *-------------------------------------------------------------- + * + * TkCanvPostscriptCmd -- + * + * This function is invoked to process the "postscript" options of the + * widget command for canvas widgets. See the user documentation for + * details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + + /* ARGSUSED */ +int +TkCanvPostscriptCmd( + TkCanvas *canvasPtr, /* Information about canvas widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int argc, /* Number of arguments. */ + CONST char **argv) /* Argument strings. Caller has already parsed + * this command enough to know that argv[1] is + * "postscript". */ +{ + TkPostscriptInfo psInfo, *psInfoPtr = &psInfo; + Tk_PostscriptInfo oldInfoPtr; + int result; + Tk_Item *itemPtr; +#define STRING_LENGTH 400 + char string[STRING_LENGTH+1]; + CONST char *p; + time_t now; + size_t length; + Tk_Window tkwin = canvasPtr->tkwin; + Tcl_HashSearch search; + Tcl_HashEntry *hPtr; + Tcl_DString buffer; + char psenccmd[] = "::tk::ensure_psenc_is_loaded"; + int deltaX = 0, deltaY = 0; /* Offset of lower-left corner of area to be + * marked up, measured in canvas units from + * the positioning point on the page (reflects + * anchor position). Initial values needed + * only to stop compiler warnings. */ + + /* + * Initialize the data structure describing Postscript generation, then + * process all the arguments to fill the data structure in. + */ + + result = Tcl_EvalEx(interp,psenccmd,-1,TCL_EVAL_GLOBAL); + if (result != TCL_OK) { + return result; + } + oldInfoPtr = canvasPtr->psInfo; + canvasPtr->psInfo = (Tk_PostscriptInfo) psInfoPtr; + psInfo.tkwin = canvasPtr->tkwin; + psInfo.x = canvasPtr->xOrigin; + psInfo.y = canvasPtr->yOrigin; + psInfo.width = -1; + psInfo.height = -1; + psInfo.pageXString = NULL; + psInfo.pageYString = NULL; + psInfo.pageX = 72*4.25; + psInfo.pageY = 72*5.5; + psInfo.pageWidthString = NULL; + psInfo.pageHeightString = NULL; + psInfo.scale = 1.0; + psInfo.pageAnchor = TK_ANCHOR_CENTER; + psInfo.rotate = 0; + psInfo.fontVar = NULL; + psInfo.colorVar = NULL; + psInfo.colorMode = NULL; + psInfo.colorLevel = 0; + psInfo.fileName = NULL; + psInfo.channelName = NULL; + psInfo.chan = NULL; + psInfo.prepass = 0; + psInfo.prolog = 1; + Tcl_InitHashTable(&psInfo.fontTable, TCL_STRING_KEYS); + result = Tk_ConfigureWidget(interp, tkwin, configSpecs, argc-2, argv+2, + (char *) &psInfo, TK_CONFIG_ARGV_ONLY); + if (result != TCL_OK) { + goto cleanup; + } + + if (psInfo.width == -1) { + psInfo.width = Tk_Width(tkwin); + } + if (psInfo.height == -1) { + psInfo.height = Tk_Height(tkwin); + } + psInfo.x2 = psInfo.x + psInfo.width; + psInfo.y2 = psInfo.y + psInfo.height; + + if (psInfo.pageXString != NULL) { + if (GetPostscriptPoints(interp, psInfo.pageXString, + &psInfo.pageX) != TCL_OK) { + goto cleanup; + } + } + if (psInfo.pageYString != NULL) { + if (GetPostscriptPoints(interp, psInfo.pageYString, + &psInfo.pageY) != TCL_OK) { + goto cleanup; + } + } + if (psInfo.pageWidthString != NULL) { + if (GetPostscriptPoints(interp, psInfo.pageWidthString, + &psInfo.scale) != TCL_OK) { + goto cleanup; + } + psInfo.scale /= psInfo.width; + } else if (psInfo.pageHeightString != NULL) { + if (GetPostscriptPoints(interp, psInfo.pageHeightString, + &psInfo.scale) != TCL_OK) { + goto cleanup; + } + psInfo.scale /= psInfo.height; + } else { + psInfo.scale = (72.0/25.4)*WidthMMOfScreen(Tk_Screen(tkwin)); + psInfo.scale /= WidthOfScreen(Tk_Screen(tkwin)); + } + switch (psInfo.pageAnchor) { + case TK_ANCHOR_NW: + case TK_ANCHOR_W: + case TK_ANCHOR_SW: + deltaX = 0; + break; + case TK_ANCHOR_N: + case TK_ANCHOR_CENTER: + case TK_ANCHOR_S: + deltaX = -psInfo.width/2; + break; + case TK_ANCHOR_NE: + case TK_ANCHOR_E: + case TK_ANCHOR_SE: + deltaX = -psInfo.width; + break; + } + switch (psInfo.pageAnchor) { + case TK_ANCHOR_NW: + case TK_ANCHOR_N: + case TK_ANCHOR_NE: + deltaY = - psInfo.height; + break; + case TK_ANCHOR_W: + case TK_ANCHOR_CENTER: + case TK_ANCHOR_E: + deltaY = -psInfo.height/2; + break; + case TK_ANCHOR_SW: + case TK_ANCHOR_S: + case TK_ANCHOR_SE: + deltaY = 0; + break; + } + + if (psInfo.colorMode == NULL) { + psInfo.colorLevel = 2; + } else { + length = strlen(psInfo.colorMode); + if (strncmp(psInfo.colorMode, "monochrome", length) == 0) { + psInfo.colorLevel = 0; + } else if (strncmp(psInfo.colorMode, "gray", length) == 0) { + psInfo.colorLevel = 1; + } else if (strncmp(psInfo.colorMode, "color", length) == 0) { + psInfo.colorLevel = 2; + } else { + Tcl_AppendResult(interp, "bad color mode \"", psInfo.colorMode, + "\": must be monochrome, gray, or color", NULL); + goto cleanup; + } + } + + if (psInfo.fileName != NULL) { + /* + * Check that -file and -channel are not both specified. + */ + + if (psInfo.channelName != NULL) { + Tcl_AppendResult(interp, "can't specify both -file", + " and -channel", NULL); + result = TCL_ERROR; + goto cleanup; + } + + /* + * Check that we are not in a safe interpreter. If we are, disallow + * the -file specification. + */ + + if (Tcl_IsSafe(interp)) { + Tcl_AppendResult(interp, "can't specify -file in a", + " safe interpreter", NULL); + result = TCL_ERROR; + goto cleanup; + } + + p = Tcl_TranslateFileName(interp, psInfo.fileName, &buffer); + if (p == NULL) { + goto cleanup; + } + psInfo.chan = Tcl_OpenFileChannel(interp, p, "w", 0666); + Tcl_DStringFree(&buffer); + if (psInfo.chan == NULL) { + goto cleanup; + } + } + + if (psInfo.channelName != NULL) { + int mode; + + /* + * Check that the channel is found in this interpreter and that it is + * open for writing. + */ + + psInfo.chan = Tcl_GetChannel(interp, psInfo.channelName, &mode); + if (psInfo.chan == (Tcl_Channel) NULL) { + result = TCL_ERROR; + goto cleanup; + } + if ((mode & TCL_WRITABLE) == 0) { + Tcl_AppendResult(interp, "channel \"", psInfo.channelName, + "\" wasn't opened for writing", NULL); + result = TCL_ERROR; + goto cleanup; + } + } + + /* + * Make a pre-pass over all of the items, generating Postscript and then + * throwing it away. The purpose of this pass is just to collect + * information about all the fonts in use, so that we can output font + * information in the proper form required by the Document Structuring + * Conventions. + */ + + psInfo.prepass = 1; + for (itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = itemPtr->nextPtr) { + if ((itemPtr->x1 >= psInfo.x2) || (itemPtr->x2 < psInfo.x) + || (itemPtr->y1 >= psInfo.y2) || (itemPtr->y2 < psInfo.y)) { + continue; + } + if (itemPtr->typePtr->postscriptProc == NULL) { + continue; + } + result = (*itemPtr->typePtr->postscriptProc)(interp, + (Tk_Canvas) canvasPtr, itemPtr, 1); + Tcl_ResetResult(interp); + if (result != TCL_OK) { + /* + * An error just occurred. Just skip out of this loop. There's no + * need to report the error now; it can be reported later (errors + * can happen later that don't happen now, so we still have to + * check for errors later anyway). + */ + break; + } + } + psInfo.prepass = 0; + + /* + * Generate the header and prolog for the Postscript. + */ + + if (psInfo.prolog) { + Tcl_AppendResult(interp, "%!PS-Adobe-3.0 EPSF-3.0\n", + "%%Creator: Tk Canvas Widget\n", NULL); +#ifdef HAVE_PW_GECOS + if (!Tcl_IsSafe(interp)) { + struct passwd *pwPtr = getpwuid(getuid()); /* INTL: Native. */ + + Tcl_AppendResult(interp, "%%For: ", + (pwPtr != NULL) ? pwPtr->pw_gecos : "Unknown", "\n", NULL); + endpwent(); + } +#endif /* HAVE_PW_GECOS */ + Tcl_AppendResult(interp, "%%Title: Window ", Tk_PathName(tkwin), "\n", + NULL); + time(&now); + Tcl_AppendResult(interp, "%%CreationDate: ", + ctime(&now), NULL); /* INTL: Native. */ + if (!psInfo.rotate) { + sprintf(string, "%d %d %d %d", + (int) (psInfo.pageX + psInfo.scale*deltaX), + (int) (psInfo.pageY + psInfo.scale*deltaY), + (int) (psInfo.pageX + psInfo.scale*(deltaX + psInfo.width) + + 1.0), + (int) (psInfo.pageY + psInfo.scale*(deltaY + psInfo.height) + + 1.0)); + } else { + sprintf(string, "%d %d %d %d", + (int) (psInfo.pageX - psInfo.scale*(deltaY+psInfo.height)), + (int) (psInfo.pageY + psInfo.scale*deltaX), + (int) (psInfo.pageX - psInfo.scale*deltaY + 1.0), + (int) (psInfo.pageY + psInfo.scale*(deltaX + psInfo.width) + + 1.0)); + } + Tcl_AppendResult(interp, "%%BoundingBox: ", string, "\n", NULL); + Tcl_AppendResult(interp, "%%Pages: 1\n", + "%%DocumentData: Clean7Bit\n", NULL); + Tcl_AppendResult(interp, "%%Orientation: ", + psInfo.rotate ? "Landscape\n" : "Portrait\n", NULL); + p = "%%DocumentNeededResources: font "; + for (hPtr = Tcl_FirstHashEntry(&psInfo.fontTable, &search); + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + Tcl_AppendResult(interp, p, + Tcl_GetHashKey(&psInfo.fontTable, hPtr), "\n", NULL); + p = "%%+ font "; + } + Tcl_AppendResult(interp, "%%EndComments\n\n", NULL); + + /* + * Insert the prolog + */ + + Tcl_AppendResult(interp, Tcl_GetVar(interp,"::tk::ps_preamable", + TCL_GLOBAL_ONLY), NULL); + + if (psInfo.chan != NULL) { + Tcl_Write(psInfo.chan, Tcl_GetStringResult(interp), -1); + Tcl_ResetResult(canvasPtr->interp); + } + + /* + * Document setup: set the color level and include fonts. + */ + + sprintf(string, "/CL %d def\n", psInfo.colorLevel); + Tcl_AppendResult(interp, "%%BeginSetup\n", string, NULL); + for (hPtr = Tcl_FirstHashEntry(&psInfo.fontTable, &search); + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + Tcl_AppendResult(interp, "%%IncludeResource: font ", + Tcl_GetHashKey(&psInfo.fontTable, hPtr), "\n", NULL); + } + Tcl_AppendResult(interp, "%%EndSetup\n\n", NULL); + + /* + * Page setup: move to page positioning point, rotate if needed, set + * scale factor, offset for proper anchor position, and set clip + * region. + */ + + Tcl_AppendResult(interp, "%%Page: 1 1\n", "save\n", NULL); + sprintf(string, "%.1f %.1f translate\n", psInfo.pageX, psInfo.pageY); + Tcl_AppendResult(interp, string, NULL); + if (psInfo.rotate) { + Tcl_AppendResult(interp, "90 rotate\n", NULL); + } + sprintf(string, "%.4g %.4g scale\n", psInfo.scale, psInfo.scale); + Tcl_AppendResult(interp, string, NULL); + sprintf(string, "%d %d translate\n", deltaX - psInfo.x, deltaY); + Tcl_AppendResult(interp, string, NULL); + sprintf(string, + "%d %.15g moveto %d %.15g lineto %d %.15g lineto %d %.15g", + psInfo.x, Tk_PostscriptY((double)psInfo.y, + (Tk_PostscriptInfo)psInfoPtr), + psInfo.x2, Tk_PostscriptY((double)psInfo.y, + (Tk_PostscriptInfo)psInfoPtr), + psInfo.x2, Tk_PostscriptY((double)psInfo.y2, + (Tk_PostscriptInfo)psInfoPtr), + psInfo.x, Tk_PostscriptY((double)psInfo.y2, + (Tk_PostscriptInfo)psInfoPtr)); + Tcl_AppendResult(interp, string, + " lineto closepath clip newpath\n", NULL); + } + /*** waj ***/ + sprintf(string, "%g %g translate\n", + (Tk_PostscriptY((double) psInfo.x, (Tk_PostscriptInfo) &psInfo) - + Tk_PostscriptY((double) psInfo.x2, (Tk_PostscriptInfo) &psInfo)) + *(1.-psScale)/2., + (Tk_PostscriptY((double) psInfo.y, (Tk_PostscriptInfo) &psInfo) - + Tk_PostscriptY((double) psInfo.y2, (Tk_PostscriptInfo) &psInfo)) + *(1.-psScale)/2.); + Tcl_AppendResult(interp, string, (char *) NULL); + + sprintf(string, "%.4g %.4g scale\n", psScale, psScale); + Tcl_AppendResult(interp, string, (char *) NULL); + /*** waj ***/ + if (psInfo.chan != NULL) { + Tcl_Write(psInfo.chan, Tcl_GetStringResult(interp), -1); + Tcl_ResetResult(canvasPtr->interp); + } + + /* + * Iterate through all the items, having each relevant one draw itself. + * Quit if any of the items returns an error. + */ + + result = TCL_OK; + for (itemPtr = canvasPtr->firstItemPtr; itemPtr != NULL; + itemPtr = itemPtr->nextPtr) { + if ((itemPtr->x1 >= psInfo.x2) || (itemPtr->x2 < psInfo.x) + || (itemPtr->y1 >= psInfo.y2) || (itemPtr->y2 < psInfo.y)) { + continue; + } + if (itemPtr->typePtr->postscriptProc == NULL) { + continue; + } + if (itemPtr->state == TK_STATE_HIDDEN) { + continue; + } + Tcl_AppendResult(interp, "gsave\n", NULL); + result = (*itemPtr->typePtr->postscriptProc)(interp, + (Tk_Canvas) canvasPtr, itemPtr, 0); + if (result != TCL_OK) { + char msg[64 + TCL_INTEGER_SPACE]; + + sprintf(msg, "\n (generating Postscript for item %d)", + itemPtr->id); + Tcl_AddErrorInfo(interp, msg); + goto cleanup; + } + Tcl_AppendResult(interp, "grestore\n", NULL); + if (psInfo.chan != NULL) { + Tcl_Write(psInfo.chan, Tcl_GetStringResult(interp), -1); + Tcl_ResetResult(interp); + } + } + + /* + * Output page-end information, such as commands to print the page and + * document trailer stuff. + */ + + if (psInfo.prolog) { + Tcl_AppendResult(interp, "restore showpage\n\n", + "%%Trailer\nend\n%%EOF\n", NULL); + } + if (psInfo.chan != NULL) { + Tcl_Write(psInfo.chan, Tcl_GetStringResult(interp), -1); + Tcl_ResetResult(canvasPtr->interp); + } + + /* + * Clean up psInfo to release malloc'ed stuff. + */ + + cleanup: + if (psInfo.pageXString != NULL) { + ckfree(psInfo.pageXString); + } + if (psInfo.pageYString != NULL) { + ckfree(psInfo.pageYString); + } + if (psInfo.pageWidthString != NULL) { + ckfree(psInfo.pageWidthString); + } + if (psInfo.pageHeightString != NULL) { + ckfree(psInfo.pageHeightString); + } + if (psInfo.fontVar != NULL) { + ckfree(psInfo.fontVar); + } + if (psInfo.colorVar != NULL) { + ckfree(psInfo.colorVar); + } + if (psInfo.colorMode != NULL) { + ckfree(psInfo.colorMode); + } + if (psInfo.fileName != NULL) { + ckfree(psInfo.fileName); + } + if ((psInfo.chan != NULL) && (psInfo.channelName == NULL)) { + Tcl_Close(interp, psInfo.chan); + } + if (psInfo.channelName != NULL) { + ckfree(psInfo.channelName); + } + Tcl_DeleteHashTable(&psInfo.fontTable); + canvasPtr->psInfo = (Tk_PostscriptInfo) oldInfoPtr; + return result; +} + +/* + *-------------------------------------------------------------- + * + * GetPostscriptPoints -- + * + * Given a string, returns the number of Postscript points corresponding + * to that string. + * + * Results: + * The return value is a standard Tcl return result. If TCL_OK is + * returned, then everything went well and the screen distance is stored + * at *doublePtr; otherwise TCL_ERROR is returned and an error message is + * left in the interp's result. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static int +GetPostscriptPoints( + Tcl_Interp *interp, /* Use this for error reporting. */ + char *string, /* String describing a screen distance. */ + double *doublePtr) /* Place to store converted result. */ +{ + char *end; + double d; + + d = strtod(string, &end); + if (end == string) { + goto error; + } + while ((*end != '\0') && isspace(UCHAR(*end))) { + end++; + } + switch (*end) { + case 'c': + d *= 72.0/2.54; + end++; + break; + case 'i': + d *= 72.0; + end++; + break; + case 'm': + d *= 72.0/25.4; + end++; + break; + case 0: + break; + case 'p': + end++; + break; + default: + goto error; + } + while ((*end != '\0') && isspace(UCHAR(*end))) { + end++; + } + if (*end != 0) { + goto error; + } + *doublePtr = d; + return TCL_OK; + + error: + Tcl_AppendResult(interp, "bad distance \"", string, "\"", NULL); + return TCL_ERROR; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */